Android MTP流程

yooooooo發表於2024-08-17

概要

本文的目的是介紹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。 MtpPacketMtpEventPacket負責對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連線訊息,並最終通知UsbManagerUsbManager發出廣播,並且廣播被MtpReceiver收到;MtpReceiver收到廣播後會啟動MtpService,同時通知MediaProviderMediaProvider會與MtpService繫結,若Android裝置中的檔案結構有變化(如"新鍵檔案"),MediaProvider則會通知MtpServiceMtpService啟動後會建立MtpDatabase;之後,還會建立MtpServerMtpServer會和MtpDatabase關聯。然後,MtpService會遍歷本地的儲存裝置,並建立相應的MtpStorage,並將該MtpStorage新增到MtpDatabaseMtpServer中。最後,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中刪除相應的儲存裝置。

相關文章