深入理解Android MTP之UsbService启动分析 分析了MTP的服务端的启动,本文来分析切换MTP模式后,存储中的数据(文件、目录)是如何映射到PC端的。
首先你得知道如何切换MTP模式。当手机通过usb连接电脑时,会出现一个关于usb的通知,点击通知后,会出现一个类似如下的界面
这个File Transfer选项,就是MTP模式。
根据 深入理解Android MTP之UsbService启动分析 可知,当切换USB功能后,会发送一个usb状态改变的广播。那么是谁接收这个广播?接收这个广播又来做什么呢?
首先广播接收者是MediaProvider模块的MtpReceiver
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)) { // 因为usb状态改变的广播是sticky广播,所以可以这样获取 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); } } } 复制代码
首先我们可以看到,这里使用 handleUsbState()
来处理usb状态改变。
然后我们还应该注意到,这里接收开机广播也处理了一次usb状态改变。这里注册广播接收器的方式与我们平时使用的不一样,广播接收器参数为null,这是因为usb状态改变的广播是sticky广播。
现在来看下 handleUsbState()
如何处理usb状态改变广播。
private void handleUsbState(Context context, Intent intent) { 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 isCurrentUser = UserHandle.myUserId() == ActivityManager.getCurrentUser(); if (configured && (mtpEnabled || ptpEnabled)) { // 处理usb连接的情况并且是ptp或者mtp模式 if (!isCurrentUser) return; intent = new Intent(context, MtpService.class); // 注意这里传入了一个参数,代表数据是否是解锁状态 intent.putExtra(UsbManager.USB_DATA_UNLOCKED, unlocked); // 处理ptp模式的情况 if (ptpEnabled) { intent.putExtra(UsbManager.USB_FUNCTION_PTP, true); } context.startService(intent); } else if (!connected || !(mtpEnabled || ptpEnabled)) { // 处理usb断开或者不是mtp和ptp的情况 // 停止MtpService boolean status = context.stopService(new Intent(context, MtpService.class)); } } 复制代码
首先会获取Intent中各种参数,这些参数在上篇文章中全部讲过,这里再解释一番。
当手机通过USB线成功连接电脑时,configured和connected的值都是true,而断开时,这两个值都是false。
mtpEnabled表示是否开启了mtp功能。ptpEnabled表示是否开启了ptp功能。unlocked表示数据是否解锁,mtp和ptp功能的数据都是解锁状态。
解锁状态意味着可以在pc端看到手机存储的映射文件。
获取参数后,接下来的判断逻辑是,如果usb成功连接,并且是mtp或ptp模式,就启动MtpService,否则停止MtpService。这也说明了MtpService只处理mtp和ptp模式。
那么接下来就分析MtpService的创建与启动过程。首先分析创建过程,它会调用 onCreate()
public void onCreate() { // 获取所有的存储 mVolumes = StorageManager.getVolumeList(getUserId(), 0); // mVolumeMap保存已经挂载的存储 mVolumeMap = new HashMap<>(); mStorageManager = this.getSystemService(StorageManager.class); // 注册存储状态改变事件 mStorageManager.registerListener(mStorageEventListener); } 复制代码
在创建的时候主要做了二件事,一是获取手机中所有的存储,二是注册了一个监听器,监听存储状态改变,例如当有新的存储挂载上时,并且是mtp模式时,会通知pc端进行存储映射,这个过程会在后面分析到。
MtpService创建后,然后会调用 onStartCommand()
来执行任务
public synchronized int onStartCommand(Intent intent, int flags, int startId) { // 代表数据是否解锁 mUnlocked = intent.getBooleanExtra(UsbManager.USB_DATA_UNLOCKED, false); // mPtpMode为false就表示是mtp模式,因为这个Service只处理mtp和ptp模式 mPtpMode = intent.getBooleanExtra(UsbManager.USB_FUNCTION_PTP, false); // 获取已挂载的存储 for (StorageVolume v : mVolumes) { if (v.getState().equals(Environment.MEDIA_MOUNTED)) { mVolumeMap.put(v.getPath(), v); } } String[] subdirs = null; if (mPtpMode) { // ... } // 获取主存储,这个主存储是用于ptp模式 final StorageVolume primary = StorageManager.getPrimaryVolume(mVolumes); // 启动服务 // 注意,mtp模式,第二个参数为null,第一个参数没有作用 startServer(primary, subdirs); return START_REDELIVER_INTENT; } 复制代码
先解析了传入的两个参数,然后用mVolumeMap保存了已经挂载的存储,最后且启动了一个服务端。
本文只针对mtp模式进行分析,通过 startServer()
启动服务端时传入的两个参数,对于mtp模式来说,其实没啥用,只对ptp模式有用,这个在后面的分析中即将看到。
现在来看下这个服务端如何启动的
private synchronized void startServer(StorageVolume primary, String[] subdirs) { if (!(UserHandle.myUserId() == ActivityManager.getCurrentUser())) { return; } synchronized (MtpService.class) { // sServerHolder不为null,表示服务已经启动 if (sServerHolder != null) { return; } // 1. 创建MtpDatabase对象 // MtpDatabase提供了上层操作MTP的接口 final MtpDatabase database = new MtpDatabase(this, subdirs); // 我的项目不支持Hal层,因此这里获取到的controlFd为null IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService( Context.USB_SERVICE)); ParcelFileDescriptor controlFd = null; try { controlFd = usbMgr.getControlFd( mPtpMode ? UsbManager.FUNCTION_PTP : UsbManager.FUNCTION_MTP); } catch (RemoteException e) { Log.e(TAG, "Error communicating with UsbManager: " + e); } FileDescriptor fd = null; if (controlFd == null) { Log.i(TAG, "Couldn't get control FD!"); } else { fd = controlFd.getFileDescriptor(); } // 2. 创建MtpServer对象 // MtpServer是上层与JNI层交互的接口 final MtpServer server = new MtpServer(database, fd, mPtpMode, new OnServerTerminated(), Build.MANUFACTURER, Build.MODEL, "1.0"); database.setServer(server); sServerHolder = new ServerHolder(server, database); // 3. 通知pc端开始映射存储 // 注意,只有数据解锁状态时,才映射存储 if (mUnlocked) { if (mPtpMode) { // ptp模式下,只映射主存储 addStorage(primary); } else { // mtp模式下映射所有已经挂载的存储 for (StorageVolume v : mVolumeMap.values()) { addStorage(v); } } } // 4. 启动服务端 // 这个服务是做什么的呢? server.start(); } } 复制代码
这里的代码看似很简单,但是其实相当复杂,我先整体介绍下逻辑,然后再分模块细讲。
第一步,创建一个MtpDatabase对象,MtpDatabase简单来说就是framework操作mtp的接口。它通过MtpStorageManager管理存储,并监听了文件系统,例如当手机端添加了新文件,它会通过回调通知MtpDatabase,然后MtpDatabase会通知pc端进行映射这个新添加的文件。
第二步,创建一个MtpServer对象,MtpServer类是framework层操作JNI的接口。MtpDatabase内部就是通过MtpServer对象来向pc端发送信息。
第三步,通知pc端进行存储映射。当pc端收到这个通知后,会向手机端发送一个请求,这个请求用于获取存储的各种数据,用以建立映射。
第四步,启动一个服务端,用于处理pc端的请求。例如处理第三步中获取存储数据的请求。
下面,我将分为四部分来讲解这些过程。
首先分析MtpDatabase对象的创建过程,MtpDatabase是framework层操作MTP的接口,也就是说,如果你想从上层做一些mtp操作,必须通过这个类。
现在看下MtpDatabase的构造函数做了什么
public MtpDatabase(Context context, String[] subDirectories) { // 1. JNI层初始化 // 用mNativeContext保存JNI层的MtpDatabase对象 native_setup(); // 2. 获取media provide的客户端接口,用于获取媒体文件的各种信息 mContext = Objects.requireNonNull(context); mMediaProvider = context.getContentResolver() .acquireContentProviderClient(MediaStore.AUTHORITY); // 3. 创建MtpStorageMananger对象 // MtpStorageManager会监听文件系统的变化,然后回调通知MtpDatabase,MtpDatabse内部通过MtpServer通知pc端 mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() { @Override public void sendObjectAdded(int id) { if (MtpDatabase.this.mServer != null) MtpDatabase.this.mServer.sendObjectAdded(id); } @Override public void sendObjectRemoved(int id) { if (MtpDatabase.this.mServer != null) MtpDatabase.this.mServer.sendObjectRemoved(id); } @Override public void sendObjectInfoChanged(int id) { if (MtpDatabase.this.mServer != null) MtpDatabase.this.mServer.sendObjectInfoChanged(id); } }, subDirectories == null ? null : Sets.newHashSet(subDirectories)); // ... 省略一些无关紧要的代码 } 复制代码
刚才已经介绍了MtpDatabase的作用,这里的变量创建与初始化与体现这一点。让我们把焦点集中到第一步,它在JNI层完成初始化。
media操作的JNI库路径为frameworks/base/media/jni。
native_setup()
是由 frameworks/base/media/jni/android_mtp_MtpDatabase.cpp
的 android_mtp_MtpDatabase_setup()
实现的
static void android_mtp_MtpDatabase_setup(JNIEnv *env, jobject thiz) { // 创建JNI层的MtpDatabase对象 MtpDatabase* database = new MtpDatabase(env, thiz); // 用Java层的MtpDatabase对象的mNativeContext引用JNI层创建的MtpDtaabase对象 env->SetLongField(thiz, field_context, (jlong)database); checkAndClearExceptionFromCallback(env, __FUNCTION__); } 复制代码
本文并不会讲解JNI的基础知识,本篇文章需要你有JNI基础。
JNI层的初始化,原来只是用Java层的MtpDatabase.mNativeContext保存了native创建的MtpDatabase对象。
这个MtpDatabase类是 android_mtp_MtpDatabase.cpp
中的一个嵌套类,看下它的构造函数
MtpDatabase::MtpDatabase(JNIEnv *env, jobject client) : mDatabase(env->NewGlobalRef(client)), // mDatabase指向Java层的MtpDatabase对象 mIntBuffer(NULL), mLongBuffer(NULL), mStringBuffer(NULL) { jintArray intArray = env->NewIntArray(3); if (!intArray) { return; } mIntBuffer = (jintArray)env->NewGlobalRef(intArray); jlongArray longArray = env->NewLongArray(2); if (!longArray) { return; } mLongBuffer = (jlongArray)env->NewGlobalRef(longArray); jcharArray charArray = env->NewCharArray(PATH_MAX + 1); if (!charArray) { return; } mStringBuffer = (jcharArray)env->NewGlobalRef(charArray); } 复制代码
这里最重要的一段代码就是mDatabase变量的初始化,代码为 mDatabase(env->NewGlobalRef(client))
。mDatabase是一个分局引用,它指向Java层的MtpDatabase对象。如此一来,JNI层的MtpDatabase对象也保存了Java层的MtpDatabase引用。
现在我们可以发现,经过JNI层的初始化,Java层的MtpDatabase对象和JNI层的MtpDatabase对象相互引用。这样就可以实现JNI层和Java层的互相操作。
读源码,我们学以致用,例如这里的上层和native如何建立映射关系。
MtpDatabase的创建过程分析完了,现在看下MtpServer的创建过程。在分析前,我还是提醒一下MtpServer的作用,它是framework层与JNI层通信的接口。
public MtpServer( MtpDatabase database, FileDescriptor controlFd, boolean usePtp, Runnable onTerminate, String deviceInfoManufacturer, String deviceInfoModel, String deviceInfoDeviceVersion) { mDatabase = Preconditions.checkNotNull(database); mOnTerminate = Preconditions.checkNotNull(onTerminate); mContext = mDatabase.getContext(); final String strID_PREFS_NAME = "mtp-cfg"; final String strID_PREFS_KEY = "mtp-id"; String strRandomId = null; String deviceInfoSerialNumber; SharedPreferences sharedPref = mContext.getSharedPreferences(strID_PREFS_NAME, Context.MODE_PRIVATE); if (sharedPref.contains(strID_PREFS_KEY)) { strRandomId = sharedPref.getString(strID_PREFS_KEY, null); // Check for format consistence (regenerate upon corruption) if (strRandomId.length() != sID_LEN_STR) { strRandomId = null; } else { // Only accept hex digit for (int ii = 0; ii < strRandomId.length(); ii++) if (Character.digit(strRandomId.charAt(ii), 16) == -1) { strRandomId = null; break; } } } if (strRandomId == null) { strRandomId = getRandId(); sharedPref.edit().putString(strID_PREFS_KEY, strRandomId).apply(); } // 1. 获取一个设备序列号 deviceInfoSerialNumber = strRandomId; // 2. 执行JNI层的初始化 native_setup( database, controlFd, usePtp, deviceInfoManufacturer, deviceInfoModel, deviceInfoDeviceVersion, deviceInfoSerialNumber); // 这一步应该是个多余的操作,因此MtpService马上执行这个操作 database.setServer(this); } 复制代码
第一步,获取一个随机的设备序列号。可以看到它是通过SharedPreferences进行保存/获取的。
第二步,执行JNI层的初始化。它由 frameworks/base/media/jni/android_mtp_MtpServer.cpp
的 android_mtp_MtpServer_setup()
实现
static void android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, jobject jControlFd, jboolean usePtp, jstring deviceInfoManufacturer, jstring deviceInfoModel, jstring deviceInfoDeviceVersion, jstring deviceInfoSerialNumber) { // 把Java层传入的字符串参数转化为的native指针 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); int controlFd = dup(jniGetFDFromFileDescriptor(env, jControlFd)); // 1. 创建JNI层的MtpServer MtpServer* server = new MtpServer(getMtpDatabase(env, javaDatabase), controlFd, usePtp, (deviceInfoManufacturerStr != NULL) ? deviceInfoManufacturerStr : "", (deviceInfoModelStr != NULL) ? deviceInfoModelStr : "", (deviceInfoDeviceVersionStr != NULL) ? deviceInfoDeviceVersionStr : "", (deviceInfoSerialNumberStr != NULL) ? deviceInfoSerialNumberStr : ""); // 释放指针指向的字符串内存 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); } // 2. 用Java层的MtpServer.mNativeContext引用JNI层的MtpServer对象 env->SetLongField(thiz, field_MtpServer_nativeContext, (jlong)server); } 复制代码
这里其实就两步,第一步创建native层的MtpServer对象,第二步用Java层的MtpServer.mNativeContext变量引用这个native层的MtpServer对象。
现在来看下native层的MtpServer的创建过程,它的路径为 frameworks/av/media/mtp/MtpServer.cpp
libmtp.so
库的路径为 frameworks/av/media/mtp
MtpServer::MtpServer(IMtpDatabase* database, int controlFd, bool ptp, const char *deviceInfoManufacturer, const char *deviceInfoModel, const char *deviceInfoDeviceVersion, const char *deviceInfoSerialNumber) : mDatabase(database), // mDatabase指向native层的MtpDatabase mPtp(ptp), mDeviceInfoManufacturer(deviceInfoManufacturer), mDeviceInfoModel(deviceInfoModel), mDeviceInfoDeviceVersion(deviceInfoDeviceVersion), mDeviceInfoSerialNumber(deviceInfoSerialNumber), mSessionID(0), mSessionOpen(false), mSendObjectHandle(kInvalidObjectHandle), mSendObjectFormat(0), mSendObjectFileSize(0), mSendObjectModifiedTime(0) { bool ffs_ok = access(FFS_MTP_EP0, W_OK) == 0; if (ffs_ok) { } else { // mHandle指向IMtpHandle类型对象 mHandle = new MtpDevHandle(); } } 复制代码
创建MtpServer对象的过程中,大部分都是变量的初始化或赋值,但是有两点需要注意下。首先mDatabase变量指向的是native层的MtpDatabase,就是前面刚创建的MtpDatabase对象。然后mHandle指针指向是是IMtpHandle的实现类MtpDevHandle对象,IMtpHandle类定义了native层操作mtp的接口。
现在总结下Java层MtpServer的创建过程
现在万事俱备,只欠东风。我们来分析手机存储到pc端映射这一过程。代码片段如下
private synchronized void startServer(StorageVolume primary, String[] subdirs) { // ... synchronized (MtpService.class) { // 1. 创建MtpDatabase对象 // 2. 创建MtpServer对象 // 3. 通知pc端开始映射存储 // 注意,只有数据解锁状态时,才映射存储 if (mUnlocked) { if (mPtpMode) { // ptp模式下,只映射主存储 addStorage(primary); } else { // mtp模式下映射所有已经挂载的存储 for (StorageVolume v : mVolumeMap.values()) { addStorage(v); } } } // 4. 启动服务端... } } 复制代码
从代码中可以发现,只有在数据解锁状态下,才会进行存储映射。
当usb模式为mtp时,会映射所有已经挂载的存储。而当usb模式为ptp时,只会映射主存储。
现在来看下 addStorage()
是如何执行存储映射的
private void addStorage(StorageVolume volume) { synchronized (MtpService.class) { if (sServerHolder != null) { sServerHolder.database.addStorage(volume); } } } 复制代码
非常简单,就是用MtpDatabase进行MTP操作,这也验证了前面所说,MtpDatabase是上层操作MTP的接口。
现在来看下 MtpDatabase
的 addStorage()
方法
public void addStorage(StorageVolume storage) { // 为存储分配一个id,并创建一个代表存储的MtpStorage对象 MtpStorage mtpStorage = mManager.addMtpStorage(storage); // 保存存储 mStorageMap.put(storage.getPath(), mtpStorage); // 通过MtpServer执行添加存储的操作 if (mServer != null) { mServer.addStorage(mtpStorage); } } 复制代码
首先通过MtpStroageManager为即将添加的存储分配了一个id,并创建了一个代表存储的MtpStorage对象。然后MtpDatabase保存了这个MtpStorage对象。最后又把添加存储的操作交给了MtpServer。
前面说过,MtpServer是上层与JNI层交互的接口,这里把添加存储的操作交给了MtpServer,实际上就是要通过JNI层,通知pc端来映射存储。到底是不是这样呢,我们来看下MtpServer的 addStorage()
实现
public void addStorage(MtpStorage storage) { native_add_storage(storage); } 复制代码
和我们刚才所说的一样,这里调用了JNI层的方法。这个方法是由 frameworks/base/media/jni/android_mtp_MtpServer.cpp
的 android_mtp_MtpServer_add_storage()
实现的
static void android_mtp_MtpServer_add_storage(JNIEnv *env, jobject thiz, jobject jstorage) { Mutex::Autolock autoLock(sMutex); // 1. 获取native层的MtpServer MtpServer* server = getMtpServer(env, thiz); if (server) { // 从Java层的MtpStorage对象中获取各种变量的值 jint storageID = env->GetIntField(jstorage, field_MtpStorage_storageId); jstring path = (jstring)env->GetObjectField(jstorage, field_MtpStorage_path); jstring description = (jstring)env->GetObjectField(jstorage, field_MtpStorage_description); jboolean removable = env->GetBooleanField(jstorage, field_MtpStorage_removable); jlong maxFileSize = env->GetLongField(jstorage, field_MtpStorage_maxFileSize); const char *pathStr = env->GetStringUTFChars(path, NULL); if (pathStr != NULL) { const char *descriptionStr = env->GetStringUTFChars(description, NULL); if (descriptionStr != NULL) { // 2. 创建native层的MtpStorage对象 MtpStorage* storage = new MtpStorage(storageID, pathStr, descriptionStr, removable, maxFileSize); // 3. 通过native层的MtpServer执行添加存储的操作 server->addStorage(storage); env->ReleaseStringUTFChars(path, pathStr); env->ReleaseStringUTFChars(description, descriptionStr); } else { env->ReleaseStringUTFChars(path, pathStr); } } } else { ALOGE("server is null in add_storage"); } } 复制代码
这里操作也很简单,首先获取Java层的MtpStorage对象的各种属性,然后利用这些属性创建native层的MtpStorage对象,最后把添加存储的操作交给了native层的MtpServer来执行。
现在来看下native层的MtpServer的 addStorage()
void MtpServer::addStorage(MtpStorage* storage) { std::lock_guard<std::mutex> lg(mMutex); // 保存到集合中 mStorages.push_back(storage); sendStoreAdded(storage->getStorageID()); } void MtpServer::sendStoreAdded(MtpStorageID id) { sendEvent(MTP_EVENT_STORE_ADDED, id); } void MtpServer::sendEvent(MtpEventCode code, uint32_t param1) { if (mSessionOpen) { // 进行数据填充 mEvent.setEventCode(code); mEvent.setTransactionID(mRequest.getTransactionID()); mEvent.setParameter(1, param1); // 通过IMtpHandle的sendEvent()接口向驱动发送消息 if (mEvent.write(mHandle)) ALOGE("Mtp send event failed: %s", strerror(errno)); } } 复制代码
首先这里会根据mtp协议对数据进行封装,然后通过 mHandle
(native操作mtp接口)向驱动发送消息,驱动会完成通知pc端的功能。
关于mtp协议的内容的代码,我不打算具体分析,usb驱动的内容也同样如此。但是如果你需要扩展mtp的一些操作,你就必须先详细阅读mtp协议内容,并且按照代码一步一步分析。
我在项目中花了一两天时间阅读mtp协议,并且分析了源码的实现。没有这个基础,就不用谈实现自己mtp的功能,这就叫磨刀不误砍柴功。
假设现在已经通过usb驱动成功向pc端发送了信息,那么pc端会向手机端发送一个请求,用于获取存储的信息,例如存储的大小,存储中有哪些文件,等等。然后pc端利用这些信息,建立对应的存储映射,这个映射就是我们在pc端看到的存储的文件。
以前的Android版本中,使用的是mass storage,而不是mtp。mass storage把手机存储直接挂载到了pc端,这样一来,在pc端操作的就是实际的手机存储,然而这会导致一个很严重的问题,那就是手机端此时无法使用这个存储,典型的例子就是切换mass storage后无法使用相机拍照。而mtp只是建立了存储映射,因此就算切换到mtp模式,手机还是可以照样使用这个存储。但是mtp相比较于mass storage也有短板,那就是文件操作没有mass storage快,尤其在大量处理文件时,例如文件复制,速度比较慢,因为这需要一个数据同步的过程。
存储映射其实留下了一个问题,当pc端收到映射手机存储的事件时,pc端会向手机端发送一个请求,这个请求用于获取存储信息,那么手机端是如何处理这个pc的请求的呢?我们接着往下分析。
private synchronized void startServer(StorageVolume primary, String[] subdirs) { if (!(UserHandle.myUserId() == ActivityManager.getCurrentUser())) { return; } synchronized (MtpService.class) { // sServerHolder不为null,表示服务已经启动 if (sServerHolder != null) { return; } // 1. 创建MtpDatabase对象 // 2. 创建MtpServer对象 // 3. 通知pc端开始映射存储 // 4. 启动服务端,处理mtp请求 server.start(); } } 复制代码
这里调用了MtpServe的start()方法
public void start() { Thread thread = new Thread(this, "MtpServer"); thread.start(); } 复制代码
很简单,启动了一个单独的线程,这个线程在做什么呢
public void run() { // 底层通过一个无限循环处理pc的请求 native_run(); // 下面的这些操作一般发生在断开mtp连接时,这些都是一些清理操作 native_cleanup(); mDatabase.close(); mOnTerminate.run(); } 复制代码
native_run()
会在底层开启一个无限循环,用于处理pc请求。如果一旦mtp断开或者处理请求发生异常,那么就会执行后面的清理操作。
接下来着重分析 native_run()
是如何处理请求的,它由 android_mtp_MtpServer.cpp
的 android_mtp_MtpServer_run()
实现
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"); } 复制代码
处理请求的任务交给了native层的MtpServer的run()方法
void MtpServer::run() { // 打开mtp节点 if (mHandle->start(mPtp)) { ALOGE("Failed to start usb driver!"); mHandle->close(); return; } // 通过无限循环处理pc端请求 while (1) { int ret = mRequest.read(mHandle); 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(); MtpTransactionID transaction = mRequest.getTransactionID(); // 如果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) { int ret = mData.read(mHandle); 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(); } // 处理mtp请求 if (handleRequest()) { // 处理请求后,把数据发送给pc端 if (!dataIn && mData.hasData()) { mData.setOperationCode(operation); mData.setTransactionID(transaction); ALOGV("sending data:"); ret = mData.write(mHandle); 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; } } // 把处理数据的结果发送给pc端 mResponse.setTransactionID(transaction); ret = mResponse.write(mHandle); 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"); } } // 走到这里就代表处理请求发生了异常,因此处理善后操作 // 提交一些已经打开的编辑操作 int count = mObjectEditList.size(); for (int i = 0; i < count; i++) { ObjectEdit* edit = mObjectEditList[i]; commitEdit(edit); delete edit; } mObjectEditList.clear(); // 关闭mtp节点 mHandle->close(); } 复制代码
这里的代码很长,包括了数据读取,请求处理,数据发送,等等。这些都是基于mtp协议实现的,如果你想在这里扩展一些自己的操作,那一定要熟悉mtp协议的内容。
但是我们分析代码,要从大局观角度出发。可以看到这里通过一个while的无限循环,然后通过 handleRequest()
来处理请求。例如处理pc端获取存储信息的请求就是这里处理的。
本篇文章从整体的角度分析了手机存储如何在pc端建立映射,但是并不涉及具体的mtp协议的内部,更不涉及驱动内容。如果你想利用mtp做点事,首先需要阅读mtp协议,然后再看源码实现,再才能做自己想做的事,而本文就是一个完整版的mtp框架分析。