深入理解Android MTP之UsbService启动分析

闲话

公司接了项目,开发一个在线升级功能,其中我需要实现手机端与PC端的通信。公司选择使用MTP来实现这个需求,因此我分析了大量的关于MTP的代码,从frameworks层到app,再到JNI层。鉴于网上关于这样的文章太少,而我开发的过程也比较长,因此我决定把framework, app , JNI层的分析都写下来,希望能帮助开发类似功能的小伙伴。

邓凡平老师写的深入理解Android系列书籍,有的已经不出版了,但是他在博客中把文章的所有内容都发布出来,他说知识需要传递。这一点,我深感佩服。

服务开启

UsbService是一个系统服务,它在system_server进程中创建并注册的。

private static final String USB_SERVICE_CLASS =
        "com.android.server.usb.UsbService$Lifecycle";
            
private void startOtherServices() {
    // ...
    
    mSystemServiceManager.startService(USB_SERVICE_CLASS);
    
    // ...   
}

SystemServiceManager通过反射创建UsbService$Lifecycle对象(Lifecycle是UsbService的一个内部类),然后加入到List集合中,最后调用Lifcycle对象的onStart方法。

SystemServiceManager保存了各种服务,并且会把系统启动的各个阶段告诉服务,我们可以看看UsbService$Lifecycle的各种生命周期回调

public class UsbService extends IUsbManager.Stub {

    public static class Lifecycle extends SystemService {
    
        // 服务创建阶段
        @Override
        public void onStart() {
            
            mUsbService = new UsbService(getContext());
            publishBinderService(Context.USB_SERVICE, mUsbService);
        }
        
        // 响应系统启动阶段
        @Override
        public void onBootPhase(int phase) {
            if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
				// 系统就绪阶段
                mUsbService.systemReady();
            } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
            	// 系统启动完毕
                mUsbService.bootCompleted();
            }
        }        
    }
}

可以看到,一个服务会经过创建阶段,系统就绪阶段,系统启动完毕阶段。接下来,分为三部分来分析UsbService的启动过程。

服务创建阶段

在服务创建阶段,首先创建了UsbService一个对象,由于UsbService是一个Binder对象,然后就把这个服务发布到ServiceManager。发布这个服务后,客户端就可以访问这个服务。

现在来看下UsbService的构造函数

    public UsbService(Context context) {
        // ...
        
        if (new File("/sys/class/android_usb").exists()) {
            mDeviceManager = new UsbDeviceManager(context, mAlsaManager, mSettingsManager);
        }

        // ...
    }

在构造函数中,与MTP相关的主要代码就是创建UsbDeviceManager对象。

MTP模式下,Android设备是作为Device端,UsbDeviceManager就是用来处理Device端的事务。

现在来看下UsbDeviceManager的构造函数做了什么

    public UsbDeviceManager(Context context, UsbAlsaManager alsaManager,
            UsbSettingsManager settingsManager) {
        // ...
        
        // 我的项目不支持MTP的Hal层
        if (halNotPresent) {
            // 1. 初始化mHandler
            mHandler = new UsbHandlerLegacy(FgThread.get().getLooper(), mContext, this,
                    alsaManager, settingsManager);
        } else {
            // ...
        }


        // 2. 注册各种广播
        //... 这里其实注册了很多个广播接收器(只不过和省略代码了)
        BroadcastReceiver chargingReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
                boolean usbCharging = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
                mHandler.sendMessage(MSG_UPDATE_CHARGING_STATE, usbCharging);
            }
        };

        mContext.registerReceiver(chargingReceiver,
                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
        
        
        // 3. 监听USB状态改变
        mUEventObserver = new UsbUEventObserver();
        mUEventObserver.startObserving(USB_STATE_MATCH);
        mUEventObserver.startObserving(USB_STATE_MATCH_SEC);
        mUEventObserver.startObserving(ACCESSORY_START_MATCH);
    }

UsbDeviceManager的构造函数做了三件事。

第一件事,初始化mHandler对象。由于我的项目不支持MTP的Hal层,因此mHandler的初始化使用的是UsbHandlerLegacy对象。

