概要
本文的目的是介紹Android系統中MTP的一些相關知識。主要的內容包括:第1部分 MTP簡介 對Mtp協議進行簡單的介紹。第2部分 MTP框架 介紹Android系統下MTP的框架。第3部分 MTP啟動流程 詳細分析MTP服務的啟動流程,包括Java層, JNI層, kernel相關知識的介紹。第4部分 MTP協議之I->R流程 以"PC中開啟一個MTP上的檔案(讀取檔案內容)"為例,來對"MTP協議中Initiator到Reponser的流程"進行說明。第5部分 MTP協議之R->I流程 以"Android裝置中將一個檔案複製到其他目錄"來對"MTP協議中Reponser到Initiator的流程"進行說明。注意:本文的MTP分析的軟體環境Android 4.3 + Kernel 3.0!
第1部分 MTP簡介
MTP,全稱是Media Transfer Protocol(媒體傳輸協議)。它是微軟的一個為計算機和行動式裝置之間傳輸影像、音樂等所定製的協議。
Android從3.0開始支援MTP。MTP的應用分兩種角色,一個是作為Initiator,另一個作為Responder。以"Android平板電腦"連線"PC"為例,他們的關係如圖1-01所示。
Initiator —— 在MTP中所有的請求都有Initiator發起。例如,PC請求獲取Android平板電腦上的檔案資料。
Responder —— 它會處理Initiator的請求;除此之外,Responder也會傳送Event事件。
注意:關於MTP的詳細規格請參考《MTP_Specification_V1.0》!
第2部分 MTP框架
說明:
在Kernel層,USB驅動負責資料交換,而MTP驅動負責和上層進行通訊,同時也和USB驅動進行通訊。
(01)USB驅動負責資料交換,是指Android裝置和PC透過USB資料線連線之後,實際的資料交換是經過USB資料線傳送給USB驅動的。
(02)對於"MTP請求"而言,MTP驅動會從USB驅動中解析出的MTP請求資料,然後傳遞給上層。而對於上層傳來的"MTP反饋",MTP驅動也會將反饋內容打包好之後,透過傳遞給USB驅動。
在JNI層,MtpServer會不斷地監聽Kernel的訊息"MTP請求",並對相應的訊息進行相關處理。同時,MTP的Event事件也是透過MtpServer傳送給MTP驅動的。 MtpStorage對應一個"儲存單元";例如,SD卡就對應一個MtpStorage。 MtpPacket和MtpEventPacket負責對MTP訊息進行打包。android_mtp_MtpServer是一個JNI類,它是"JNI層的MtpServer 和 Java層的MtpServer"溝通的橋樑。android_mtp_MtpDatabase也是一個JNI類,JNI層透過它實現了對MtpDatabase(Framework層)的操作。
在Framework層,MtpServer相當於一個伺服器,它透過和底層進行通訊從而提供了MTP的相關服務。MtpDatabase充當著資料庫的功能,但它本身並沒有資料庫對資料進行儲存,本質上是透過MediaProvider資料庫獲取所需要的資料。MtpStorage對應一個"儲存單元",它和"JNI層的MtpStorage"相對應。
在Application層,MtpReceiver負責接收廣播,接收到廣播後會啟動/關閉MtpService;例如,MtpReceiver收到"Android裝置 和 PC連上"的訊息時,會啟動MtpService。 MtpService的作用是提供管理MTP的服務,它會啟動MtpServer,以及將本地儲存內容和MTP的內容同步。 MediaProvider在MTP中的角色,是本地儲存內容查詢和本地內容同步;例如,本地新增一個檔案時,MediaProvider會通知MtpServer從而進行MTP資料同步。
第3部分 MTP啟動流程
該部分對MTP服務的啟動流程進行詳細介紹。我們先透過時序圖對MTP啟動流程有個整體印象,然後再透過程式碼進行詳細分析。其中,涉及的內容,包括Java層、JNI層和Kernel。
MTP服務啟動時,Java層的程式流程如下圖3-01所示。
說明:MTP服務啟動的觸發事件是"PC和Android裝置建立MTP連線"。當她們建立MTP連線時,USB驅動將產生USB連線訊息,並最終通知UsbManager。UsbManager發出廣播,並且廣播被MtpReceiver收到;MtpReceiver收到廣播後會啟動MtpService,同時通知MediaProvider。MediaProvider會與MtpService繫結,若Android裝置中的檔案結構有變化(如"新鍵檔案"),MediaProvider則會通知MtpService。MtpService啟動後會建立MtpDatabase;之後,還會建立MtpServer,MtpServer會和MtpDatabase關聯。然後,MtpService會遍歷本地的儲存裝置,並建立相應的MtpStorage,並將該MtpStorage新增到MtpDatabase和MtpServer中。最後,MtpService會啟動MtpServer。
MTP服務啟動時,JNI層的程式流程如下圖3-02所示。
說明: 前面說過MtpService啟動後會先後建立MtpDatabase物件和MtpServer物件(Java層),然後啟動MtpServer(Java層)。
在建立MtpDatabase物件時,會透過native_setup()呼叫JNI本地方法。目的是進行初始化,為後面的MtpServer呼叫做準備。
在建立MtpServer物件(Java層)時,會透過native_setup()呼叫JNI本地方法。在本地方法中,開啟MTP驅動建立的檔案節點"/dev/mtp_usb",並會獲取MyMtpDatabase物件,然後建立"MtpServer物件(JNI層)"。
在啟動MtpServer執行緒時,會對應的執行MtpServer(JNI層)的run()方法。MtpServer(JNI層)的run()中會不斷的從"/dev/mtp_usb"中讀取資料,並進行相應的處理。
涉及到的主要檔案的路徑:
packages/providers/MediaProvider/src/com/android/providers/media/MtpReceiver.java
packages/providers/MediaProvider/src/com/android/providers/media/MtpService.java
packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
frameworks/base/media/java/android/mtp/MtpServer.java
frameworks/base/media/java/android/mtp/MtpDatabase.java
frameworks/base/media/java/android/mtp/MtpStorage.java
frameworks/base/media/jni/android_mtp_MtpServer.cpp
frameworks/base/media/jni/android_mtp_MtpDatabase.cpp
frameworks/av/media/mtp/MtpServer.h
frameworks/av/media/mtp/MtpServer.cpp
frameworks/av/media/mtp/MtpDatabase.h
USB_STATE廣播,即"android.hardware.usb.action.USB_STATE"廣播。它是在USB連上/斷開時,由UsbManager發出的廣播;MtpReceive會接收該廣播並進行處理。
例如,當"Android裝置"和"PC"透過USB連線時,MtpReceiver會接收到USB_STATE廣播,並判斷"USB是不是連上,MTP是不是Enable狀態"從而決定是否啟動MtpService。
接下來,透過程式碼對MTP的服務啟動的各個流程進行分析
1 USB_STATE廣播
USB_STATE廣播,即"android.hardware.usb.action.USB_STATE"廣播。它是在USB連上/斷開時,由UsbManager發出的廣播;MtpReceive會接收該廣播並進行處理。
例如,當"Android裝置"和"PC"透過USB連線時,MtpReceiver會接收到USB_STATE廣播,並判斷"USB是不是連上,MTP是不是Enable狀態"從而決定是否啟動MtpService。
1.1 MtpReceiver監聽廣播的註冊
MtpReceiver.java在它對應的manifest中註冊監聽"android.intent.action.BOOT_COMPLETED" 和 "android.hardware.usb.action.USB_STATE" 監聽。
packages/providers/MediaProvider/AndroidManifest.xml中的原始碼如下:
<receiver android:name=".MtpReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<intent-filter>
<action android:name="android.hardware.usb.action.USB_STATE" />
</intent-filter>
</receiver>
1.2 MtpReceiver對廣播的處理
MtpReceiver對廣播的處理在MtpReceiver.java中實現,原始碼如下:
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
final Intent usbState = context.registerReceiver(
null, new IntentFilter(UsbManager.ACTION_USB_STATE));
if (usbState != null) {
handleUsbState(context, usbState);
}
} else if (UsbManager.ACTION_USB_STATE.equals(action)) {
handleUsbState(context, intent);
}
}
說明:
MtpReceiver的onReceive()中會處理Intent.ACTION_BOOT_COMPLETED 和 UsbManager.ACTION_USB_STATE 這兩個廣播。
Intent.ACTION_BOOT_COMPLETED 和 UsbManager.ACTION_USB_STATE 的處理流程一樣,最終都是透過handleUsbState()來處理的。下面的是基於UsbManager.ACTION_USB_STATE廣播。
當"Android裝置"和"PC"連線時,Android系統會檢測USB連線事件併發出UsbManager.ACTION_USB_STATE廣播。MtpReceiver最終會呼叫handleUsbState()對該廣播進行處理。
1.3 MtpReceiver的handleUsbState()
handleUsbState()也在MtpReceiver.java中實現,原始碼如下:
private void handleUsbState(Context context, Intent intent) {
Bundle extras = intent.getExtras();
boolean connected = extras.getBoolean(UsbManager.USB_CONFIGURED); // 獲取USB的連線狀態
boolean mtpEnabled = extras.getBoolean(UsbManager.USB_FUNCTION_MTP); // 獲取MTP的Enable狀態
boolean ptpEnabled = extras.getBoolean(UsbManager.USB_FUNCTION_PTP); // 獲取PTP的Enable狀態
if (connected && (mtpEnabled || ptpEnabled)) {
// 如果USB是連線狀態,並且“MTP或者PTP是Enable狀態”就執行下面的程式碼
intent = new Intent(context, MtpService.class);
if (ptpEnabled) {
intent.putExtra(UsbManager.USB_FUNCTION_PTP, true);
}
// 啟動MtpService服務
context.startService(intent);
// 通知MediaProvider,MTP已經連上。
context.getContentResolver().insert(Uri.parse(
"content://media/none/mtp_connected"), null);
} else {
// 結束MtpService服務
context.stopService(new Intent(context, MtpService.class));
// 通知MediaProvider,MTP已經斷開。
context.getContentResolver().delete(Uri.parse(
"content://media/none/mtp_connected"), null, null);
}
}
說明:
handleUsbState()會先獲取"USB連線狀態","MTP和PTP的Enable"狀態。
如果USB是連上的,並且MTP或PTP是Enable,則啟動MtpService,並通知MediaProvider。
否則的話,則終止MtpService,並通知MediaProvider。
小結:MtpReceiver會監聽"Android裝置開機完成廣播" 和 "USB連線/斷開廣播"的處理。到收到廣播時,會根據"USB的連線狀態,MTP/PTP的Enable狀態"決定對MTP的處理。如果是連上狀態,而且MTP服務是Enable的,則啟動MtpService服務;並且通知MediaProvider。
2 startService()
在"Android裝置與PC連上,並且MTP是Enable"的情況下,MtpService會被啟動。在"Android裝置與PC斷開"時,MtpService會被終止。
MtpService的作用是提供管理MTP的服務。例如,MtpService啟動時,它會遍歷Android裝置上所有的儲存裝置,如果該儲存裝置是掛載的,則建立該儲存裝置對應的MtpStorage物件,並將該MtpStorage物件新增到MtpDatabase和MtpServer中。在Android裝置中儲存結構發生變化時,會收到MediaProvider發來的訊息,進而將訊息轉發給MtpServer,進行MTP同步。
下面,透過程式碼對MtpService進行介紹。
2.1 建立MtpService
MtpService繼承於Service,這意味著它是一個服務。根據服務的執行流程,MtpService在建立後會執行onCreate()函式。
MtpService中onCreate()的原始碼如下:
public class MtpService extends Service {
@Override
public void onCreate() {
// 監聽“螢幕解鎖”廣播
registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_USER_PRESENT));
// 獲取StorageManager物件。根據靜態工廠方法獲取的。
mStorageManager = StorageManager.from(this);
synchronized (mBinder) {
// 根據“螢幕鎖定與否”來啟動/禁用Mtp功能。
// 如果是當前使用者在設定了螢幕解鎖密碼的情況下鎖屏,則禁用Mtp功能。
updateDisabledStateLocked();
// 監聽“儲存裝置的掛載/解除安裝等廣播事件”。
mStorageManager.registerListener(mStorageEventListener);
// 遍歷“Android裝置”上所有儲存裝置。
// 如果該儲存裝置是“掛載狀態(MEDIA_MOUNTED)”,則透過Mtp鎖定該儲存裝置;
// 這裡的Mtp鎖定,是指Mtp能識別到該儲存裝置,並將該儲存裝置對映到PC上。
StorageVolume[] volumes = mStorageManager.getVolumeList();
mVolumes = volumes;
for (int i = 0; i < volumes.length; i++) {
String path = volumes[i].getPath();
String state = mStorageManager.getVolumeState(path);
if (Environment.MEDIA_MOUNTED.equals(state)) {
volumeMountedLocked(path);
}
}
}
}
...
}
說明:
(01) 如果當前使用者在設定了"螢幕鎖定密碼"的情況下將Android裝置鎖屏,此時MTP功能是被禁用掉的。
updateDisabledStateLocked()的作用,就是處理這種情況的。如果"使用者不是當前使用者" 或者 "使用者在設定了'螢幕鎖定密碼'的情況下將Android裝置鎖屏";此時MTP功能都是被禁用了的。
mReceiver是處理解鎖的廣播。當螢幕鎖解除之後,MTP又能恢復正常工作!
(02) MTP是將Android裝置的儲存裝置對映到PC上。因此,Android裝置上的儲存裝置如果被使用者解除安裝掉的話,要通知PC;而mStorageEventListener就是來監聽"Android裝置上的儲存裝置的掛載/解除安裝狀態"的。
2.2 volumeMountedLocked()
volumeMountedLocked()在MtpService.java中實現,原始碼如下:
private void volumeMountedLocked(String path) {
// 忽略“隨身碟”
if(MediaProvider.UDISK_MOUNT_POINT.equals(path))
return;
// 在所有的儲存裝置中遍歷,找出該“path對應的儲存裝置”,並將它新增到mVolumeMap中。
for (int i = 0; i < mVolumes.length; i++) {
StorageVolume volume = mVolumes[i];
if (volume.getPath().equals(path)) {
long reserveSpace = volume.getMtpReserveSpace() * 1024 * 1024;
if(path.equals(MediaProvider.LOCAL_MOUNT_POINT))
volume.setStorageId(0);
else if(path.equals(MediaProvider.UDISK_MOUNT_POINT)){
volume.setStorageId(2);
}else{
volume.setStorageId(1);
}
mVolumeMap.put(path, volume);
if (!mMtpDisabled) {
if (volume.isPrimary() || !mPtpMode) {
addStorageLocked(volume);
}
}
break;
}
}
}
說明:
雖然 volumeMountedLocked()呼叫addStorageLocked()。但此時沒有進行實質性的動作,真正對映的工作是在onStartCommand()中完成的,即在服務啟動之後完成的。
2.3 onStartCommand()
由於MtpService是"Started Service"型別的服務,而不是"Bound Service"。所以,MtpService啟動之後會執行onStartCommand()。
MtpService.java中onStartCommand()的原始碼如下:
public int onStartCommand(Intent intent, int flags, int startId) {
synchronized (mBinder) {
// 根據“螢幕鎖定與否”來啟動/禁用Mtp功能。
// 如果是當前使用者在設定了螢幕解鎖密碼的情況下鎖屏,則禁用Mtp功能。
updateDisabledStateLocked();
mPtpMode = (intent == null ? false
: intent.getBooleanExtra(UsbManager.USB_FUNCTION_PTP, false));
String[] subdirs = null;
if (mPtpMode) {
// PTP模型才執行到這裡。
int count = PTP_DIRECTORIES.length;
subdirs = new String[count];
for (int i = 0; i < count; i++) {
File file =
Environment.getExternalStoragePublicDirectory(PTP_DIRECTORIES[i]);
// make sure this directory exists
file.mkdirs();
subdirs[i] = file.getPath();
}
}
// 獲取“主儲存分割槽”。
final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes);
// 新建MtpDatabase物件
mDatabase = new MtpDatabase(this, MediaProvider.LOCAL_VOLUME,
primary.getPath(), subdirs);
manageServiceLocked();
}
return START_STICKY;
}
說明:onStartCommand()中建立了mDatabase物件,然後呼叫manageServiceLocked()。
2.4 manageServiceLocked()
該函式在MtpService.java中實現,原始碼如下:
private void manageServiceLocked() {
// 是不是當前使用者
final boolean isCurrentUser = UserHandle.myUserId() == ActivityManager.getCurrentUser();
if (mServer == null && isCurrentUser) {
// 新建mServer物件
mServer = new MtpServer(mDatabase, mPtpMode);
// 如果MTP沒被禁用調,則呼叫addStorageDevicesLocked()
if (!mMtpDisabled) {
addStorageDevicesLocked();
}
// 啟動MtpServer
mServer.start();
} else if (mServer != null && !isCurrentUser) {
mServer = null;
}
}
說明:manageServiceLocked()會新建MtpServer物件,在透過addStorageDevicesLocked()將儲存裝置新增到Mtp上之後,再啟動MtpServer。MtpService會啟動一個執行緒用於管理Android裝置和PC之間的通訊,它也會"將透過addStorageDevicesLocked()新增的儲存裝置"對映到PC上。
2.5 addStorageDevicesLocked()
該函式在MtpService.java中實現,原始碼如下:
private void addStorageDevicesLocked() {
if (mPtpMode) {
final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes);
final String path = primary.getPath();
if (path != null) {
String state = mStorageManager.getVolumeState(path);
if (Environment.MEDIA_MOUNTED.equals(state)) {
addStorageLocked(mVolumeMap.get(path));
}
}
} else {
// 如果是MTP模式,則呼叫addStorageLocked(),mVolumeMap中的儲存裝置新增到Mtp中。
for (StorageVolume volume : mVolumeMap.values()) {
addStorageLocked(volume);
}
}
}
說明: 如果是MTP模式,則呼叫addStorageLocked(),mVolumeMap中的儲存裝置新增到Mtp中。mVolumeMap在volumeMountedLocked()中已經被初始化,它儲存的是掛載狀態的儲存裝置。
2.6 addStorageLocked()
該函式在MtpService.java中實現,原始碼如下:
該函式在MtpService.java中實現,原始碼如下:
private void addStorageLocked(StorageVolume volume) {
// 忽略 “volume為空” 或者 “volume是u盤”的情況
if(volume != null && MediaProvider.UDISK_MOUNT_POINT.equals(volume.getPath()))
return;
// 新建該“儲存裝置”對應的MtpStorage,並將該“儲存裝置”新增到雜湊表mStorageMap中。
MtpStorage storage = new MtpStorage(volume, getApplicationContext());
String path = storage.getPath();
mStorageMap.put(path, storage);
// 將storage新增到mDatabase中
if (mDatabase != null) {
mDatabase.addStorage(storage);
}
// 將storage新增到mServer中
if (mServer != null) {
mServer.addStorage(storage);
}
}
小結:
MtpService服務的主要工作是搜尋出Android裝置上所有"掛載"的儲存裝置,然後根據這些掛載的儲存裝置分別建立MtpStorage物件;隨後,將MtpStorage物件新增到MtpDatabase中進行資料轉換和同步,同時也將MtpStorage新增MtpServer,隨後的"Android裝置和PC之間的通訊和資料同步等工作"就交由MtpServer主導進行。
3 insert("mtp_connected")
MtpReceiver在handleUsbState()透過insert()將訊息上報給MediaProvider。
3.1 MediaProvider靜態註冊塊
MediaProvider在靜態塊中新增了對"mtp_connected"事件的監聽。原始碼如下:
static
{
...
URI_MATCHER.addURI("media", "*/mtp_connected", MTP_CONNECTED);
...
}
3.2 MediaProvider中的insert()
當MtpReceiver給MediaProvider發出"插入的mtp_connected"訊息時,MediaProvider會執行insert()函式。原始碼如下:
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
int match = URI_MATCHER.match(uri);
ArrayList<Long> notifyRowIds = new ArrayList<Long>();
Uri newUri = insertInternal(uri, match, initialValues, notifyRowIds);
notifyMtp(notifyRowIds);
// we will signal instead after file transfer is successful.
if (newUri != null && match != MTP_OBJECTS) {
getContext().getContentResolver().notifyChange(uri, null);
}
return newUri;
}
3.3 MediaProvider中的insertInternal()
MediaProvider對插入訊息的處理在insertInternal()中執行,它的原始碼如下:
private Uri insertInternal(Uri uri, int match, ContentValues initialValues,
ArrayList<Long> notifyRowIds) {
...
switch (match) {
case MTP_CONNECTED:
synchronized (mMtpServiceConnection) {
if (mMtpService == null) {
Context context = getContext();
// 將MediaProvider和MtpService繫結。
context.bindService(new Intent(context, MtpService.class),
mMtpServiceConnection, Context.BIND_AUTO_CREATE);
}
}
break;
...
}
...
}
說明:insertInternal()會呼叫bindService()將MediaProvider和MtpService繫結。
4 bindService()
在MediaProvider的insertInternal()中會呼叫bindService(),而bindService()則會將MediaProvider和MtpService繫結。
而之所以要繫結,是為了將實現MTP同步。例如,當Android裝置上新建一個檔案時,最終後同步到MediaProvider資料庫中;而MediaProvider資料庫看同步完成之後,會傳送訊息給MtpService通知它進行MTP的同步。
小結:MediaProvider在MtpService啟動時和MtpService繫結,在MtpService終止時解除繫結。而繫結的目的是為了實現MTP同步功能。
5 mDatabase=new MtpDatabase()
在MtpService的onStartCommand()中,會透過new MtpDatabase()建立MtpDatabase物件。
MtpDatabase在MTP中,充當著資料庫的功能。但它本身並沒有資料庫對資料進行儲存,本質上是透過MediaProvider資料庫獲取所需要的資料。例如,當在PC上,需要讀取某個檔案時,MtpDatabase會在MediaProvider資料庫中查詢出檔案的相關資訊(包括檔名、大小、副檔名等);然後將這些資訊交給MtpServer,MtpServer將訊息傳遞給JNI,在JNI中會透過檔名開啟,然後再檔案控制代碼等資訊傳遞給Kernel;Kernel根據檔案控制代碼讀取檔案資訊,並傳給PC。
下面,透過程式碼檢視以下MtpDatabase的流程。先看MtpDatabase建構函式,原始碼如下:
public class MtpDatabase {
public MtpDatabase(Context context, String volumeName, String storagePath,
String[] subDirectories) {
// 呼叫JNI函式
native_setup();
// 初始化
mContext = context;
mPackageName = context.getPackageName();
mMediaProvider = context.getContentResolver().acquireProvider("media");
mVolumeName = volumeName;
mMediaStoragePath = storagePath;
mObjectsUri = Files.getMtpObjectsUri(volumeName);
mMediaScanner = new MediaScanner(context);
mSubDirectories = subDirectories;
...
// 初始化裝置屬性,將其儲存到SharedPreferences中
initDeviceProperties(context);
}
...
}
說明:MtpDatabase的建構函式主要進行的是初始化工作,它首先會呼叫native_setup()。
5.1 native_setup()
native_setup()在MtpDatabase.java中是一個本地方法。它的相關定義如下:
1 static {
2 System.loadLibrary("media_jni");
3 }
4 private native final void native_setup();
說明:
從中可以看出native_setup()的實現在libmedia_jni.so中,準確的說是在android_mtp_MtpDatabase.cpp中的註冊。相關的程式碼如下:
1 static JNINativeMethod gMtpDatabaseMethods[] = {
2 {"native_setup", "()V", (void *)android_mtp_MtpDatabase_setup},
3 {"native_finalize", "()V", (void *)android_mtp_MtpDatabase_finalize},
4 };
從中,我們看出,native_setup()實際上是和JNI中的android_mtp_MtpDatabase_setup()對應。android_mtp_MtpDatabase_setup()的原始碼如下:
static void android_mtp_MtpDatabase_setup(JNIEnv *env, jobject thiz)
{
// 新建MyMtpDatabase物件database
MyMtpDatabase* database = new MyMtpDatabase(env, thiz);
// 將database物件儲存“field_context”域中。
env->SetIntField(thiz, field_context, (int)database);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
}
說明:
android_mtp_MtpDatabase_setup()會建立一個MyMtpDatabase物件,並將該物件儲存"field_context"域中。這個被儲存的MyMtpDatabase物件在後面會被用到。
5.2 database=new MyMtpDatabase()
MyMtpDatabase位於"JNI層",它與"Java層的MtpDatabase"對應。MTP透過呼叫MyMtpDatabase的介面,給Java層的MtpDatabase傳送訊息;從而進行相關MTP資料的收集。
小結:MtpDatabase的相當於MTP的資料庫。在MtpDatabase的建立過程中,它最終會呼叫JNI本地方法,建立一個MyMtpDatabase物件,並將該物件儲存在域field_context中。MTP透過呼叫儲存在field_context域中的MyMtpDatabase物件,從而呼叫MtpDatabase,進而獲取相關的資料。
6 mServer=new MtpServer()
MtpServer是一個實現Runnable介面,它相當於一個執行緒;並且在MtpService中被啟動。
MtpServer在MTP的Framework層中,充當著伺服器的角色。例如,當MTP服務啟動時,它會通知底層;當Android裝置中新增檔案時,它會收到MtpService的訊息,並將該訊息轉發給底層。
MtpServer的建構函式原始碼如下:
public class MtpServer implements Runnable {
public MtpServer(MtpDatabase database, boolean usePtp) {
native_setup(database, usePtp);
}
...
}
說明:MtpServer實現了Runnable介面,在它的建構函式中,它會呼叫native_setup()本地方法。在MtpServer中,宣告瞭許多native方法,它們的相關程式碼如下:
static {
System.loadLibrary("media_jni");
}
private native final void native_setup(MtpDatabase database, boolean usePtp);
private native final void native_run();
private native final void native_cleanup();
private native final void native_send_object_added(int handle);
private native final void native_send_object_removed(int handle);
private native final void native_add_storage(MtpStorage storage);
private native final void native_remove_storage(int storageId);
6.1 native_setup()
MtpServer中的native方法在android_mtp_MtpServer.cpp中註冊,登錄檔格如下:
static JNINativeMethod gMethods[] = {
{"native_setup", "(Landroid/mtp/MtpDatabase;Z)V",
(void *)android_mtp_MtpServer_setup},
{"native_run", "()V", (void *)android_mtp_MtpServer_run},
{"native_cleanup", "()V", (void *)android_mtp_MtpServer_cleanup},
{"native_send_object_added", "(I)V", (void *)android_mtp_MtpServer_send_object_added},
{"native_send_object_removed", "(I)V", (void *)android_mtp_MtpServer_send_object_removed},
{"native_add_storage", "(Landroid/mtp/MtpStorage;)V",
(void *)android_mtp_MtpServer_add_storage},
{"native_remove_storage", "(I)V", (void *)android_mtp_MtpServer_remove_storage},
};
從中,我們直到native_setup()實際上是與android_mtp_MtpServer_setup()對應。
android_mtp_MtpServer_setup()的原始碼如下:
static void android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, jboolean usePtp)
{
// 開啟檔案“/dev/mtp_usb”
int fd = open("/dev/mtp_usb", O_RDWR);
if (fd >= 0) {
// 根據“fd”和“MtpDatabase”建立MtpServer物件server
MtpServer* server = new MtpServer(fd, getMtpDatabase(env, javaDatabase),
usePtp, AID_MEDIA_RW, 0664, 0775);
// 將server物件儲存到“field_MtpServer_nativeContext”域中。
env->SetIntField(thiz, field_MtpServer_nativeContext, (int)server);
} else {
ALOGE("could not open MTP driver, errno: %d", errno);
}
}
說明:
(01) fd是檔案"/dev/mtp_usb"的控制代碼。實際上,MTP是透過"/dev/mtp_usb"去監聽PC的請求和向PC傳送資料的。
(02) getMtpDatabase(env, javaDatabase)返回的是MtpDatabase物件。
(03) 根據fd和getMtpDatabase()返回的MtpDatabase物件,建立server物件;然後透過SetIntFiel()將server物件儲存到field_MtpServer_nativeContext這個域中。
6.2 fd = open("/dev/mtp_usb")
android_mtp_MtpServer_setup()會開啟"/dev/mtp_usb"檔案。在MTP中,MtpServer會不斷的從"/dev/mtp_usb"去讀取資料來監聽PC的請求;同時,資料反饋和其他事件也是透過"/dev/mtp_usb"去反饋給Kernel的。
6.3 getMtpDatabase()
該函式在android_mtp_MtpDatabase.cpp中實現,原始碼如下:
1 MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database) {
2 return (MtpDatabase *)env->GetIntField(database, field_context);
3 }
說明:field_context在前面介紹的"android_mtp_MtpDatabase_setup()"中被初始化。所以,這裡實際上返回的是MyMtpDatabase物件。
6.4 new MtpServer(fd, ...)
MtpServer的建構函式在MtpServer.cpp中實現,原始碼如下:
MtpServer::MtpServer(int fd, MtpDatabase* database, bool ptp,
int fileGroup, int filePerm, int directoryPerm)
: mFD(fd),
mDatabase(database),
mPtp(ptp),
mFileGroup(fileGroup),
mFilePermission(filePerm),
mDirectoryPermission(directoryPerm),
mSessionID(0),
mSessionOpen(false),
mSendObjectHandle(kInvalidObjectHandle),
mSendObjectFormat(0),
mSendObjectFileSize(0) {}
說明:
其中比較重要的兩則資訊:(01) mFD是"/dev/mtp_usb"的檔案控制代碼。 (02) mDatabase是上一步getMtpDatabase()返回的MtpDatabase物件。
7 storage = new MtpStorage()
一個MtpStorage物件代表一個MTP儲存單元。當Android裝置和PC連上時,可能有幾個儲存單元:例如,內部儲存分割槽,SD卡分割槽等。
MtpStorage的建構函式如下:
public class MtpStorage {
private final int mStorageId;
private final String mPath;
private final String mDescription;
private final long mReserveSpace;
private final boolean mRemovable;
private final long mMaxFileSize;
public MtpStorage(StorageVolume volume, Context context) {
// 儲存裝置ID
mStorageId = volume.getStorageId();
// 對應“Android裝置”上的儲存路徑
mPath = volume.getPath();
// 描述
mDescription = context.getResources().getString(volume.getDescriptionId());
// (對MTP而言)可用空間
mReserveSpace = volume.getMtpReserveSpace() * 1024L * 1024L;
// 是否可移除
mRemovable = volume.isRemovable();
// 最大檔案大小。(0表示無限制)
mMaxFileSize = volume.getMaxFileSize();
}
...
}
8 mDatabase.addStorage(storage)
MtpDatabase.java中addStorage()的原始碼如下:
public void addStorage(MtpStorage storage) {
mStorageMap.put(storage.getPath(), storage);
}
public void removeStorage(MtpStorage storage) {
mStorageMap.remove(storage.getPath());
}
說明:mStorageMap是個HashMap物件。此處,addStorage()的作用就是將"儲存裝置的資訊儲存到雜湊表中"。當該儲存裝置被解除安裝時,會呼叫MtpDatabase.java的removeStorage(),進而從mStorageMap中刪除相應的儲存裝置。