Android MTP 实现框架分析

有了上篇文章Andrid MTP之UsbService分析的基础,本片文章就来分析MTP,那么我们首先要搞清楚,如何切换MTP

MTP切换

通过上篇文章分析可知,当手机通过USB连接电脑的时候,会发送一个USBADB通知,而且会发送一个USB状态切换广播。

通知界面如下

在这里插入图片描述

当点击最后一个通知,就会出现如下的USB功能设置选项

在这里插入图片描述
当选择Transfer files选项的时候,就是USB开启了MTP模式。开启MTP的核心代码如下

mUsbManager = context.getSystemService(UsbManager.class);
mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP, true);

UsbManager的服务端实现为UsbService,而UsbServicesetCurrentFunction()方法在
Andrid MTP之UsbService分析中已经分析过,这里就简单的总结下UsbService切换MTP模式所做的工作:

  1. 设置sys.usb.config属性的值为mtp,adb,底层响应属性改变,切换到MTP功能。
  2. 上层mUEventObserver监听到USB状态改变,在手机通过USB连接到电脑的情况下,会生成两个通知(如上面的第一幅图所示),以及发送一个USB状态改变的广播。

那么今天我们分析的起点就是这个USB状态改变广播,发送广播的代码如下

        private void updateUsbStateBroadcastIfNeeded(boolean configChanged) {
            // send a sticky broadcast containing current USB state
            Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
                    | Intent.FLAG_RECEIVER_FOREGROUND);
            intent.putExtra(UsbManager.USB_CONNECTED, mConnected);
            intent.putExtra(UsbManager.USB_HOST_CONNECTED, mHostConnected);
            intent.putExtra(UsbManager.USB_CONFIGURED, mConfigured);
            intent.putExtra(UsbManager.USB_DATA_UNLOCKED,
                    isUsbTransferAllowed() && mUsbDataUnlocked);
            intent.putExtra(UsbManager.USB_CONFIG_CHANGED, configChanged);

            if (mCurrentFunctions != null) {
                String[] functions = mCurrentFunctions.split(",");
                for (int i = 0; i < functions.length; i++) {
                    final String function = functions[i];
                    if (UsbManager.USB_FUNCTION_NONE.equals(function)) {
                        continue;
                    }
                    intent.putExtra(function, true);
                }
            }

            // send broadcast intent only if the USB state has changed
            // 如果usb状态没有改变并且配置也没有改变,就不发送广播
            if (!isUsbStateChanged(intent) && !configChanged) {
                if (DEBUG) {
                    Slog.d(TAG, "skip broadcasting " + intent + " extras: " + intent.getExtras());
                }
                return;
            }

            if (DEBUG) Slog.d(TAG, "broadcasting " + intent + " extras: " + intent.getExtras());
            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
            mBroadcastedIntent = intent;
        }

那么,这个广播的接收者是谁呢?接收这个广播又干了啥呢?这些话题都是本文要讨论的,也就是MTP的实现。

MtpReceiver

MtpReceiver.java属于packages/providers/MediaProvider模块,MediaProvider这个模块还包括了音频扫描工作以及一个铃声的选择功能,当然最主要功能还是向外部提供手机存储中的数据,其中就包括向电脑端提供MTP数据。

public class MtpReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {// 处理开机广播
            // If we somehow fail to configure after boot, it becomes difficult to
            // recover usb state. Thus we always configure once on boot, but it
            // has no effect if Mtp is disabled or already configured.
            MtpServer.configure(false);
            final Intent usbState = context.registerReceiver(
                    null, new IntentFilter(UsbManager.ACTION_USB_STATE));
            if (usbState != null) {
                handleUsbState(context, usbState, true);
            }
        } else if (UsbManager.ACTION_USB_STATE.equals(action)) { // 处理USB状态改变的广播
            handleUsbState(context, intent, false);
        }
    }
}

MtpReceiver虽然处理了两种广播,但是殊途同归,最终都是调用同一个方法进行处理的。

那么接下来看下handleUsbState()方法如何处理USB状态改变的

    private void handleUsbState(Context context, Intent intent, boolean from_boot) {
        Bundle extras = intent.getExtras();
        boolean configured = extras.getBoolean(UsbManager.USB_CONFIGURED);
        boolean connected = extras.getBoolean(UsbManager.USB_CONNECTED);
        boolean mtpEnabled = extras.getBoolean(UsbManager.USB_FUNCTION_MTP);
        boolean ptpEnabled = extras.getBoolean(UsbManager.USB_FUNCTION_PTP);
        boolean unlocked = extras.getBoolean(UsbManager.USB_DATA_UNLOCKED);
        boolean configChanged = extras.getBoolean(UsbManager.USB_CONFIG_CHANGED);
		
		// 1.处理两种情况
		// 1.1 mtp/ptp模式 && 配置改变了
		// 1.2 mtp/ptp模式 && usb已连接 && usb没有完成配置工作
        if ((configChanged || (connected && !configured)) && (mtpEnabled || ptpEnabled)) {
            MtpServer.configure(ptpEnabled);
            // tell MediaProvider MTP is configured so it can bind to the service
            context.getContentResolver().insert(Uri.parse(
                    "content://media/none/mtp_connected"), null);
        } 
        // 2. mtp/ptp模式 && usb已经配置
		else if (configured && (mtpEnabled || ptpEnabled)) {
            if(from_boot && mServiceStarted)
                return; // fix the problem of repeated adding storage
            intent = new Intent(context, MtpService.class);
            intent.putExtra(UsbManager.USB_DATA_UNLOCKED, unlocked);
            if (ptpEnabled) {
                intent.putExtra(UsbManager.USB_FUNCTION_PTP, true);
            }
            if (DEBUG) { Log.d(TAG, "handleUsbState startService"); }
            context.startService(intent);
            mServiceStarted = true;
        } 
		// 3. usb断开链接 || 不是mtp/ptp模式
		else if (!connected || !(mtpEnabled || ptpEnabled)) {
            // Only unbind if disconnected or disabled.
            boolean status = context.stopService(new Intent(context, MtpService.class));
            if (DEBUG) { Log.d(TAG, "handleUsbState stopService status=" + status); }
            // tell MediaProvider MTP is disconnected so it can unbind from the service
            context.getContentResolver().delete(Uri.parse(
                    "content://media/none/mtp_connected"), null, null);
        }
    }