第二事,注册了各种广播接收器,例如端口变化,语言变化,等等。这里我把关于充电的广播接收器代码展示出来了。当我们把手机通过USB线连接到电脑端的时候,手机会充电,并且手机上会出现一个关于USB充电的通知。打开这个关于USB的通知,我们就可以切换USB的功能,例如MTP, PTP,等等。

第三件事,通过Linux Uevent机制监听USB状态变换。当手机通过USB线连接电脑时,USB状态会从DISCONNECTED变为CONNECTED,再变为CONFIGURED。当状态改变会处理usb状态更新操作,这个过程在后面会分析到。

现在来看看UsbHandlerLegacy对象的创建。

        UsbHandlerLegacy(Looper looper, Context context, UsbDeviceManager deviceManager,
                UsbAlsaManager alsaManager, UsbSettingsManager settingsManager) {
            // 父类构造函数初始化了一些参数,大家可以根据需要自己分析
            super(looper, context, deviceManager, alsaManager, settingsManager);
            try {
                // 1. 读取oem覆盖配置
                readOemUsbOverrideConfig(context);
                
                // 2. 读取各种属性的值
                // 2.1 正常模式,读取的是persist.sys.usb.config属性的值
                mCurrentOemFunctions = getSystemProperty(getPersistProp(false),
                        UsbManager.USB_FUNCTION_NONE);
                        
                // ro.bootmode属性的值为normal或unknown,就表示正常启动
                if (isNormalBoot()) {
                    // 2.2 读取sys.usb.config属性的值,这个属性表示当前设置的usb功能
                    mCurrentFunctionsStr = getSystemProperty(USB_CONFIG_PROPERTY,
                            UsbManager.USB_FUNCTION_NONE);
                    // 2.3 比较sys.usb.config属性与sys.usb.state属性的值
                    // sys.usb.state属性表示usb的实际功能
                    // 如果两个属性相等,表示usb设置的功能都起效了
                    mCurrentFunctionsApplied = mCurrentFunctionsStr.equals(
                            getSystemProperty(USB_STATE_PROPERTY, UsbManager.USB_FUNCTION_NONE));
                } else {

                }
                
                // mCurrentFunctions代表当前要设置的usb功能,初始值为0
                mCurrentFunctions = UsbManager.FUNCTION_NONE;
                mCurrentUsbFunctionsReceived = true;
                
                // 3. 读取一次usb状态,然后做一次更新操作
                String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
                updateState(state);
            } catch (Exception e) {
                Slog.e(TAG, "Error initializing UsbHandler", e);
            }
        }

UsbHandlerLegacy的构造函数大致分为三步。首先看第一步,读取oem厂商的关于usb功能的覆盖配置。

        private void readOemUsbOverrideConfig(Context context) {
            // 数组每一项的格式为[bootmode]:[original USB mode]:[USB mode used]
            String[] configList = context.getResources().getStringArray(
                    com.android.internal.R.array.config_oemUsbModeOverride);

            if (configList != null) {
                for (String config : configList) {
                    String[] items = config.split(":");
                    if (items.length == 3 || items.length == 4) {
                        if (mOemModeMap == null) {
                            mOemModeMap = new HashMap<>();
                        }
                        HashMap<String, Pair<String, String>> overrideMap =
                                mOemModeMap.get(items[0]);
                        if (overrideMap == null) {
                            overrideMap = new HashMap<>();
                            mOemModeMap.put(items[0], overrideMap);
                        }

                        // Favoring the first combination if duplicate exists
                        if (!overrideMap.containsKey(items[1])) {
                            if (items.length == 3) {
                                overrideMap.put(items[1], new Pair<>(items[2], ""));
                            } else {
                                overrideMap.put(items[1], new Pair<>(items[2], items[3]));
                            }
                        }
                    }
                }
            }
        }

读取的是config_oemUsbModeOverride数组,然后保存到mOemModeMap中。数组每一项的格式为[bootmode]:[original USB mode]:[USB mode used],保存的格式可以大致描述为HashMap<bootmode, HashMap<original_usb_mode, Pair<usb_mode_used, "">。我的项目中,这个数组为空。

