深入理解Android MTP之存储映射分析

深入理解Android MTP之UsbService启动分析 分析了MTP的服务端的启动,本文来分析切换MTP模式后,存储中的数据(文件、目录)是如何映射到PC端的。

首先你得知道如何切换MTP模式。当手机通过usb连接电脑时,会出现一个关于usb的通知,点击通知后,会出现一个类似如下的界面

MTP

这个File Transfer选项,就是MTP模式。

根据 深入理解Android MTP之UsbService启动分析 可知,当切换USB功能后,会发送一个usb状态改变的广播。那么是谁接收这个广播?接收这个广播又来做什么呢?

首先广播接收者是MediaProvider模块的MtpReceiver

publicclassMtpReceiverextendsBroadcastReceiver{

@Override

publicvoidonReceive(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);

}

} elseif (UsbManager.ACTION_USB_STATE.equals(action)) {

handleUsbState(context, intent);

}

}

}

首先我们可以看到,这里使用handleUsbState()来处理usb状态改变。

然后我们还应该注意到,这里接收开机广播也处理了一次usb状态改变。这里注册广播接收器的方式与我们平时使用的不一样,广播接收器参数为null,这是因为usb状态改变的广播是sticky广播。

现在来看下handleUsbState()如何处理usb状态改变广播。

privatevoidhandleUsbState(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);

} elseif (!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()

publicvoidonCreate(){

// 获取所有的存储

mVolumes = StorageManager.getVolumeList(getUserId(), 0);

// mVolumeMap保存已经挂载的存储

mVolumeMap = new HashMap<>();

mStorageManager = this.getSystemService(StorageManager.class);

// 注册存储状态改变事件

mStorageManager.registerListener(mStorageEventListener);

}

在创建的时候主要做了二件事,一是获取手机中所有的存储,二是注册了一个监听器,监听存储状态改变,例如当有新的存储挂载上时,并且是mtp模式时,会通知pc端进行存储映射,这个过程会在后面分析到。

MtpService创建后,然后会调用onStartCommand()来执行任务

publicsynchronizedintonStartCommand(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模式有用,这个在后面的分析中即将看到。

现在来看下这个服务端如何启动的

privatesynchronizedvoidstartServer(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端的请求。例如处理第三步中获取存储数据的请求。

下面,我将分为四部分来讲解这些过程。

创建上层操作MTP的接口

首先分析MtpDatabase对象的创建过程,MtpDatabase是framework层操作MTP的接口,也就是说,如果你想从上层做一些mtp操作,必须通过这个类。

现在看下MtpDatabase的构造函数做了什么

publicMtpDatabase(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

publicvoidsendObjectAdded(int id){

if (MtpDatabase.this.mServer != null)

MtpDatabase.this.mServer.sendObjectAdded(id);

}

@Override

publicvoidsendObjectRemoved(int id){

if (MtpDatabase.this.mServer != null)

MtpDatabase.this.mServer.sendObjectRemoved(id);

}

@Override

publicvoidsendObjectInfoChanged(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.cppandroid_mtp_MtpDatabase_setup()实现的

staticvoid

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如何建立映射关系。

创建MtpServer

MtpDatabase的创建过程分析完了,现在看下MtpServer的创建过程。在分析前,我还是提醒一下MtpServer的作用,它是framework层与JNI层通信的接口。

publicMtpServer(

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.cppandroid_mtp_MtpServer_setup()实现

staticvoid

android_mtp_MtpServer_setup(JNIEnv *env, jobject thiz, jobject javaDatabase, jobject jControlFd,

jboolean usePtp, jstring deviceInfoManufacturer, jstring deviceInfoModel,

jstring deviceInfoDeviceVersion, jstring deviceInfoSerialNumber)

{

// 把Java层传入的字符串参数转化为的native指针

constchar *deviceInfoManufacturerStr = env->GetStringUTFChars(deviceInfoManufacturer, NULL);

constchar *deviceInfoModelStr = env->GetStringUTFChars(deviceInfoModel, NULL);

constchar *deviceInfoDeviceVersionStr = env->GetStringUTFChars(deviceInfoDeviceVersion, NULL);

constchar *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,

constchar *deviceInfoManufacturer,

constchar *deviceInfoModel,

constchar *deviceInfoDeviceVersion,

constchar *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的创建过程

  1. 使用MtpServer的mNativeContext绑定native层的MtpServer对象。
  2. native层的MtpServer对象用mDatabase指向native层的MtpDatabase对象。

数据映射

现在万事俱备,只欠东风。我们来分析手机存储到pc端映射这一过程。代码片段如下

privatesynchronizedvoidstartServer(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()是如何执行存储映射的

privatevoidaddStorage(StorageVolume volume){

synchronized (MtpService.class) {

if (sServerHolder != null) {

sServerHolder.database.addStorage(volume);

}

}

}

非常简单,就是用MtpDatabase进行MTP操作,这也验证了前面所说,MtpDatabase是上层操作MTP的接口。

现在来看下MtpDatabaseaddStorage()方法

publicvoidaddStorage(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()实现

publicvoidaddStorage(MtpStorage storage){

native_add_storage(storage);

}

和我们刚才所说的一样,这里调用了JNI层的方法。这个方法是由frameworks/base/media/jni/android_mtp_MtpServer.cppandroid_mtp_MtpServer_add_storage()实现的

staticvoid

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);

constchar *pathStr = env->GetStringUTFChars(path, NULL);

if (pathStr != NULL) {

constchar *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快,尤其在大量处理文件时,例如文件复制,速度比较慢,因为这需要一个数据同步的过程。

处理mtp请求

存储映射其实留下了一个问题,当pc端收到映射手机存储的事件时,pc端会向手机端发送一个请求,这个请求用于获取存储信息,那么手机端是如何处理这个pc的请求的呢?我们接着往下分析。

privatesynchronizedvoidstartServer(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()方法

publicvoidstart(){

Thread thread = new Thread(this, "MtpServer");

thread.start();

}

很简单,启动了一个单独的线程,这个线程在做什么呢

publicvoidrun(){

// 底层通过一个无限循环处理pc的请求

native_run();

// 下面的这些操作一般发生在断开mtp连接时,这些都是一些清理操作

native_cleanup();

mDatabase.close();

mOnTerminate.run();

}

native_run()会在底层开启一个无限循环,用于处理pc请求。如果一旦mtp断开或者处理请求发生异常,那么就会执行后面的清理操作。

接下来着重分析native_run()是如何处理请求的,它由android_mtp_MtpServer.cppandroid_mtp_MtpServer_run()实现

staticvoid

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);

constint 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 responsen");

}

}

// 走到这里就代表处理请求发生了异常,因此处理善后操作

// 提交一些已经打开的编辑操作

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框架分析。

以上是 深入理解Android MTP之存储映射分析 的全部内容, 来源链接: utcz.com/a/27280.html

回到顶部