本文是在APP界面点击Home键后,马上在后台启动Activity会延时5秒而引起的源码分析文章。
在APP界面,点击Home键后会打开一个悬浮窗,以表示APP在后台运行。点击悬浮窗上的按键会返回APP。就是这么一个简单的过程,却无意中发现了这个BUG。当我点击Home键后,回到主桌面,然后点击悬浮窗上的按钮返回APP,而APP不会马上返回,需要延时一段时间才会启动,开始我以为是APP初始化比较耗时引起的,但这个怀疑马上就被打消了,因为我点击Back键后,回到主桌面,再点击悬浮窗上的按钮返回APP,非常迅速的就启动了。同时,我在主桌面上点击APP,也是非常迅速的就启动了。Why?这里就存在两个问题了:
启动Activity,我们都知道是调用startActivity,startActivity在framework中的调用流程,真正有用的就是下面startActivityLocked这个函数了。
、、frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
final int startActivityLocked() {
...
final ActivityStack stack = getFocusedStack();
//下面的两个判断条件就是真正影响Activity是否延时启动的关键
//这里的uid比较就是对应了我们上面的问题2. 点击Home键,再点击主桌面的APP也不会延时启动。
//在主桌面点击APP,它的uid和callingUid是相等的所以不会进入下面的判断,会退出if条件,执行后面的方法。也就不会加入延时列表直接启动了。
if (stack.mResumedActivity == null || stack.mResumedActivity.info.applicationInfo.uid != callingUid) {
//如果条件都满足Activity启动将放到延时启动列表中
//checkAppSwitchAllowedLocked在ActivityManagerService中,代码在下面
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;
}
}
...
err = startActivityUncheckedLocked(r, sourceRecord, startFlags, true, options);
...
}
//frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
// Amount of time after a call to stopAppSwitches() during which we will
// prevent further untrusted switches from happening.
static final long APP_SWITCH_DELAY_TIME = 5*1000;
//这里有两个判断条件,如果都不满足,就会返回false,返回false就会加入到延时列表中了
boolean checkAppSwitchAllowedLocked(int callingPid, int callingUid,
String name) {
//mAppSwitchesAllowedTime的时间小于当前的系统时间不会延时
//mAppSwitchesAllowedTime的赋值在stopAppSwitches函数中
if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) {
return true;
}
//权限检测,如果APP有STOP_APP_SWITCHES权限不会延时
final int perm = checkComponentPermission(
android.Manifest.permission.STOP_APP_SWITCHES, callingPid,
callingUid, -1, true);
if (perm == PackageManager.PERMISSION_GRANTED) {
return true;
}
Slog.w(TAG, name + " request from " + callingUid + " stopped");
return false;
}
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) {
//调用stopAppSwitches函数后,mAppSwitchesAllowedTime会在当前系统的时间上加上5秒
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);
//延时5秒后发送DO_PENDING_ACTIVITY_LAUNCHES_MSG消息,它的处理在doPendingActivityLaunchesLocked函数中。
mHandler.sendMessageDelayed(msg, APP_SWITCH_DELAY_TIME);
}
}
final void doPendingActivityLaunchesLocked(boolean doResume) {
final int N = mPendingActivityLaunches.size();
if (N <= 0) {
return;
}
//将延时列表中的Activity一个个启动
for (int i=0; i<N; i++) {
PendingActivityLaunch pal = mPendingActivityLaunches.get(i);
mStackSupervisor.startActivityUncheckedLocked(pal.r, pal.sourceRecord, pal.startFlags,
doResume && i == (N-1), null);
}
mPendingActivityLaunches.clear();
}
上面是ActivityManagerService延时处理Activity的一个机制。下面来分析下Home按键的响应处理,代码如下:
//frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
void launchHomeFromHotKey(int displayId, final boolean awakenFromDreams,
final boolean respectKeyguard) {
if (respectKeyguard) {
if (isKeyguardShowingAndNotOccluded()) {
// don't launch home if keyguard showing
return;
}
if (!mKeyguardOccluded && mKeyguardDelegate.isInputRestricted()) {
// when in keyguard restricted mode, must first verify unlock
// before launching home
mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() {
@Override
public void onKeyguardExitResult(boolean success) {
if (success) {
startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
}
}
});
return;
}
}
// no keyguard stuff to worry about, just launch home!
if (mRecentsVisible) {
try {
//调用ActivityManagerService中的stopAppSwitches()函数
ActivityManager.getService().stopAppSwitches();
} catch (RemoteException e) {}
// Hide Recents and notify it to launch Home
if (awakenFromDreams) {
awakenDreams();
}
hideRecentApps(false, true);
} else {
// Otherwise, just launch Home
startDockOrHome(displayId, true /*fromHomeKey*/, awakenFromDreams);
}
}
上面已经把大部分核心代码列出来了,并做了部分中文注释,感兴趣的可以看看。接下来我们把上面这个逻辑理顺一下。点击Home键,会调用ActivityManagerNative.getDefault().stopAppSwitches()->stopAppSwitches()(ActivityManagerService.java),这个函数会将mAppSwitchesAllowedTime设置成当前系统时间加上5秒后的时间,然后5秒后发送处理启动延时队列中的Activity的消息。所以,在点击Home键后,在后台Service或者BroadcasetReceiver中启动Activity时,在checkAppSwitchAllowedLocked()(ActivityManagerService.java)时,如果mAppSwitchesAllowedTime的时间大于当前的系统时间且没有STOP_APP_SWITCHES权限,Activity会加入到延时列表中。等Handler的5秒延时到达后,在启动Activity。
当然checkAppSwitchAllowedLocked前还有一个uid判断if (stack.mResumedActivity == null || stack.mResumedActivity.info.applicationInfo.uid != callingUid),这样也就解释了我们的第二个问题,所以开头的问题一和问题二都得到了解答。
通过上面的源码分析,我们知道如果想要我们的应用在Home界面快速响应,就是给APP加上android.Manifest.permission.STOP_APP_SWITCHES,当然这个权限是有限制的,普通APP是不能使用的,所以我们得在AndroidManifest.xml和Android.mk加上相应的内容。
AndroidManifest.xml
<uses-permission android:name="android.permission.STOP_APP_SWITCHES" />
Android.mk
LOCAL_CERTIFICATE := platform