然后第二步,读取了各种属性值(只考虑正常启动模式),如下。

  1. mCurrentOemFunctions的值是persist.sys.usb.config属性的值。按照源码注释,这个属性值存储了adb的开启状态(如果开启了adb,那么这个值会包含adb字符串)。另外,源码注释说这个属性也可以运营商定制的一些功能,但是只用于测试目的。
  2. mCurrentFunctionsStr的值是sys.usb.config属性值。这个属性表示当前设置的usb功能的值。在日常工作中,我们可以通过adb shell命令设置这个属性值来切换usb功能,例如adb shell setprop sys.usb.config mtp,adb可以切换到mtp功能。
  3. 如果通过sys.usb.config属性切换功能成功,那么sys.usb.state属性值就与sys.usb.config属性值一样。也就是说sys.usb.state代表usb的实际功能的值。所以,可以通过比较这两个属性值来判断usb所有功能是否切换成功,如果成功了,mCurrentFunctionsApplied的值为1,否则为0。

第三步,读取了当前usb状态,并且做了一次更新操作。更新操作会发送相关通知,以及发送广播,但是现在处理服务创建阶段,这个操作都无法执行,因此这里不做分析。但是当处理系统就绪阶段或系统启动完毕阶段,就可以做相应的操作,在后面的分析中可以看到。

系统就绪阶段

根据前面的代码,在系统就绪阶段,会调用UsbService的systemRead()方法,然后转到UsbDeviceManager的systemRead()方法

    public void systemReady() {
        // 注册一个关于屏幕状态的回调,有两个方法
        LocalServices.getService(ActivityTaskManagerInternal.class).registerScreenObserver(this);

        mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
    }

首先注册了一个关于屏幕的回调,这个回调用于处理在安全锁屏下,设置usb的功能。但是这个功能好像处于开发阶段,只能通过adb shell命令操作,通过输入adb shell svc usb可以查看使用帮助。

接下来,发送了一个消息MSG_SYSTEM_READY,我们来看下这个消息是如何处理的

        case MSG_SYSTEM_READY:
            // 获取到notification服务接口
            mNotificationManager = (NotificationManager)
                    mContext.getSystemService(Context.NOTIFICATION_SERVICE);

            // 向adb service注册一个回调,用于状态adb相关的状态
            LocalServices.getService(
                    AdbManagerInternal.class).registerTransport(new AdbTransport(this));

            // Ensure that the notification channels are set up
            if (isTv()) {
                // ...
            }
            // 设置系统就绪的标志位
            mSystemReady = true;
            // 此时系统还没有启动完成,这里没有做任何事
            // 这应该是历史原因造成的代码冗余
            finishBoot();
            break;

可以看到,在系统就绪阶段才获取到了通知服务的接口,这也从侧面证明了在UsbService创建阶段,是无法发送通知的。然而我却有点疑惑,通知服务和UsbService同进程,并且通知服务也有内部接口可供系统服务调用,为何这里还要通过NotificationManager发送广播?难道只是为写代码方便,但是这样一来,不就进行了一次不必要的Binder通信(可能这是我的妄言!)?

获取通知服务接口后,就向adb服务注册了一个回调,可以通过这个回调,可以接收到关于adb开户/关闭的消息。

系统启动完毕阶段

现在来看下最后一个阶段,系统启动完毕阶段。根据前面的代码,会调用UsbService的bootcompleted()方法,然后调用UsbDeviceManager的bootcompleted()方法

    public void bootCompleted() {
        mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED);
    }

只是发送了一条消息,看下消息如何处理的

        case MSG_BOOT_COMPLETED:
			// 设置系统启动完成的标志
            mBootCompleted = true;
            finishBoot();
            break;

