深入理解Android MTP之存储映射分析
深入理解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端的请求。例如处理第三步中获取存储数据的请求。下面,我将分为四部分来讲解这些过程。创建上层操作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.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如何建立映射关系。
创建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.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的创建过程
- 使用MtpServer的mNativeContext绑定native层的MtpServer对象。
- 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. 启动服务端...
}
}
非常简单,就是用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快,尤其在大量处理文件时,例如文件复制,速度比较慢,因为这需要一个数据同步的过程。
处理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.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框架分析。
深入理解Android MTP之存储映射分析的更多相关文章
- 理解 Android 本地数据存储 API
利用首选项.SQLite 和内部及外部内存 API 对于需要跨应用程序执行期间或生命期而维护重要信息的应用程序来说,能够在移动设备上本地存储数据是一种非常关键的功能.作为一名开发人员,您经常需要存储诸 ...
- 关于Android开发数据存储的方式(一)
关于Android开发数据存储方式(一) 在厦门做Android开发也有两个月了,快情人节了.我还在弄代码. 在微信平台上开发自己的APP,用到了数据存储的知识,如今总结一下: 整体的来讲.数据存储方 ...
- Android之 MTP框架和流程分析
概要 本文的目的是介绍Android系统中MTP的一些相关知识.主要的内容包括:第1部分 MTP简介 对Mtp协议进行简单的介绍.第2部分 MTP框架 介绍 ...
- [转载] 深入理解Android之Java虚拟机Dalvik
本文转载自: http://blog.csdn.net/innost/article/details/50377905 一.背景 这个选题很大,但并不是一开始就有这么高大上的追求.最初之时,只是源于对 ...
- 理解Android虚拟机体系结构
1 什么是Dalvik虚拟机 Dalvik是Google公司自己设计用于Android平台的Java虚拟机,它是Android平台的重要组成部分,支持dex格式(Dalvik Executable)的 ...
- 理解Android安全机制
本文从Android系统架构着手,分析Android的安全机制以SE Android,最后给出一些Android安全现状和常见的安全解决方案. 1.Android系统架构 Android采用分层的系统 ...
- 理解Android虚拟机体系结构(转)
1 什么是Dalvik虚拟机 Dalvik是Google公司自己设计用于Android平台的Java虚拟机,它是Android平台的重要组成部分,支持dex格式(Dalvik Executable)的 ...
- [深入理解Android卷一全文-第三章]深入理解init
因为<深入理解Android 卷一>和<深入理解Android卷二>不再出版,而知识的传播不应该因为纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的全部内容. ...
- [深入理解Android卷一全文-第八章]深入理解Surface系统
由于<深入理解Android 卷一>和<深入理解Android卷二>不再出版.而知识的传播不应该由于纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的全部内容. ...
- [深入理解Android卷一全文-第七章]深入理解Audio系统
由于<深入理解Android 卷一>和<深入理解Android卷二>不再出版,而知识的传播不应该由于纸质媒介的问题而中断,所以我将在CSDN博客中全文转发这两本书的全部内容. ...
随机推荐
- 使用nvm管理node
安装包在阿里云盘 安装时,记录安装位置 安装后,在安装目录的setting添加镜像地址 node_mirror:npm.taobao.org/mirrors/node/ npm_mirror:npm. ...
- 2023/4/22 SCRUM个人博客
1.我昨天的任务 学习如何使用QTdesign,并完善UI 2.遇到了什么困难 在QTable上无法理解前后端互通·的问题 3.我今天的任务 学习Qt知识QTableWidgetItem完善Pyqt5 ...
- python面向对象游戏练习:好人坏人手枪手榴弹
python面向对象游戏练习:好人坏人手枪手榴弹 主要是多态的练习,对象作为参数传给方法使用 1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 ...
- pyqt报错、python报错:src/pyaudio/device_api.c:9:10: fatal error: portaudio.h: 没有那个文件或目录
报错信息: -DNDEBUG -fwrapv -O2 -Wall -fPIC -O2 -isystem /home/devil/anaconda3/envs/91/include -fPIC -O2 ...
- 一个简单的例子测试numpy和Jax的性能对比
参考: https://baijiahao.baidu.com/s?id=1725356123619612187&wfr=spider&for=pc 个人认为如果把Jax作为一款深度学 ...
- 使用MindSpore_hub 进行 加载模型用于推理或迁移学习
从官方资料: https://www.mindspore.cn/tutorial/training/zh-CN/r1.2/use/save_model.html?highlight=save_chec ...
- 【共建开源】手把手教你贡献一个 SeaTunnel PR,超级详细教程!
Apache SeaTunnel是一个非常易于使用的.超高性能的分布式数据集成平台,支持海量数据的实时同步.每天可稳定高效同步数百亿数据,已被近百家企业投入生产使用. 现在的版本不支持通过jtds的方 ...
- Grafana的仪表盘URL参数设置
转载请注明出处: 在调试grafana时,需要对grafana进行share的url图表进行一些自定义设置,总结下常用的参数 参考网站:https://grafana.com/docs/grafana ...
- YOLOv10添加输出各类别训练过程指标
昨天有群友,在交流群[群号:392784757]里提到了这个需求,进行实现一下 V10 官方代码结构相较于 V8 稍微复杂一些 yolov10 是基于 v8 的代码完成开发,yolov10 进行了继承 ...
- OpenPCDet复现过程记录
0.前言 OpenPCDet项目之前我就复现过,一个很优秀的项目,这几天又需要用到这个项目,再次复现遇到了不少问题,特此记录复现的流程 1.环境准备 1.1.前置条件 以下是我安装的版本 CUDA 1 ...