从上篇文章可知,USB连接时,状态是从DISCONNECTEDCONNECTED,再到CONFIGURED,因此这里处理的三种情况就非常好理解了。

handleUsbState的第步,对于设置MTP功能来说,这里是处理配置改变,或者已连接但是没有配置完成的情况

			// 1. 底层初始化,并进行USB配置工作
            MtpServer.configure(ptpEnabled);
            // 2. 通知MediaProvider,MTP已经配置完毕,所以可以绑定服务了
            // tell MediaProvider MTP is configured so it can bind to the service
            context.getContentResolver().insert(Uri.parse(
                    "content://media/none/mtp_connected"), null);

首先看下底层是如何进行配置的,调用MtpServer的静态方法configure()MtpServer.java是在frameworks/base/media/java/android/mtp/目录下,这是framework层专用于处理mtp操作的目录

public class MtpServer implements Runnable {
    static {
        System.loadLibrary("media_jni");
    }

    public static void configure(boolean usePtp) {
        native_configure(usePtp);
    }

    public static native final void native_configure(boolean usePtp);
}

MtpServer实现了Runnable接口,那么我们可以猜测,肯定会创建一个线程,那么用这个线程干嘛呢?后面会说道。

MtpServer类在加载的时候会加载一个名为media_jni库,Android 是基于Linux操作系统构建的,因此这个库的全名是libmedia_jni.so,这个库对应的路径是frameworks/base/media/jni目录,而本地方法native_configure()android_mtp_MtpServer.cpp文件中实现。

static void android_mtp_configure(JNIEnv *, jobject, jboolean usePtp) {
    MtpServer::configure(usePtp);
}

本地方法native_configure()在JNI层注册对应的函数为android_mtp_configure(),后面不再说明类似的对应情况,读者可自行查看源码查找。

原来是调用MtpServerconfigure()函数,而MtpServer.cpp是在frameworks/av/media/mtp目录下,对应的库是libmtp.so

IMtpHandle* MtpServer::sHandle = nullptr;

int MtpServer::configure(bool usePtp) {
    // 1. 初始化sHandle
    if (sHandle == nullptr) {
        bool ffs_ok = access(FFS_MTP_EP0, W_OK) == 0;
        sHandle = ffs_ok ? get_ffs_handle() : get_mtp_handle();
    }

    // 2. 调用sHandle的configure方法
    int ret = sHandle->configure(usePtp);
    if (ret) ALOGE("Failed to configure MTP driver!");

    // 3. 设置属性sys.usb.ffs.mtp.ready值为1
    android::base::SetProperty("sys.usb.ffs.mtp.ready", "1");

    return ret;
}

这里最重要的一步就是初始化sHandle变量,我的手机环境中调用的是get_mtp_handle()函数,这个函数的实现类为frameworks/av/media/mtp/MtpDevHandle.cpp

IMtpHandle *get_mtp_handle() {
    return new MtpDevHandle();
}

很简单,就是调用自己的构造方法

MtpDevHandle::MtpDevHandle()
    : mFd(-1) {};

更简单,就是给mFd变量赋值为-1

初始化sHandle变量后,接着会调用它的configure()函数来完成配置工作。

int MtpDevHandle::configure(bool) {
    // Nothing to do, driver can configure itself
    return 0;
}

根据注释可知,什么都不用做,驱动可以自己完成。

配置完成后,最后还把系统属性sys.usb.ffs.mtp.ready的值设置为1,这个属性在我的系统中好像并没有用,sHandler的初始化调用的不是get_ffs_handle()函数,我猜测这个属性是跟这个函数相关的吧。

至此,底层的配置已经完成,再回到handleUsbState的第步,接下来就是通知MediaProvider底层已经配置完毕,可以绑定服务了,绑定哪个服务呢?绑定服务干嘛呢?

public class MediaProvider extends ContentProvider {

    private static final int MTP_CONNECTED = 705;

    static {
        URI_MATCHER.addURI("media", "*/mtp_connected", MTP_CONNECTED);
    }

    public Uri insert(Uri uri, ContentValues initialValues) {
        int match = URI_MATCHER.match(uri);

        ArrayList<Long> notifyRowIds = new ArrayList<Long>();
        // 返回的newUri是null
        Uri newUri = insertInternal(uri, match, initialValues, notifyRowIds);
		
        // ...

        return newUri;
    }
    
    private Uri insertInternal(Uri uri, int match, ContentValues initialValues,
                               ArrayList<Long> notifyRowIds) {
        Uri newUri = null;

        switch (match) {
            // ...

            case MTP_CONNECTED:
                synchronized (mMtpServiceConnection) {
                    if (mMtpService == null) {
                        Context context = getContext();
                        // MTP is connected, so grab a connection to MtpService
                        context.bindService(new Intent(context, MtpService.class),
                                mMtpServiceConnection, Context.BIND_AUTO_CREATE);
                    }
                }

                // ...
                break;

            // ...
        }
        return newUri;
    }
}    

原来绑定的是MtpService,既然是绑定而不是启动,那么肯定是要与服务进行通信,看下mMtpServiceConnection的赋值操作

    private IMtpService mMtpService;

    private final ServiceConnection mMtpServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, android.os.IBinder service) {
            synchronized (this) {
                mMtpService = IMtpService.Stub.asInterface(service);
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName className) {
            synchronized (this) {
                mMtpService = null;
            }
        }
    };

原来是利用AIDL来实现通讯,IMtpService.java类就是使用AIDL语法自动生成的Java类,因此接口方法是定义在IMtpService.aidl中,可以看下这个文件的定义

package com.android.providers.media;

interface IMtpService
{
    void sendObjectAdded(int objectHandle);
    void sendObjectRemoved(int objectHandle);
}

从方法的命名可知,有两种类型的消息,一种是告诉MtpService数据库中增加了某些数据,一种是告诉MtpService数据库中删除了某些数据。例如,当手机中新增一个文件的时候,会使用sendObjectAdded()方法,而删除一个文件会使用另外一种方法。而MtpService在接收消息后,会向底层发送消息,底层会通知PC端。