很简单,设置了一个启动标志,然后就调用finishBoot()方法完成最后的任务

        protected void finishBoot() {
            if (mBootCompleted && mCurrentUsbFunctionsReceived && mSystemReady) {
                // mPendingBootBroadcast是在服务创建阶段设置的
                if (mPendingBootBroadcast) {
                    // 1. 发送/更新usb状态改变的广播
                    updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
                    mPendingBootBroadcast = false;
                }
                if (!mScreenLocked
                        && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
                    // 这个功能还是处于调试阶段,不分析
                    setScreenUnlockedFunctions();
                } else {
                    // 2. 设置USB功能为NONE
                    setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
                }
                // 关于Accessory功能
                if (mCurrentAccessory != null) {
                    mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
                }
				// 3. 如果手机已经连接电脑就发送usb通知,通过这个通知,可以选择usb模式
                updateUsbNotification(false);
				// 4. 如果adb已经开启,并且手机已经连接电脑,就发送adb通知
                updateAdbNotification(false);
                // 关于MIDI功能
                updateUsbFunctions();
            }
        }

如果现在手机没有通过USB线连接电脑,那么第一步的发送USB状态广播,第三步的USB通知,第四步adb通知,都无法执行。唯一能执行的就是第二步,设置USB功能为NONE。

OK,现在终于到最关键的一步,设置USB功能,它调用的是setEnabledFunctions()方法。这个方法本身是一想抽象方法,在我的项目中,实现类为UsbHandlerLegacy

        protected void setEnabledFunctions(long usbFunctions, boolean forceRestart) {
            // 判断数据是否解锁,只有MTP和PTP的数据是解锁的
            boolean usbDataUnlocked = isUsbDataTransferActive(usbFunctions);
            
            // 处理数据解锁状态改变的情况
            if (usbDataUnlocked != mUsbDataUnlocked) {
                // 更新数据解锁状态
                mUsbDataUnlocked = usbDataUnlocked;
                // 更新usb通知
                updateUsbNotification(false);
                // forceRestart设置为true,表示需要强制重启usb功能
                forceRestart = true;
            }
            
            // 在设置新usb功能前,先保存旧的状态,以免设置新功能失败,还可以恢复
            final long oldFunctions = mCurrentFunctions;
            final boolean oldFunctionsApplied = mCurrentFunctionsApplied;
            
            // 尝试设置usb新功能
            if (trySetEnabledFunctions(usbFunctions, forceRestart)) {
                return;
            }

            // 如果到这里,就表示新功能设置失败,那么就回退之前的状态
            if (oldFunctionsApplied && oldFunctions != usbFunctions) {
                Slog.e(TAG, "Failsafe 1: Restoring previous USB functions.");
                if (trySetEnabledFunctions(oldFunctions, false)) {
                    return;
                }
            }

            // 如果回退还是失败了,那么就设置usb功能为NONE
            if (trySetEnabledFunctions(UsbManager.FUNCTION_NONE, false)) {
                return;
            }

            // 如果设置NONE还是失败了,那么再试一次设置NONE
            if (trySetEnabledFunctions(UsbManager.FUNCTION_NONE, false)) {
                return;
            }
            
            // 如果走到这里,就表示异常了。
            Slog.e(TAG, "Unable to set any USB functions!");
        }

首先判断要设置的新的USB功能的数据是否是解锁状态,只有MTP和PTP模式的数据是解锁状态,这是为何你能在设置MTP或PTP模式后,在PC端能看到手机中的文件,然而这个文件只是手机内存中文件的映射,并不是文件本身。

然后处理数据解锁状态改变的情况,如果是,那么会更新状态,更新usb广播,然后最重要的是设置forceRestart变量的值为true,这个变量代表要强制重启usb功能。

