有了上篇文章Andrid MTP之UsbService分析的基础,本片文章就来分析MTP
,那么我们首先要搞清楚,如何切换MTP
。
MTP切换
通过上篇文章分析可知,当手机通过USB
连接电脑的时候,会发送一个USB
和ADB
通知,而且会发送一个USB
状态切换广播。
通知界面如下
当点击最后一个通知,就会出现如下的USB
功能设置选项
当选择Transfer files
选项的时候,就是USB
开启了MTP
模式。开启MTP
的核心代码如下
mUsbManager = context.getSystemService(UsbManager.class);
mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP, true);
UsbManager
的服务端实现为UsbService
,而UsbService
的setCurrentFunction()
方法在
Andrid MTP之UsbService分析中已经分析过,这里就简单的总结下UsbService
切换MTP
模式所做的工作:
- 设置
sys.usb.config
属性的值为mtp,adb
,底层响应属性改变,切换到MTP
功能。 - 上层
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
连接时,状态是从DISCONNECTED
到CONNECTED
,再到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()
,后面不再说明类似的对应情况,读者可自行查看源码查找。
原来是调用MtpServer
的configure()
函数,而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
来说,就是处理USB
的CONFIGURED
状态,处理逻辑如下
// 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
。这个值非常重要,如果为false
,PC
端是不会映射手机存储的数据的。
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
对象,MtpDatabase
是Java
层数据库操作的代理类,看下构造函数
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.cpp
的register_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.java
在JNI
层的初始化到底做了啥,首先创建了JNI
层的MyMtpDatabase
对象,在这个对象中用mDatabase
保存了Java
层MtpDatabase
对象,而之后,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();
看下MtpServer
的start()
方法
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");
}
调用了底层的MtpServer
的run()
方法,注意,这个方法是在一个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
包括三个阶段
- 操作请求阶段: 也就是
PC
端向手机端发送请求 - 数据传输阶段: 这个阶段是可选的,也就是请求之后可能
PC
可能需要传输数据 - 相应阶段: 手机处理了
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