现在看下handleUsbState的第步,对于MTP来说,就是处理USBCONFIGURED状态,处理逻辑如下

			// MtpService只允许启动一次
            if(from_boot && mServiceStarted)
                return; // fix the problem of repeated adding storage
            intent = new Intent(context, MtpService.class);
            intent.putExtra(UsbManager.USB_DATA_UNLOCKED, unlocked);
            if (ptpEnabled) {
                intent.putExtra(UsbManager.USB_FUNCTION_PTP, true);
            }
            if (DEBUG) { Log.d(TAG, "handleUsbState startService"); }
            context.startService(intent);
            mServiceStarted = true;

又是MtpService,只是这一次换了一个姿势,不是绑定,而是启动。注意参数unlocked的值,前面说过在选择MTP模式时候传入的值是true。这个值非常重要,如果为falsePC端是不会映射手机存储的数据的。

MtpService

终于,MtpService千呼万唤始出来,我们不分析MtpService的绑定过程,只要你懂AIDL就能明白,因此着重看下启动过程到底在干嘛

public class MtpService extends Service {

    @Override
    public void onCreate() {
        mStorageManager = this.getSystemService(StorageManager.class);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        UserHandle user = new UserHandle(ActivityManager.getCurrentUser());
        // 1. 保存所有已挂载的存储的信息
        synchronized (this) {
            // mVolueMap是String->StorageVolume的映射,String代表存储的路径,StorageVolume由StorageManager获取
            mVolumeMap = new HashMap<>();
            // mStroageMap是String->MtpStorage的映射,String代表存储路径,MtpStorage保存存储的信息
            mStorageMap = new HashMap<>();
            // 监听存储的挂载与卸载
            mStorageManager.registerListener(mStorageEventListener);
            mVolumes = StorageManager.getVolumeList(user.getIdentifier(), 0);
            for (StorageVolume volume : mVolumes) {
            	// 如果是已挂载状态
                if (Environment.MEDIA_MOUNTED.equals(volume.getState())) {
                    // 用mVolumeMap和mStorageMap保存信息
                    volumeMountedLocked(volume.getPath());
                } else {
                    Log.e(TAG, "StorageVolume not mounted " + volume.getPath());
                }
            }
        }

		// 2. 向底层进行映射
        synchronized (this) {
        	// mUnlocked值为true
            mUnlocked = intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false);
            // 使用mUnlocked的值更新mMtpDisabled的值
            updateDisabledStateLocked();
            // mPtpMode在MTP模式下当然为false
            mPtpMode = (intent == null ? false
                    : intent.getBooleanExtra(UsbManager.USB_FUNCTION_PTP, false));
            String[] subdirs = null;
            if (mPtpMode) {
                // ...
            }
            // 获取主存储,这也就代表这MTP只映射主存储
            final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes);
            try {
                manageServiceLocked(primary, subdirs, user);
            } catch (PackageManager.NameNotFoundException e) {
                Log.e(TAG, "Couldn't find the current user!: " + e.getMessage());
            }
        }

        return START_REDELIVER_INTENT;
    }

先看下onStartCommand()的第步,这一步是用几个变量保存所有已挂在的存储的信息,其中调用的是volumeMountedLocked()方法保存已挂载存储的信息

    private void volumeMountedLocked(String path) {
        for (int i = 0; i < mVolumes.length; i++) {
            StorageVolume volume = mVolumes[i];
            if (volume.getPath().equals(path)) {
            	// 用mVolumeMap保存已挂在的存储信息,是volume path到StorageVolume的映射
                mVolumeMap.put(path, volume);
                // 此时mMtpDisabled使用的是默认的初始值false
                if (!mMtpDisabled) {
                    // In PTP mode we support only primary storage
                    // 如果存储为主存储或者不是PTP模式时,保存存储的信息
                    // 注意,此时mPtpMode使用的也是默认的初始值false
                    if (volume.isPrimary() || !mPtpMode) {
                        addStorageLocked(volume);
                    }
                }
                break;
            }
        }
    }
    
    private void addStorageLocked(StorageVolume volume) {
    	// MtpStorage就是用来保存存储的各种信息,例如路径,剩余空间等等,之后会在JNI层映射MtpStroage保存的信息
        MtpStorage storage = new MtpStorage(volume, getApplicationContext());
        // 用mStorageMap保存storage path到MtpStorage的映射
        mStorageMap.put(storage.getPath(), storage);

		// ...
		
		// 此时sServerHolder还没有被赋值
        synchronized (MtpService.class) {
            if (sServerHolder != null) {
                sServerHolder.database.addStorage(storage);
                sServerHolder.server.addStorage(storage);
            }
        }
		
		// ...
    }

很简单,就是用几个变量保存了已挂载存储的信息。

接下来看下onStartCommand()的第

        synchronized (this) {
        	// mUnlocked值为true
            mUnlocked = intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false);
            // 为变量mMtpDisabled 赋值为 !mUnlocked;
            updateDisabledStateLocked();
            // mPtpMode在MTP模式下当然为false
            mPtpMode = (intent == null ? false
                    : intent.getBooleanExtra(UsbManager.USB_FUNCTION_PTP, false));
            String[] subdirs = null;
            if (mPtpMode) {
                // ...
            }
            // 获取主存储,这也就代表着MTP只映射主存储
            final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes);
            try {
                manageServiceLocked(primary, subdirs, user);
            } catch (PackageManager.NameNotFoundException e) {
                Log.e(TAG, "Couldn't find the current user!: " + e.getMessage());
            }
        }