最后,设置新usb功能。如果失败了,就回退。现在来看下trySetEnabledFunctions()方法如何设置新功能

        private boolean trySetEnabledFunctions(long usbFunctions, boolean forceRestart) {
            // 1. 把新usb功能转化为字符串
			String functions = null;
			
			// 如果新功能不是NONE,就转化
            if (usbFunctions != UsbManager.FUNCTION_NONE) {
                functions = UsbManager.usbFunctionsToString(usbFunctions);
            }
            
            // 保存待设置的新usb功能
            mCurrentFunctions = usbFunctions;
            
            // 如果转化后的功能为空,那么就从其它地方获取
            if (functions == null || applyAdbFunction(functions)
                    .equals(UsbManager.USB_FUNCTION_NONE)) {
                // 获取persist.sys.usb.config属性值
                functions = getSystemProperty(getPersistProp(true),
                            UsbManager.USB_FUNCTION_NONE);
                
                // 如果persist.sys.usb.config属性值还是为NONE
                if (functions.equals(UsbManager.USB_FUNCTION_NONE))
                // 如果adb开启,返回adb,否则返回mtp
                functions = UsbManager.usbFunctionsToString(getChargingFunctions());
            }

            // adb开启,就追加adb值,否则移除adb值
            functions = applyAdbFunction(functions);

            // 2. 获取oem覆盖的usb功能
            String oemFunctions = applyOemOverrideFunction(functions);
            
            // 处理非正常启动模式情况,忽略
            if (!isNormalBoot() && !mCurrentFunctionsStr.equals(functions)) {
                setSystemProperty(getPersistProp(true), functions);
            }
            
            // 3. 设置新功能
            if ((!functions.equals(oemFunctions) 
            && !mCurrentOemFunctions.equals(oemFunctions))
                    || !mCurrentFunctionsStr.equals(functions)
                    || !mCurrentFunctionsApplied
                    || forceRestart) {
                Slog.i(TAG, "Setting USB config to " + functions);
                // 保存要设置新功能对应的字符串值
                mCurrentFunctionsStr = functions;
                // 保存oem覆盖功能的字符串值
                mCurrentOemFunctions = oemFunctions;
                mCurrentFunctionsApplied = false;

                // 先断开已经存在的usb连接
                setUsbConfig(UsbManager.USB_FUNCTION_NONE);
                // 判断是否成功
                if (!waitForState(UsbManager.USB_FUNCTION_NONE)) {
                    Slog.e(TAG, "Failed to kick USB config");
                    return false;
                }

                // 设置新功能,注意,这里使用的是oem覆盖的功能
                setUsbConfig(oemFunctions);
            
                // 如果新功能包含mtp或ptp,那么就要更新usb状态改变广播
                // 广播接收者会映射主内存的文件到PC端
                if (mBootCompleted
                        && (containsFunction(functions, UsbManager.USB_FUNCTION_MTP)
                        || containsFunction(functions, UsbManager.USB_FUNCTION_PTP))) {
                    updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
                }
                
                // 等待新功能设置完毕
                if (!waitForState(oemFunctions)) {
                    Slog.e(TAG, "Failed to switch USB config to " + functions);
                    return false;
                }

                mCurrentFunctionsApplied = true;
            }
            return true;
        }

我把这里的逻辑分为了三步.

第一步,把待设置的USB功能转化为字符串,有两种情况

  1. 如果新功能为FUNCTION_NONE,那么转化后的值从persist.sys.usb.config获取,如果获取值为NONE,就判断adb是否开启,如果开启了,转化后的值为adb,如果没有开启,转化后的值为mtp。前面分析说过,persist.sys.usb.config主要包含用于判断adb是否开启在值,然后还包含一些厂商定制且用于测试目的的功能。例如,高通项目,这个值可能为adb,diag,这个diag就是高通自己的功能。
  2. 如果新功能不为FUNCTION_NONE,把直接转化。例如新功能为FUNCTION_MTP,那么转化后的字符串为mtp

转化字符串后,根据adb是否开启,来决定从转化后的字符串中增加adb属性还是移除adb属性。

第二步,获取oem覆盖的功能。前面说过,默认系统是没有使用覆盖功能,所以这里获取的覆盖后的功能与新功能转化后的字符串是一样的。

我在分析代码的时候,脑海里一直在想,这个覆盖功能如何使用。根据我的对代码的分析,唯一的规则就是主要功能不能覆盖。举个例子,如果新设置的功能的字符串为mtp,那么覆盖数组中的其中一项元素的值应该是normal:mtp:mtp,diag,其中nomral表示正常启动,mtp表示原始的功能,mtp,diag表示复盖后的功能,请注意,覆盖后的功能一定要保存mtp这个主功能。当然这只是我个人对代码分析得出的结论,还没验证。这里我要吐槽一下这个功能的设计者,难道写个例子以及注意事项就这么难吗?

