公司接了项目,开发一个在线升级功能,其中我须要实现手机端与PC端的通讯。公司选择使用MTP来实现这个需求,所以我分析了大量的关于MTP的代码,从frameworks层到app,再到JNI层。鉴于网上关于这样的文章太少,而我开发的过程也比较长,所以我决定把framework, app , JNI层的分析都写下来,但愿能帮助开发相似功能的小伙伴。java
邓凡平老师写的深刻理解Android系列书籍,有的已经不出版了,可是他在博客中把文章的全部内容都发布出来,他说知识须要传递。这一点,我深感佩服。android
UsbService是一个系统服务,它在system_server进程中建立并注册的。shell
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的启动过程。app
在服务建立阶段,首先建立了UsbService一个对象,因为UsbService是一个Binder对象,而后就把这个服务发布到ServiceManager。发布这个服务后,客户端就能够访问这个服务。ide
如今来看下UsbService的构造函数函数
public UsbService(Context context) {
// ...
if (new File("/sys/class/android_usb").exists()) {
mDeviceManager = new UsbDeviceManager(context, mAlsaManager, mSettingsManager);
}
// ...
}
复制代码
在构造函数中,与MTP相关的主要代码就是建立UsbDeviceManager对象。oop
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, "">
。个人项目中,这个数组为空。
而后第二步,读取了各类属性值(只考虑正常启动模式),以下。
persist.sys.usb.config
属性的值。按照源码注释,这个属性值存储了adb的开启状态(若是开启了adb,那么这个值会包含adb字符串)。另外,源码注释说这个属性也能够运营商定制的一些功能,可是只用于测试目的。sys.usb.config
属性值。这个属性表示当前设置的usb功能的值。在平常工做中,咱们能够经过adb shell命令设置这个属性值来切换usb功能,例如adb shell setprop sys.usb.config mtp,adb
能够切换到mtp功能。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功能转化为字符串,有两种状况
FUNCTION_NONE
,那么转化后的值从persist.sys.usb.config
获取,若是获取值为NONE,就判断adb是否开启,若是开启了,转化后的值为adb,若是没有开启,转化后的值为mtp。前面分析说过,persist.sys.usb.config
主要包含用于判断adb是否开启在值,而后还包含一些厂商定制且用于测试目的的功能。例如,高通项目,这个值可能为adb,diag
,这个diag就是高通本身的功能。FUNCTION_NONE
,把直接转化。例如新功能为FUNCTION_MTP
,那么转化后的字符串为mtp
。转化字符串后,根据adb是否开启,来决定从转化后的字符串中增长adb属性仍是移除adb属性。
第二步,获取oem覆盖的功能。前面说过,默认系统是没有使用覆盖功能,因此这里获取的覆盖后的功能与新功能转化后的字符串是同样的。
我在分析代码的时候,脑海里一直在想,这个覆盖功能如何使用。根据个人对代码的分析,惟一的规则就是主要功能不能覆盖。举个例子,若是新设置的功能的字符串为mtp,那么覆盖数组中的其中一项元素的值应该是normal:mtp:mtp,diag
,其中nomral表示正常启动,mtp表示原始的功能,mtp,adb表示覆盖后的功能,请注意,覆盖后的功能必定要保存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实际的功能,从这里就能够获得验证。
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;
复制代码
UsbService是usb协议的实现,例如MTP,PTP构建于usb协议上,UsbService就实现了。然而本文是以MTP为主线进行分析的,篇幅不小,可是若是你要开发或定制关于usb功能时,这篇文章不容错过。