几个参数就不多说了,直接看下manageServiceLocked()方法的实现,对于MTP模式来说,这个方法只针对主存储

    private void manageServiceLocked(StorageVolume primary, String[] subdirs, UserHandle user)
            throws PackageManager.NameNotFoundException {
        synchronized (this) {
        	// MtpServer只能被启动一次
            if (sServerHolder != null) {
                if (LOGD) {
                    Log.d(TAG, "Cannot launch second MTP server.");
                }
                // Previously executed MtpServer is still running. It will be terminated
                // because MTP device FD will become invalid soon. Also MtpService will get new
                // intent after that when UsbDeviceManager configures USB with new state.
                return;
            }

			// 1. 创建MTP在Java层和JNI层的的数据库操作代理类对象
            final MtpDatabase database = new MtpDatabase(this,
                    createPackageContextAsUser(this.getPackageName(), 0, user),
                    MediaProvider.EXTERNAL_VOLUME,
                    primary.getPath(), subdirs);
                
            // 2. 创建MtpServer,负责向底层发送信息
            String deviceSerialNumber = Build.SERIAL;
            if (Build.UNKNOWN.equals(deviceSerialNumber)) {
                deviceSerialNumber = "????????";
            }
            final MtpServer server =
                    new MtpServer(
                            database, // 保存了MtpDatabase对象,可以操作数据库
                            mPtpMode,
                            new OnServerTerminated(),
                            Build.MANUFACTURER, // MTP DeviceInfo: Manufacturer
                            Build.MODEL,        // MTP DeviceInfo: Model
                            "1.0",              // MTP DeviceInfo: Device Version
                            deviceSerialNumber,  // MTP DeviceInfo: Serial Number
                            );
			// MtpDatabase对象保存了MtpServer对象,可以通过MtpServer对象向底层发送消息                            
            database.setServer(server);
            
            // 3. 用sServerHolder同时保存server和database
            sServerHolder = new ServerHolder(server, database);

            // Need to run addStorageDevicesLocked after sServerHolder is set since it accesses
            // sServerHolder.
            // 4. 如果MTP开启,向底层映射StorageId
            if (!mMtpDisabled) {
                addStorageDevicesLocked();
            }
			// 5. 开启线程,循环读取请求并处理
            server.start();
        }
    }

首先看第步,创建MtpDatabase对象,MtpDatabaseJava层数据库操作的代理类,看下构造函数

public class MtpDatabase implements AutoCloseable {
	
	// 保存JNI层创建的MtpDatabase对象
    private long mNativeContext;

    private native final void native_setup();

	static {
        System.loadLibrary("media_jni");
    }
    
    public MtpDatabase(Context context, Context userContext, String volumeName, String storagePath,
            String[] subDirectories) {
        // 1. 创建底层的MyMtpDatabase对象,并用this.mNativeContext指向
        native_setup();

		// 2. 初始化操作
        mContext = context;
        mUserContext = userContext;
        mPackageName = context.getPackageName();
        // 用于操作MediaProvider的数据库封装类
        mMediaProvider = userContext.getContentResolver()
                .acquireContentProviderClient(MediaStore.AUTHORITY);
        // 保存存储的名字
        mVolumeName = volumeName;
        // 存储的路径
        mMediaStoragePath = storagePath;
        // 操作MTP对象的URI: content://media/{volumeName}/object
        mObjectsUri = Files.getMtpObjectsUri(volumeName);
        // 用于媒体文件扫描
        mMediaScanner = new MediaScanner(context, mVolumeName);

        mSubDirectories = subDirectories;
        if (subDirectories != null) {
			// ...
        }
		// 为了兼容版本,在高版本中这里没做任何事
        initDeviceProperties(context);
		
		// 系统默认没有这个属性这个属性
        mDeviceType = SystemProperties.getInt("sys.usb.mtp.device_type", 0);

        mCloseGuard.open("close");
    }
}    

MtpDatabase也是加载了libmedia_jni.so库,前面已经说过,这个库对应于frameworks/base/media/jni目录。Java层的MtpDatabase.java的本地方法对应的JNI层的实现类为android_mtp_MtpDatabase.cpp,本地方法native_setup()对应的实现如下

static jfieldID field_context;

static void
android_mtp_MtpDatabase_setup(JNIEnv *env, jobject thiz)
{
	// 1. 创建MyMtpDatabase对象
    MyMtpDatabase* database = new MyMtpDatabase(env, thiz);
    // 2. 把MyMtpDatabase对象地址保存到Java层的MtpDatabase对象的mNativeContext变量中
    env->SetLongField(thiz, field_context, (jlong)database);
    // 如果有异常就打印log,然后清空异常
    checkAndClearExceptionFromCallback(env, __FUNCTION__);
}

这个函数首先创建一个MyMtpDatabase对象,然后保存在Java层的MtpDatabase对象的变量中,那么这个field_context指的是哪个变量呢?原来,在JNI层的库加载的时候,会调用JNI_OnLoad()函数,这个函数在frameworks/base/media/jni/android_media_MediaPlayer.cpp文件中定义,而在JNI_OnLoad()函数中,会调用MtpDatabase.cppregister_android_mtp_MtpDatabase()函数

int register_android_mtp_MtpDatabase(JNIEnv *env)
{
    jclass clazz;

    clazz = env->FindClass("android/mtp/MtpDatabase");
    if (clazz == NULL) {
        ALOGE("Can't find android/mtp/MtpDatabase");
        return -1;
    }
    
	// ...
	
	field_context = env->GetFieldID(clazz, "mNativeContext", "J");
    if (field_context == NULL) {
        ALOGE("Can't find MtpDatabase.mNativeContext");
        return -1;
    }
    
    // ...
}

现在我们应该就明白了,field_context其实代表的就是Java层的MtpDatabase类的mNativeContext变量。

读者一定要懂得JNI的基本知识,本文并不会对JNI的基本知识做过多解释。

再来看下android_mtp_MtpDatabase_setup()函数的第步,构造MyMtpDatabase对象,注意,构造函数传入了Java层的MtpDatabase对象

MyMtpDatabase::MyMtpDatabase(JNIEnv *env, jobject client)
    :   mDatabase(env->NewGlobalRef(client)),
        mIntBuffer(NULL),
        mLongBuffer(NULL),
        mStringBuffer(NULL)
{
    // create buffers for out arguments
    // we don't need to be thread-safe so this is OK
    jintArray intArray = env->NewIntArray(3);
    if (!intArray) {
        return; // Already threw.
    }
    mIntBuffer = (jintArray)env->NewGlobalRef(intArray);
    jlongArray longArray = env->NewLongArray(2);
    if (!longArray) {
        return; // Already threw.
    }
    mLongBuffer = (jlongArray)env->NewGlobalRef(longArray);
    // Needs to be long enough to hold a file path for getObjectFilePath()
    jcharArray charArray = env->NewCharArray(PATH_MAX + 1);
    if (!charArray) {
        return; // Already threw.
    }
    mStringBuffer = (jcharArray)env->NewGlobalRef(charArray);
}

这里初始化了四个全局变量,注意,其中mDatabase指向Java层MtpDatabase对象。