第三步,设置新功能。不过设置新功能前,首先要断开已经存在的连接,然后再设置新功能。设置新功能是通过setUsbConfig()方法,来看下实现

        private void setUsbConfig(String config) {
            // 设置sys.usb.config
            setSystemProperty(USB_CONFIG_PROPERTY, config);
        }

震惊!原来就是设置sys.usb.config的属性值,还记得吗,在前面的分析中,也解释过这个属性值,它就是代表当前设置的usb功能,从这里就可以得到证明。

这其实也在提示我们,其实可以通过adb shell setprop命令设置这个属性,从而控制usb功能的切换。在实际的工作中,屡试不爽。

设置这个属性后如何判断设置成功了呢?这就是waitForState()所做的

        private boolean waitForState(String state) {
            String value = null;
            for (int i = 0; i < 20; i++) {
                // 获取sys.usb.stat值
                value = getSystemProperty(USB_STATE_PROPERTY, "");
                // 与刚才设置的sys.usb.config属性值相比较
                if (state.equals(value)) return true;
                SystemClock.sleep(50);
            }
            return false;
        }

说实话,我看到这段代码,确实吃了一鲸! 这段代码在1秒内执行20次,获取sys.usb.state属性值,然后与设置的sys.usb.config属性值相比较,如果相等就表示功能设置成功。

还记得吗?在前面的分析中,我也解释过sys.usb.state属性的作用,它代表usb实际的功能,从这里就可以得到验证。

那么现在有一个问题,底层如何实现usb功能切换呢?当然是响应属性sys.usb.config属性改变,例如我的项目的底层呼应代码是这样

on property:sys.usb.config=mtp,adb && property:sys.usb.configfs=0
	# 先写0
    write /sys/class/android_usb/android0/enable 0
    # 写序列号
    write /sys/class/android_usb/android0/iSerial ${ro.serialno}
    # 写vid, pid
    write /sys/class/android_usb/android0/idVendor 05C6
    write /sys/class/android_usb/android0/idProduct 9039
    # 设置USB功能为mtp,adb
    write /sys/class/android_usb/android0/functions mtp,adb
    # 再写1启动功能
    write /sys/class/android_usb/android0/enable 1
    # 启动adb
    start adbd
    # 设置 sys.usb.state属性值为sys.usb.config的属性值
    setprop sys.usb.state ${sys.usb.config}

根据注释,你应该就可以很清楚了解这个过程了。

so, 你以为这就完了吗?还没呢,如果新设置的功能是MTP或PTP,那么还要更新广播呢。

        protected void updateUsbStateBroadcastIfNeeded(long functions) {
            // 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);
            // 保存了usb状态值
            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() && isUsbDataTransferActive(mCurrentFunctions));
            
            // 保存了要设置的新功能的值,例如设置的是MTP,那么参数的key为mtp,值为true
            long remainingFunctions = functions;
            while (remainingFunctions != 0) {
                intent.putExtra(UsbManager.usbFunctionsToString(
                        Long.highestOneBit(remainingFunctions)), true);
                remainingFunctions -= Long.highestOneBit(remainingFunctions);
            }

            // 如果状态没有改变,就不发送广播
            if (!isUsbStateChanged(intent)) {
                return;
            }

            // 注意这里发送的是一个sticky广播
            sendStickyBroadcast(intent);
            mBroadcastedIntent = intent;

注意,这里发送的是一个sticky广播。那么这个广播的接收者是谁呢?接收这个广播又做了什么呢?这就是下一篇文章的内容。

结束

UsbService是usb协议的实现,例如MTP,PTP构建于usb协议上,UsbService就实现了。然而本文是以MTP为主线进行分析的,篇幅不小,但是如果你要开发或定制关于usb功能时,这篇文章不容错过。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章