[021]MTP架构解析

前言

这两天正好在研究一个通过MTP模式拷贝文件到手机速度慢的问题,顺便把整个MTP架构学了一遍,所以写一篇文章记录并分享一下。

1.MTP传输原理

主要分为三部分
1.手机端的MediaProvider进程
2.USB线
3.PC端的MTP客户端
简单的描述就是:

手机端的MediaProvider进程不断的监听USB端口根据MTP协议读写数据
PC端的MTP客户端也是不断的监听USB端口根据MTP协议读写数据

PC端的MTP客户端代码是微软写的,我们只需要研究手机端MediaProvider进程中MTP相关的代码即可。

2.MediaProvider中MTP功能整体架构图

整体架构比较清晰,如果你看过Binder驱动,你会发现结构非常类似,Java->JNI->Native->设备节点->驱动程序。

2.1 Java层

/frameworks/base/media/java/android/mtp/MtpServer.java

public class MtpServer implements Runnable {

      public void start() {
          Thread thread = new Thread(this, "MtpServer");
          thread.start();
      }
  
      @Override
      public void run() {
          native_run();
          native_cleanup();
          mDatabase.close();
          mOnTerminate.run();
      }
}

MtpServer的代码很简单,本身就是实现了Runnable接口,然后新启一个“MtpServer”的线程运行MtpServer,调用native_run。

2.2 JNI层

/frameworks/base/media/jni/android_mtp_MtpServer.cpp

android_mtp_MtpServer_run(JNIEnv *env, jobject thiz)
{
    MtpServer* server = getMtpServer(env, thiz);
    if (server)
        server->run();
    else
        ALOGE("server is null in run");
}

JNI没有太多的逻辑,调用MtpServer->run(),更多是实现在Native层的MtpServer.cpp

2.3 Native层

/frameworks/av/media/mtp/MtpServer.cpp

MtpServer::MtpServer(IMtpDatabase* database, int controlFd, bool ptp,
                    const char *deviceInfoManufacturer,
                    const char *deviceInfoModel,
                    const char *deviceInfoDeviceVersion,
                    const char *deviceInfoSerialNumber)
    {
    //我们的设备不支持FFS_MTP_EP0
    bool ffs_ok = access(FFS_MTP_EP0, W_OK) == 0;
    if (ffs_ok) {
        bool aio_compat = android::base::GetBoolProperty("sys.usb.ffs.aio_compat", false);
        mHandle = aio_compat ? new MtpFfsCompatHandle(controlFd) : new MtpFfsHandle(controlFd);
    } else {
        mHandle = new MtpDevHandle();//默认都是走这里
    }
}

void MtpServer::run() {
    //启动MtpDevHandle
    if (mHandle->start(mPtp)) {
        ALOGE("Failed to start usb driver!");
        mHandle->close();
        return;
    }
    ...
    //请注意这里是死循环
    while (1) {
        //读取收到的MTP协议
        int ret = mRequest.read(mHandle);
        MtpOperationCode operation = mRequest.getOperationCode();
        MtpTransactionID transaction = mRequest.getTransactionID();
        ...
        //根据上面获得的MTP协议进行处理
        if (handleRequest()) {
            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;
                }
            }

            mResponse.setTransactionID(transaction);
            ALOGV("sending response %04X", mResponse.getResponseCode());
            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");
        }
    }

   ...
}

MtpServer.cpp的逻辑就相对复杂,主要是通过MtpDevHandle去和MTP驱动沟通,下面就是MtpDevHandle.cpp

/frameworks/av/media/mtp/MtpDevHandle.cpp

#include "MtpDevHandle.h"

namespace android {

constexpr char mtp_dev_path[] = "/dev/mtp_usb";

MtpDevHandle::MtpDevHandle()
    : mFd(-1) {};

MtpDevHandle::~MtpDevHandle() {}

int MtpDevHandle::read(void *data, size_t len) {
    return ::read(mFd, data, len);
}

int MtpDevHandle::write(const void *data, size_t len) {
    return ::write(mFd, data, len);
}

int MtpDevHandle::receiveFile(mtp_file_range mfr, bool) {
    return ioctl(mFd, MTP_RECEIVE_FILE, reinterpret_cast<unsigned long>(&mfr));
}

int MtpDevHandle::sendFile(mtp_file_range mfr) {
    return ioctl(mFd, MTP_SEND_FILE_WITH_HEADER, reinterpret_cast<unsigned long>(&mfr));
}

int MtpDevHandle::sendEvent(mtp_event me) {
    return ioctl(mFd, MTP_SEND_EVENT, reinterpret_cast<unsigned long>(&me));
}

int MtpDevHandle::start(bool /* ptp */) {
    mFd.reset(TEMP_FAILURE_RETRY(open(mtp_dev_path, O_RDWR)));
    if (mFd == -1) return -1;
    return 0;
}

void MtpDevHandle::close() {
    mFd.reset();
}

} // namespace android

如果看过Binder机制的人,看这个代码是不是非常熟悉,open,ioctl,close,因为mtp驱动也是一个字符型的驱动。

2.4 驱动层

驱动层的代码我就补贴了,我担心很多人看不懂,但是为了方便理解驱动层的做的事,我会用文字描述一下从PC端通过MTP拷贝文件到手机中过程。

3 从PC端通过MTP拷贝1.txt文件到手机中的流程

1.MtpServer通过MtpDevHandle从MTP驱动中获得了1.txt接受的信息,包含了1.txt文件的信息以及存储的路径信息。
2.MtpServer根据第1步中的信息创建了一个1.txt在手机中。
3.MtpServer通过MtpDevHandle将1.txt文件路径传给了MTP驱动
4.MTP驱动不断读取USB驱动中的数据并写入到1.txt文件中
MTP驱动层伪代码

read_data = null;
write_data = null;

while(usb有数据 || write_data != null){
   read_data = read(usb);//从usb驱动中读数据
   if(write_data != null) {
       write("1.txt", write_data);
   }
   if(read_data != null) {
       write_data = read_data;
   }
}

总结

以上就是我对MTP的架构的总结,细心的读者会说android系统层很多功能的套路都差不多啊,是的,其实你只要掌握了基础Kernel驱动知识,Native开发,JNI开发,Java开发,Android Framework,Binder机制(Binder HIDL VNDBinder),会发现看很多模块自己会学的越来越快。以下就会我画的一个简单ION架构图,从看代码到画图,也就十几分钟,后续我会详细讲一下ION架构,敬请期待。

ION整体架构图
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,547评论 4 374
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,787评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,175评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,752评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,169评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,056评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,195评论 2 321
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,980评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,724评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,872评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,344评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,667评论 3 264
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,379评论 3 245
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,202评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,992评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,189评论 2 286
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,987评论 2 279