那么,我们回顾下MtpDatabase.javaJNI层的初始化到底做了啥,首先创建了JNI层的MyMtpDatabase对象,在这个对象中用mDatabase保存了JavaMtpDatabase对象,而之后,Java层的MtpDatabase对象的变量mNativeContext保存了JNI层的MyMtpDatabase对象的地址,这就相当于相互保存了。那么,为何要这样设计呢?当我们把Java层的MtpDatabase对象传递到JNI层,就可以通过这个对象获取到JNI层的MyMtpDatabase对象,这样就可以操作JNI层的数据,操作完成后,由于MyMtpDatabase对象保存了Java层的MtpDatabase对象,就可以回调上层的方法。源码中就是这个套路,我们可以学以致用。

再回到manageServiceLocked的第步,创建MtpServer对象,看下它的构造函数

    public MtpServer(
            MtpDatabase database,
            boolean usePtp,
            Runnable onTerminate,
            String deviceInfoManufacturer,
            String deviceInfoModel,
            String deviceInfoDeviceVersion,
            String deviceInfoSerialNumber) {
        mDatabase = Preconditions.checkNotNull(database);
        mOnTerminate = Preconditions.checkNotNull(onTerminate);
        // 创建底层的MtpServer对象,并用this.mNativeContext指向该对象
        native_setup(
                database,
                usePtp,
                deviceInfoManufacturer,
                deviceInfoModel,
                deviceInfoDeviceVersion,
                deviceInfoSerialNumber);
        database.setServer(this);
    }

构造方法中最重要的一步就是调用本地方法进行初始化,实现方法在android_mtp_MtpServer.cpp

static void
android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, jboolean usePtp,
        jstring deviceInfoManufacturer,
        jstring deviceInfoModel,
        jstring deviceInfoDeviceVersion,
        jstring deviceInfoSerialNumber)
{
	// 1. 把jstring转换为本地字符串
    const char *deviceInfoManufacturerStr = env->GetStringUTFChars(deviceInfoManufacturer, NULL);
    const char *deviceInfoModelStr = env->GetStringUTFChars(deviceInfoModel, NULL);
    const char *deviceInfoDeviceVersionStr = env->GetStringUTFChars(deviceInfoDeviceVersion, NULL);
    const char *deviceInfoSerialNumberStr = env->GetStringUTFChars(deviceInfoSerialNumber, NULL);

	// 2. 创建MtpServer对象
    MtpServer* server = new MtpServer(getMtpDatabase(env, javaDatabase),
            usePtp, AID_MEDIA_RW, 0664, 0775,
            MtpString((deviceInfoManufacturerStr != NULL) ? deviceInfoManufacturerStr : ""),
            MtpString((deviceInfoModelStr != NULL) ? deviceInfoModelStr : ""),
            MtpString((deviceInfoDeviceVersionStr != NULL) ? deviceInfoDeviceVersionStr : ""),
            MtpString((deviceInfoSerialNumberStr != NULL) ? deviceInfoSerialNumberStr : ""));
            
	// 3. 释放本地字符串
    if (deviceInfoManufacturerStr != NULL) {
        env->ReleaseStringUTFChars(deviceInfoManufacturer, deviceInfoManufacturerStr);
    }
    if (deviceInfoModelStr != NULL) {
        env->ReleaseStringUTFChars(deviceInfoModel, deviceInfoModelStr);
    }
    if (deviceInfoDeviceVersionStr != NULL) {
        env->ReleaseStringUTFChars(deviceInfoDeviceVersion, deviceInfoDeviceVersionStr);
    }
    if (deviceInfoSerialNumberStr != NULL) {
        env->ReleaseStringUTFChars(deviceInfoSerialNumber, deviceInfoSerialNumberStr);
    }
	// 4. 保存MtpServer对象的地址到Java层MtpServer对象的mNativeContext中
    env->SetLongField(thiz, field_MtpServer_nativeContext, (jlong)server);
}

第一步,是把Java层的字符串转化为native字符串,第三步是释放这里本地字符串资源,这都是JNI的基本操作,不多说。

第二步是创建底层的MtpServer对象,然后第四步是用Java层的MtpServer对象的mNativeContext保存底层的MtpServer对象。

那么,重点关注第二步,看它如何创建底层MtpServer对象

    MtpServer* server = new MtpServer(getMtpDatabase(env, javaDatabase),
            usePtp, AID_MEDIA_RW, 0664, 0775,
            MtpString((deviceInfoManufacturerStr != NULL) ? deviceInfoManufacturerStr : ""),
            MtpString((deviceInfoModelStr != NULL) ? deviceInfoModelStr : ""),
            MtpString((deviceInfoDeviceVersionStr != NULL) ? deviceInfoDeviceVersionStr : ""),
            MtpString((deviceInfoSerialNumberStr != NULL) ? deviceInfoSerialNumberStr : ""));

第一个参数是由getMtpDatase()获取的,看下函数声明

// in android_mtp_MtpDatabase.cpp
extern MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database);

从注释中我们就可以看出,这个实现在android_mtp_MtpDatabase.cpp

MtpDatabase* getMtpDatabase(JNIEnv *env, jobject database) {
    return (MtpDatabase *)env->GetLongField(database, field_context);
}

database指的就是Java层的MtpDatabase对象,这里不就是获取JNI层的MyMtpDatabase对象吗?

现在看下MtpServer的构造函数

MtpServer::MtpServer(MtpDatabase* database, bool ptp,
                    int fileGroup, int filePerm, int directoryPerm,
                    const MtpString& deviceInfoManufacturer,
                    const MtpString& deviceInfoModel,
                    const MtpString& deviceInfoDeviceVersion,
                    const MtpString& deviceInfoSerialNumber)
    :   mDatabase(database),
        mPtp(ptp),
        mFileGroup(fileGroup),
        mFilePermission(filePerm),
        mDirectoryPermission(directoryPerm),
        mDeviceInfoManufacturer(deviceInfoManufacturer),
        mDeviceInfoModel(deviceInfoModel),
        mDeviceInfoDeviceVersion(deviceInfoDeviceVersion),
        mDeviceInfoSerialNumber(deviceInfoSerialNumber),
        mSessionID(0),
        mSessionOpen(false),
        mSendObjectHandle(kInvalidObjectHandle),
        mSendObjectFormat(0),
        mSendObjectFileSize(0),
        mSendObjectModifiedTime(0)
{
}

