H5W3
当前位置:H5W3 > 其他技术问题 > 正文

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

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

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

MTP

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

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

创建上层操作MTP的接口

首先分析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.cppandroid_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如何建立映射关系。

创建MtpServer

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.cppandroid_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的创建过程

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

数据映射

现在万事俱备,只欠东风。我们来分析手机存储到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的接口。

现在来看下MtpDatabaseaddStorage()方法

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

处理mtp请求

存储映射其实留下了一个问题,当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.cppandroid_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 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框架分析。

本文地址:H5W3 » 深入理解Android MTP之存储映射分析

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址