首页 > 其他分享 >Android MTP流程

Android MTP流程

时间:2024-08-17 20:40:24浏览次数:9  
标签:MtpService USB MtpDatabase MTP mtp MtpServer Android 流程

概要

本文的目的是介绍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) {
    // 忽略“U盘”
    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中删除相应的存储设备。

标签:MtpService,USB,MtpDatabase,MTP,mtp,MtpServer,Android,流程
From: https://www.cnblogs.com/linhaostudy/p/18364927

相关文章

  • HTTP 请求流程
    HTTP的请求流程包括地址解析、封装HTTP数据包、封装TCP包、建立TCP连接、客户端发送请求、服务端响应、服务端关闭TCP连接,流程如下:地址解析:通过域名系统DNS解析服务器域名从而获得主机的IP地址。例如客户端的浏览器请求:http://localhost.com:8080/index.html,则可从......
  • Scheduler工作流程
    Scheduler的目的React实现Scheuler的目的是想要实现时间切片。时间切片是指:将长任务拆分成多段,每次执行一小段的任务的操作。Scheduler的实现React利用MessageChannel创建出一个port实例,port实例有两个属性port1和port2。如果在Scheduler当中调用port2.postM......
  • JAVA执行流程
    基本流程Java程序的运行必须经过编写、编译和运行3个步骤:1、编写:是指在Java开发环境中进行程序代码的输入,最终形成后缀名为.java的Java源文件。2、编译:是指使用Java编译器对源文件进行错误排査的过程,编译后将生成后缀名为.class的字节码文件,不像C语言那样生成可执......
  • 【Java毕设选题推荐】基于SpringBoot的springboot基于Android的房屋租赁App
    前言:我是IT源码社,从事计算机开发行业数年,专注Java领域,专业提供程序设计开发、源码分享、技术指导讲解、定制和毕业设计服务......
  • 误闯机器学习(第一关-概念和流程)
    以下内容,皆为原创,实属不易,请各位帅锅,镁铝点点赞赞和关注吧!好戏开场了。一.什么是机器学习        机器学习就是从数据中自动分析获取模型(总结出的数据),并训练模型,去预测数据。    内心独白:就好比我们人从日常生活中,归纳总结得出经验。利用总结的经验去得出......
  • Android 13 about launcher3 (1)
    Android13Launcher3android13#launcher3#分屏相关Launcher3修改wmdensity界面布局不改变/packages/apps/Launcher3/src/com/android/launcher3/InvariantDeviceProfile.javaLauncher的默认配置加载类,通过InvariantDeviceProfile方法可以看出,CellLayout显示的应用行数和列......
  • Android架构组件中的MVVM
    Android架构组件中的MVVM(Model-View-ViewModel)模式是一种广泛应用的设计模式,它通过将应用程序分为三个主要部分(Model、View、ViewModel)来分离用户界面和业务逻辑,从而提高代码的可维护性、可扩展性和可测试性。下面将详细介绍MVVM模式在Android开发中的实战应用,包括基本概念......
  • Android开发 - 使用自定义接口在新窗口中传回数据
    在Android开发中,有时候我们需要在新打开的窗口中传递数据回来。例如:在新打开的DialogFragment窗口传递数据回MainActivity启动窗口代码举例首先在MainActivity中定义实例化一个DialogFragment窗口//MainActivity.java//点击按钮打开一个DialogFragment窗口......
  • Android Linux EAS优化-schedtune
    SchedTuneSchedTune是一项与CPU调频相关的性能提升技术,它实现为一个cgroup控制器。这个控制器提供了一个名称为schedtune.boost的配置参数,运行时系统可以使用它来更改该组中的进程的调度方式。每当调整这个参数的时候,它会使受影响的进程看起来比实际更重(或更轻)。如果一个组被提......
  • h5直播源码,用户登录流程及权限校验
    h5直播源码,用户登录流程及权限校验今天我们来看一下用户登录的流程前端部分 以一个后台管理系统登录为例:登录篇1.用户输入账号和密码点击登录传给服务器用户名和密码2.服务器验证成功后给客户端传递一个token,并且把这个token存在cookies中,这样下次再向服务器发请......