很简单的初始化,这里比较重要的一步就是在底层的MtpServer对象中用mDatabase指向JNI层的MyMtpDatabase对象,透过这个对象,可以操作上层的MtpDatabse对象。

再回到manageServiceLocked()函数的第步,代码如下

			sServerHolder = new ServerHolder(server, database);
			
            // Need to run addStorageDevicesLocked after sServerHolder is set since it accesses
            // sServerHolder.
            if (!mMtpDisabled) {
            	// 向底层映射存储的信息
                addStorageDevicesLocked();
            }

从注释中可以看出,只有sServerHolder被赋值,才需要运行addStorageDevicesLocked()方法,这个方法是向底层映射存储的信息

    private void addStorageDevicesLocked() {
        if (mPtpMode) {
			// ...
        } else {
            for (StorageVolume volume : mVolumeMap.values()) {
                addStorageLocked(volume);
            }
        }
    }

前面已经说过,mVolumeMap保存的是已挂载的存储,通过遍历它来调用addStorageLocked()方法

    private void addStorageLocked(StorageVolume volume) {
        MtpStorage storage = new MtpStorage(volume, getApplicationContext());
        mStorageMap.put(storage.getPath(), storage);

        if (storage.getStorageId() == StorageVolume.STORAGE_ID_INVALID) {
            Log.w(TAG, "Ignoring volume with invalid MTP storage ID: " + storage);
            return;
        } else {
            Log.d(TAG, "Adding MTP storage 0x" + Integer.toHexString(storage.getStorageId())
                    + " at " + storage.getPath());
        }
		
        synchronized (MtpService.class) {
            if (sServerHolder != null) {
            	// MtpDatabase对象的mStorageMap变量保存MtpStroage对象
                sServerHolder.database.addStorage(storage);
                // MtpServer对象通过本地方法向底层映射MtpStroage对象信息
                sServerHolder.server.addStorage(storage);
            }
        }
		
		// ...
    }

这里主要看下sServerHolder保存的MtpServer对象,是如何向底层进行映射操作的

    public void addStorage(MtpStorage storage) {
        native_add_storage(storage);
    }

JNI层的实现如下

static void
android_mtp_MtpServer_add_storage(JNIEnv *env, jobject thiz, jobject jstorage)
{
    Mutex::Autolock autoLock(sMutex);
	// 1. 获取Java层的MtpServer对象保存的JNI层对象
    MtpServer* server = getMtpServer(env, thiz);
    if (server) {
    	// 获取Java层的MtpStorage对象的各种变量的值
    	// int mStorageId;
        jint storageID = env->GetIntField(jstorage, field_MtpStorage_storageId);
        // String mPath;
        jstring path = (jstring)env->GetObjectField(jstorage, field_MtpStorage_path);
        // String mDescription;
        jstring description = (jstring)env->GetObjectField(jstorage, field_MtpStorage_description);
        // long mReserveSpace;
        jlong reserveSpace = env->GetLongField(jstorage, field_MtpStorage_reserveSpace);
        // boolean mRemovable;
        jboolean removable = env->GetBooleanField(jstorage, field_MtpStorage_removable);
        // long mMaxFileSize;
        jlong maxFileSize = env->GetLongField(jstorage, field_MtpStorage_maxFileSize);

		// 把path转换为本地字符串
        const char *pathStr = env->GetStringUTFChars(path, NULL);
        if (pathStr != NULL) {
        	// 把description转换为本地字符串
            const char *descriptionStr = env->GetStringUTFChars(description, NULL);
            if (descriptionStr != NULL) {
            	// 2. 利用从Java层MtpStorage对象获取到的所有信息,创建底层的MtpStorage对象
                MtpStorage* storage = new MtpStorage(storageID, pathStr, descriptionStr,
                        reserveSpace, removable, maxFileSize);
                // 3. 底层的MtpServer对象保存这个刚创建的MtpStorage对象
                server->addStorage(storage);
                env->ReleaseStringUTFChars(path, pathStr);
                env->ReleaseStringUTFChars(description, descriptionStr);
            } else {
                env->ReleaseStringUTFChars(path, pathStr);
            }
        }
    } else {
        ALOGE("server is null in add_storage");
    }
}

从实现中可以看出,首先是获取了JNI层的MtpServer对象,然后获取Java层的MtpStorage对象的各种信息,并利用这些信息创建了JNI层的MtpStorage对象,最后把创建的MtpStorage对象保存到MtpServer对象中。前两步比较简单,只看下最后一步是如何保存的

MtpStorageList      mStorages;

void MtpServer::addStorage(MtpStorage* storage) {
    Mutex::Autolock autoLock(mMutex);
    // 1. 保存
    mStorages.push(storage);
    // 2. 发送Event
    sendStoreAdded(storage->getStorageID());
}

void MtpServer::sendStoreAdded(MtpStorageID id) {
    ALOGV("sendStoreAdded %08X\n", id);
    sendEvent(MTP_EVENT_STORE_ADDED, id);
}

void MtpServer::sendEvent(MtpEventCode code, uint32_t param1) {
    ALOGD("sendEvent %d\n", mSessionOpen);
    if (mSessionOpen) {
        mEvent.setEventCode(code);
        mEvent.setTransactionID(mRequest.getTransactionID());
        mEvent.setParameter(1, param1);
        if (mEvent.write(sHandle))
            ALOGE("Mtp send event failed: %s", strerror(errno));
    }
}

底层的MtpServer对象用mStorages保存了存储信息,然后利用MTP协议,向电脑端发送了一个MTP Event事件,用于告知电脑端可以映射存储了,电脑端会发送各种命令用于获取存储中的信息,从而在电脑端建立一个文件系统的映射。

现在看下manageServiceLocked()的第步,代码如下

server.start();

看下MtpServerstart()方法

    public void start() {
        Thread thread = new Thread(this, "MtpServer");
        thread.start();
    }

启动一个线程,看下这个线程做了什么

    @Override
    public void run() {
    	// 循环读取MTP指令,执行相应的动作
        native_run();
        // 异常处理工作
        native_cleanup();
        mDatabase.close();
        mOnTerminate.run();
    }

native_run()是在一个新的线程中开启一个无限循环处理PC发过来的请求,而后面的几步就是处理异常情况,我们这里主要集中分析处理请求的过程,看下native_fun()方法的JNI实现

