首页 > 其他分享 >深入理解Android MTP之存储映射分析

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

时间:2024-08-22 18:05:26浏览次数:8  
标签:存储 映射 MtpDatabase MTP pc MtpServer env 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.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. 启动服务端...
        }
    }

非常简单,就是用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 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框架分析。

标签:存储,映射,MtpDatabase,MTP,pc,MtpServer,env,Android,mtp
From: https://www.cnblogs.com/linhaostudy/p/18374461

相关文章

  • android调用h5代码步骤
    要在Android应用中调用H5代码,可以使用WebView来加载并执行H5代码。以下是一个简单的示例:首先,在你的Android项目中的布局文件中添加一个WebView组件:```xml<WebView  android:id="@+id/webview"  android:layout_width="match_parent"  android:layout_height="......
  • EF Core使用SharedTypeEntity,映射实体类到不同的数据库表(转载)
    我们可以借助EFCore的SharedTypeEntity,映射一个实体类到多个结构相同的数据库表:publicclassUser{publicintId{get;set;}publicstringName{get;set;}}publicclassSomeDbContext:DbContext{protectedoverridevoidOnConfiguring(DbConte......
  • 免费【2024】springboot 基于Android的个人财务系统的设计与实现
    博主介绍:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌技术范围:SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数......
  • 免费【2024】基于Android的房屋租赁App的设计与实现
    博主介绍:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌技术范围:SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数......
  • 免费【2024】基于Android的房屋租赁App的设计与实现
    博主介绍:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌技术范围:SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数......
  • 免费【2024】springboot 基于Android的个人财务系统的设计与实现
    博主介绍:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌技术范围:SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数......
  • 免费【2024】基于Android的房屋租赁App的设计与实现
    博主介绍:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌技术范围:SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数......
  • 免费【2024】springboot 基于Android的个人财务系统的设计与实现
    博主介绍:✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌技术范围:SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数......
  • Android开发语言Kotlin简介
    官方认可:自2017年Google正式宣布Kotlin成为Android开发的官方语言后,它在Android开发中的流行度就有了显著提升。与Java的兼容性:Kotlin在设计时就考虑到了与Java的互操作性,这让开发者能够在Android项目中轻松使用Kotlin,同时继续利用现有的Java代码和库。......
  • 【Android】Android AOP 编程框架
    什么是AOP编程AOP编程全称AspectOrientedProgramming,面向切面编程主要功能是在不改变原代码的前提下,对特点代码节点进行修改,预处理,后期处理AOP的历史Android的AOP编程框架比较多,它们大多具备以下特点以AspectJ为基础,提供AOP编程能力AspectJ最早为Java项目中的编程框......