最近车载项目中碰到一个问题:在酷我音乐Activity界面,按下Home按键后,点击音源硬按键,中间件后台服务接收到音源硬按键消息后,会启动酷我音乐界面Activity,但是Activity是在延时5s后才会显示;而点击导航硬按键,中间件后台服务接收到导航硬按键消息后,会启动导航APP界面,此时Activity会立刻显示出来。那问题来了,同样的都是由后台服务启动,按home键后,为什么导航界面会立刻显示出来,领导把这件事情交给我来负责研究。
一、事件分发前的拦截过程
详细的源码分析,网上已经有了相关的资料,在这里我就不重复分析(详细见https://m.2cto.com/kf/201611/563390.html)。
在这里只是分析一下Home事件在分发前的关键拦截过程:
PhoneWindowManager.java类
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
........
if (keyCode == KeyEvent.KEYCODE_HOME) {
if (!down) {
.........
launchHomeFromHotKey();
return -1;
}
........
}
}
void launchHomeFromHotKey() {
.....
try {
ActivityManagerNative.getDefault().stopAppSwitches();
} catch (RemoteException e) {
}
.....
}
最后会走到ActivityManagerService的stopAppSwitches()方法
static final long APP_SWITCH_DELAY_TIME = 5*1000;
public void stopAppSwitches() {
if (checkCallingPermission(android.Manifest.permission.STOP_APP_SWITCHES)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires permission "
+ android.Manifest.permission.STOP_APP_SWITCHES);
}
synchronized(this) {
mAppSwitchesAllowedTime = SystemClock.uptimeMillis()
+ APP_SWITCH_DELAY_TIME;
mDidAppSwitch = false;
mHandler.removeMessages(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
Message msg = mHandler.obtainMessage(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
mHandler.sendMessageDelayed(msg, APP_SWITCH_DELAY_TIME);
}
}
二、startActivity的启动流程
关于启动流程,网上已经有很多的相关资料,在这里我们只分析ActivityStackSupervisor类的startActivityLocked的方法,在此方法内我们可以发现在执行下个流程的startActivityUncheckedLocked方法前,会有个条件判断,如下:
final ActivityStack stack = getFocusedStack();
if (stack.mResumedActivity == null
|| stack.mResumedActivity.info.applicationInfo.uid != callingUid) {
if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) {
PendingActivityLaunch pal =
new PendingActivityLaunch(r, sourceRecord, startFlags, stack);
mService.mPendingActivityLaunches.add(pal);
setDismissKeyguard(false);
ActivityOptions.abort(options);
return ActivityManager.START_SWITCHES_CANCELED;
}
}
由于是后台服务启动的Activity,所以stack.mResumedActivity.info.applicationInfo.uid != callingUid的值肯定为true,其中callingUid为后台服务的UID,stack.mResumedActivity.info.applicationInfo.uid为当前前台显示Activity的UID。
继续分析ActivityManagerService类的checkAppSwitchAllowedLocked的方法:
int checkComponentPermission(String permission, int pid, int uid,
int owningUid, boolean exported) {
...
return ActivityManager.checkComponentPermission(permission, uid,
owningUid, exported);
}
最后分析ActivityManager类的checkComponentPermission的方法。
public static int checkComponentPermission(String permission, int uid,
int owningUid, boolean exported) {
if (uid == 0 || uid == Process.SYSTEM_UID) {
return PackageManager.PERMISSION_GRANTED;
}
....
try {由上可以发现后台服务的UID如果为Process.SYSTEM_UID,或者启动的Activity具有android.Manifest.permission.STOP_APP_SWITCHES权限,就不会进入延时5s启动Activity流程,而是进入startActivityUncheckedLocked方法正常启动Activity。
三、原因分析
经过一、二的分析,再在关键地方加入日志,把callingUid的值打印出来,最后发现后台服务启动导航的时候,callingUid的值为1000,与Process.SYSTEM_UID相等,而启动酷我音乐,callingUid的值为50122,进入到延时5s启动Activity的流程。
经过咨询中间件模块负责人,中间件服务的UID为Process.SYSTEM_UID,且中间件服务启动导航应用和启动酷我音乐的方式是有所区别的,启动导航应用是通过中间件的上下文调用startActivity来启动的,而启动酷我音乐是通过绑定酷我服务调用aidl接口来启动的。
综上所述,由于酷我音乐属于第三方应用,不具有系统权限,不能赋予android.Manifest.permission.STOP_APP_SWITCHES的权限,那只能改变中间件服务启动酷我的方法来解决酷我音乐启动延时5s才会显示的问题。