static void
android_mtp_MtpServer_run(JNIEnv *env, jobject thiz)
{
    MtpServer* server = getMtpServer(env, thiz);
    if (server)
    	// 开启无限循环,从节点读取命令,然后执行命令
        server->run();
    else
        ALOGE("server is null in run");
}

调用了底层的MtpServerrun()方法,注意,这个方法是在一个Java创建的线程中执行的,主要就是不断读取MTP节点,解析命令,然后执行相应动作

void MtpServer::run() {
    if (!sHandle) {
        ALOGE("MtpServer was never configured!");
        return;
    }
	// 1. 打开/dev/mtp_usb节点
    if (sHandle->start()) {
        ALOGE("Failed to start usb driver!");
        sHandle->close();
        return;
    }

	// 2. 循环处理事务
    while (1) {
    	// 2.1 用sHandle读取请求
        int ret = mRequest.read(sHandle);
        if (ret < 0) {
            ALOGE("request read returned %d, errno: %d", ret, errno);
            if (errno == ECANCELED) {
                // return to top of loop and wait for next command
                continue;
            }
            break;
        }
        // 解析操作码
        MtpOperationCode operation = mRequest.getOperationCode();
        // 解析事务ID
        MtpTransactionID transaction = mRequest.getTransactionID();

        ALOGV("operation: %s", MtpDebug::getOperationCodeName(operation));
        // FIXME need to generalize this
        // 下面操作码表示是PC端发数据过来
        bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO
                    || operation == MTP_OPERATION_SET_OBJECT_REFERENCES
                    || operation == MTP_OPERATION_SET_OBJECT_PROP_VALUE
                    || operation == MTP_OPERATION_SET_DEVICE_PROP_VALUE);
        if (dataIn) { // 如果是PC端发数据过来,就解析数据
            int ret = mData.read(sHandle);
            if (ret < 0) {
                ALOGE("data read returned %d, errno: %d", ret, errno);
                if (errno == ECANCELED) {
                    // return to top of loop and wait for next command
                    continue;
                }
                break;
            }
            ALOGV("received data:");
        } else {
            mData.reset();
        }

		// 2.2 处理请求
        if (handleRequest()) {
        	// 2.3 成功处理后进行响应
            if (!dataIn && mData.hasData()) { // 代表手机端在发数据
                mData.setOperationCode(operation);
                mData.setTransactionID(transaction);
                ALOGV("sending data:");
                ret = mData.write(sHandle);
                if (ret < 0) {
                    ALOGE("request write returned %d, errno: %d", ret, errno);
                    if (errno == ECANCELED) {
                        // return to top of loop and wait for next command
                        continue;
                    }
                    break;
                }
            }
			// 设置相应的事务ID
            mResponse.setTransactionID(transaction);
            ALOGV("sending response %04X", mResponse.getResponseCode());
            // 写一些数据,并用sHandle发送
            ret = mResponse.write(sHandle);
            const int savedErrno = errno;
            if (ret < 0) {
                ALOGE("request write returned %d, errno: %d", ret, errno);
                if (savedErrno == ECANCELED) {
                    // return to top of loop and wait for next command
                    continue;
                }
                break;
            }
        } else {
            ALOGV("skipping response\n");
        }
    }

	// 处理异常情况,例如断开连接
    // commit any open edits
    int count = mObjectEditList.size();
    for (int i = 0; i < count; i++) {
        ObjectEdit* edit = mObjectEditList[i];
        commitEdit(edit);
        delete edit;
    }
    mObjectEditList.clear();

    if (mSessionOpen)
        mDatabase->sessionEnded();

    sHandle->close();
}

处理过程大致分为两步,首先是打开节点,然后循环读取节点并处理。打开节点这个过程不分析,比较简单,着重看下处理过程。

在分析处理过程之前,这里有几个MTP的概念,首先是transcation,也就是事务的意思,transcation包括三个阶段

  1. 操作请求阶段: 也就是PC端向手机端发送请求
  2. 数据传输阶段: 这个阶段是可选的,也就是请求之后可能PC可能需要传输数据
  3. 相应阶段: 手机处理了PC的请求后,会进行响应。

另外一个概念就是session,也就是会话的意思,一次session代表一次连接环境,有的操作必须要在这个环境中进行,例如只有在MTP已连接的情况下才能向PC发送事件,但是有些操作并不依赖于session,例如PC端要获取设备信息。

读取请求

那么,先来看下transcation的请求阶段,手机端首先调用mRequest.read(sHandle)读取请求

int MtpRequestPacket::read(IMtpHandle *h) {
	// 1. 读取节点的数据到mBuffer中
    int ret = h->read(mBuffer, mBufferSize);
    if (ret < 0) {
        // file read error
        return ret;
    }

    // request packet should have 12 byte header followed by 0 to 5 32-bit arguments
    // 2. 解析数据的大小和参数的个数
    const size_t read_size = static_cast<size_t>(ret);
    if (read_size >= MTP_CONTAINER_HEADER_SIZE
            && read_size <= MTP_CONTAINER_HEADER_SIZE + 5 * sizeof(uint32_t)
            && ((read_size - MTP_CONTAINER_HEADER_SIZE) & 3) == 0) {
        mPacketSize = read_size;
        mParameterCount = (read_size - MTP_CONTAINER_HEADER_SIZE) / sizeof(uint32_t);
    } else {
        ALOGE("Malformed MTP request packet");
        ret = -1;
    }
    return ret;
}

读取的详细操作就不分析了,这里需要关心下读取的数据格式。请求的数据包包括12字节的头部,然后跟随0个或者5个参数,每个参数是4个字节。头文件中定义如下

// Container Offsets
#define MTP_CONTAINER_LENGTH_OFFSET             0	//4个字节
#define MTP_CONTAINER_TYPE_OFFSET               4	//2个字节
#define MTP_CONTAINER_CODE_OFFSET               6	//2个字节
#define MTP_CONTAINER_TRANSACTION_ID_OFFSET     8	//4个字节
#define MTP_CONTAINER_PARAMETER_OFFSET          12	//参数起始位置
#define MTP_CONTAINER_HEADER_SIZE               12	//头部总共12字节

处理请求

读取之后就需要处理请求

bool MtpServer::handleRequest() {
    Mutex::Autolock autoLock(mMutex);
	
	// 获取操作码
    MtpOperationCode operation = mRequest.getOperationCode();
    MtpResponseCode response;

    mResponse.reset();

    if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) {
        // FIXME - need to delete mSendObjectHandle from the database
        ALOGE("expected SendObject after SendObjectInfo");
        mSendObjectHandle = kInvalidObjectHandle;
    }

	// 获取操作类型
    int containertype = mRequest.getContainerType();
    if (containertype != MTP_CONTAINER_TYPE_COMMAND) {
        ALOGE("wrong container type %d", containertype);
        return false;
    }

    ALOGV("got command %s (%x)", MtpDebug::getOperationCodeName(operation), operation);

    switch (operation) {
        case MTP_OPERATION_GET_DEVICE_INFO:
            response = doGetDeviceInfo();
            break;
        case MTP_OPERATION_OPEN_SESSION:
            response = doOpenSession();
            break;
        case MTP_OPERATION_RESET_DEVICE:
        case MTP_OPERATION_CLOSE_SESSION:
            response = doCloseSession();
            break;
        case MTP_OPERATION_GET_STORAGE_IDS:
            response = doGetStorageIDs();
            break;
         case MTP_OPERATION_GET_STORAGE_INFO:
            response = doGetStorageInfo();
            break;
        case MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED:
            response = doGetObjectPropsSupported();
            break;
        case MTP_OPERATION_GET_OBJECT_HANDLES:
            response = doGetObjectHandles();
            break;
        case MTP_OPERATION_GET_NUM_OBJECTS:
            response = doGetNumObjects();
            break;
        case MTP_OPERATION_GET_OBJECT_REFERENCES:
            response = doGetObjectReferences();
            break;
        case MTP_OPERATION_SET_OBJECT_REFERENCES:
            response = doSetObjectReferences();
            break;
        case MTP_OPERATION_GET_OBJECT_PROP_VALUE:
            response = doGetObjectPropValue();
            break;
        case MTP_OPERATION_SET_OBJECT_PROP_VALUE:
            response = doSetObjectPropValue();
            break;
        case MTP_OPERATION_GET_DEVICE_PROP_VALUE:
            response = doGetDevicePropValue();
            break;
        case MTP_OPERATION_SET_DEVICE_PROP_VALUE:
            response = doSetDevicePropValue();
            break;
        case MTP_OPERATION_RESET_DEVICE_PROP_VALUE:
            response = doResetDevicePropValue();
            break;
        case MTP_OPERATION_GET_OBJECT_PROP_LIST:
            response = doGetObjectPropList();
            break;
        case MTP_OPERATION_GET_OBJECT_INFO:
            response = doGetObjectInfo();
            break;
        case MTP_OPERATION_GET_OBJECT:
            response = doGetObject();
            break;
        case MTP_OPERATION_GET_THUMB:
            response = doGetThumb();
            break;
        case MTP_OPERATION_GET_PARTIAL_OBJECT:
        case MTP_OPERATION_GET_PARTIAL_OBJECT_64:
            response = doGetPartialObject(operation);
            break;
        case MTP_OPERATION_SEND_OBJECT_INFO:
            response = doSendObjectInfo();
            break;
        case MTP_OPERATION_SEND_OBJECT:
            response = doSendObject();
            break;
        case MTP_OPERATION_DELETE_OBJECT:
            response = doDeleteObject();
            break;
        case MTP_OPERATION_GET_OBJECT_PROP_DESC:
            response = doGetObjectPropDesc();
            break;
        case MTP_OPERATION_GET_DEVICE_PROP_DESC:
            response = doGetDevicePropDesc();
            break;
        case MTP_OPERATION_SEND_PARTIAL_OBJECT:
            response = doSendPartialObject();
            break;
        case MTP_OPERATION_TRUNCATE_OBJECT:
            response = doTruncateObject();
            break;
        case MTP_OPERATION_BEGIN_EDIT_OBJECT:
            response = doBeginEditObject();
            break;
        case MTP_OPERATION_END_EDIT_OBJECT:
            response = doEndEditObject();
            break;
        default:
            ALOGE("got unsupported command %s (%x)",
                    MtpDebug::getOperationCodeName(operation), operation);
            response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
            break;
    }

    if (response == MTP_RESPONSE_TRANSACTION_CANCELLED)
        return false;
    // 设置响应码
    mResponse.setResponseCode(response);
    return true;
}

这里处理了各种操作码对应的请求,包括PC端执行的初始化动作,如果需要了解每一步做了什么,就需要了解MTP协议的内容。

响应请求

最后就是响应阶段。

			// 在handleRequest()中设置的
			mResponse.setResponseCode(response);
			
			// 设置事务ID,代表完成了一次事务
            mResponse.setTransactionID(transaction);
            // 发送
            ret = mResponse.write(sHandle);

相应数据和请求数据的格式是一样的,这里这是了响应码和事务ID,当然还可以附带参数,还有类型,对于响应应该就是MTP_CONTAINER_TYPE_RESPONSE,最后就是长度,这个可以根据是否有参数进行计算出来的。

总结

由于受专业知识限制,本文无法更深入的分析底层操作,在工作中也确实遇到了与底层实现有关的问题,最后还是请教了专业的人士给出了解决方案。

MTP协议内容很多,本文只是抛砖引玉,工作中你可能只会遇到协议中的某一部分内容,因此如果搞清楚了流程,我想那一部分的分析就比较简单了。

另外,跟着这本文进行更深入的分析,我们就会发现精彩的JNI层到上层的回调,这不失为一个好的学习机会。

感想

在写本文的时候,其进行了很多构思,总想这把所有事情解释明白(除了驱动和内核操作),但是这需要巨大的篇幅,我想读者很可能也没那么多耐心读完。那么我只能把源码的"架构图"尽量完整的描述,以求抛砖引玉。

另外我并没有用一个UML图来描绘整体的"架构图",因为我们实际中用到的只是某一部分,而且我也并没有打算分析所有的流程,因此就省略了。

参考

https://en.wikipedia.org/wiki/Media_Transfer_Protocol#Comparison_with_USB_Mass_Storage
https://www.cnblogs.com/skywang12345/p/3474206.html
MTP文档
https://www.usb.org/documents

发布了50 篇原创文章 · 获赞 30 · 访问量 400万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 像素格子 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览