一、问题现象
首先进入非launcher任意界面,按Home键返回,点击Compass,弹出Compass请求device’s location权限,勾选Never ask again,点击DENY后进入应用,点击底部小字,等待从下方弹出一个获取权限的activity后按back键退出Compass,退出过程中会闪现之前打开的界面。
Platform: MSM8976
Android version: Android M 6.0.1
系统软件版本: SWA2I
二、初步分析结论
Activity被finish的时候,如果当前Activity不是所在Task中最后的一个,那么系统会重新设定Task中ActivityRecord的frontOfTask值,而frontOfTask的值会对FocusedActivity产生影响,导致上面所出现的问题。
三、具体分析过程
正常情况下,打开Compass后会先启动开始界面Compass.java,同时弹出权限申请框,随后会从启动界面Compass.java跳转到Compass的主界面CompassMainActivity.java,
(1)在启动每个Activity的时候,都会在startActivityLocked(ActivityStack.java)方法中调用setFrontOfTask(TaskRecord.java)方法,对task中ActivityRecord的frontOfTask值进行设置,跳转到主界面CompassMainActivity.java后,此时Task中有以上两个ActivityRecord,根据setFrontOfTask方法的逻辑,ActivityRecord{e188681 u0 com.jrdcom.compass/.Compass t20}的frontOfTask属性被设置为true,随后启动界面Compass.java会被finish,在finish的过程中又会在finishActivityLocked方法中判断正在finish的Activity在task中的位置,如果(index < (activities.size() - 1)),那么就会再一次调用setFrontOfTask,此时Compass.index=0,(activities.size()-1)=1, ActivityRecord{eaedfb0 u0 com.jrdcom.compass/.CompassMainActivity t20}的FrontOfTask会被设置为true。
/**
* @return Returns true if the activity is being finished, false if for
* some reason it is being left as-is.
*/
final boolean requestFinishActivityLocked(IBinder token, int resultCode,
Intent resultData, String reason, boolean oomAdj) {
ActivityRecord r = isInStackLocked(token);
if (DEBUG_RESULTS || DEBUG_STATES) Slog.v(TAG_STATES,
"Finishing activity token=" + token + " r="
+ ", result=" + resultCode + ", data=" + resultData
+ ", reason=" + reason);
if (r == null) {
return false;
}
finishActivityLocked(r, resultCode, resultData, reason, oomAdj);
return true;
}
/**
* @return Returns true if this activity has been removed from the history
* list, or false if it is still in the list and will be removed later.
*/
final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData,
String reason, boolean oomAdj) {
if (r.finishing) {
Slog.w(TAG, "Duplicate finish request for " + r);
return false;
}
r.makeFinishingLocked();
final TaskRecord task = r.task;
EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY,
r.userId, System.identityHashCode(r),
task.taskId, r.shortComponentName, reason);
final ArrayList<ActivityRecord> activities = task.mActivities;
final int index = activities.indexOf(r);
if (index < (activities.size() - 1)) {
task.setFrontOfTask();
if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
// If the caller asked that this activity (and all above it)
// be cleared when the task is reset, don't lose that information,
// but propagate it up to the next activity.
ActivityRecord next = activities.get(index+1);
next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
}
}
r.pauseKeyDispatchingLocked();
adjustFocusedActivityLocked(r, "finishActivity");
/** Call after activity movement or finish to make sure that frontOfTask is set correctly */
final void setFrontOfTask() {
boolean foundFront = false;
final int numActivities = mActivities.size();
for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
final ActivityRecord r = mActivities.get(activityNdx);
if (foundFront || r.finishing) {
r.frontOfTask = false;
} else {
r.frontOfTask = true;
// Set frontOfTask false for every following activity.
foundFront = true;
}
}
if (!foundFront && numActivities > 0) {
// All activities of this task are finishing. As we ought to have a frontOfTask
// activity, make the bottom activity front.
mActivities.get(0).frontOfTask = true;
}
}
(2)紧接着按back键退出Compass,被finish的是主界面CompassMainActivity,同样调用 finishActivityLocked,
1. 若此时启动界面已经被finish,启动界面activity也已经从历史列表中移除,CMA.index=0,(activities.size()-1)=0,task中只有一个ActivityRecord,不再重新设置frontOfTask的值;
2. 若此时启动界面activity还没有从历史列表中移除,CMA.index=1, (activities.size()-1)=1, 同样 index<(activities.size()-1)不成立,也不再重新设置frontOfTask的值。
紧接着调用 adjustFocusedActivityLocked方法对FocusedActivity进行处理。
private void adjustFocusedActivityLocked(ActivityRecord r, String reason) {
if (mStackSupervisor.isFrontStack(this) && mService.mFocusedActivity == r) {
System.out.println("ran.zhou-AS-L2663-r = " + r);
ActivityRecord next = topRunningActivityLocked(null);
final String myReason = reason + " adjustFocus";
if (next != r) {
final TaskRecord task = r.task;
//modify by minzhang@tcl.com start
//merge from google,https://code.google.com/p/android/issues/detail?id=192090
//as same as defect 958818.
boolean adjust = false;
// || (r+"").contains("CompassMainActivity")
if ((next == null || next.task != task) && r.frontOfTask){
if (task.isOverHomeStack() && task == topTask()) {
adjust = true;
} else {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord tr = mTaskHistory.get(taskNdx);
//It is importance ! Annotate by minzhang@tcl.com
if (tr.getTopActivity() != null) {
break;
} else if (tr.isOverHomeStack()) {
adjust = true;
break;
}
}
}
}
//modify by minzhang@tcl.com end
if (adjust) {
// For non-fullscreen stack, we want to move the focus to the next visible
// stack to prevent the home screen from moving to the top and obscuring
// other visible stacks.
if (!mFullscreen
&& adjustFocusToNextVisibleStackLocked(null, myReason)) {
return;
}
// Move the home stack to the top if this stack is fullscreen or there is no
// other visible stack.
if (mStackSupervisor.moveHomeStackTaskToTop(
task.getTaskToReturnTo(), myReason)) {
//moveHomeStackTaskToTop会调用setFocusedActivityLocked.
// Activity focus was already adjusted. Nothing else to do...
return;
}
}
}
final ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
if (top != null) {
mService.setFocusedActivityLocked(top, myReason);
}
}
}
(3)adjustFocusedActivityLocked方法是调用setFocusedActivityLocked方法对焦点activity进行调整的,此时会把HomeStack移动到顶端,把ActivityRecord{7fafecf u0 com.google.android.googlequicksearchbox/com.google.android.launcher.GEL t18}设置为FocusedActivity。
出现问题的情况下,经过以上步骤(1)过后,立即点击主界面下方小字(此时主界面还没有从history list中移除),弹出申请权限的GrantPermissionsActivity,startActivityLocked()方法会再一次调用setFrontOfTask,此时Compass.finishing=true,结果为CMS.frontOfTask=true。
(4)弹出GrantPermissionsActivity后按back键,依次对GrantPermissionsActivity、CompassMainActivity执行finishActivityLocked方法,此时启动界面activity Compass依然还没有从history中移除,GPA.index = 2, CMA.index = 1, Compass.index = 0, activities.size()=3,在finish CMA的时候调用了setFrontOfTask方法,根据代码逻辑,mActivities.get(0).frontOfTask=true,即Compass.frontOfTask=true, 导致在finish CMA的时候adjustFocusedActivityLocked方法在if ((next == null || next.task != task) && r.frontOfTask){时没有走进去,而是继续往下执行走到了下面代码:
final ActivityRecord top = mStackSupervisor.topRunningActivityLocked();
if (top != null) {
mService.setFocusedActivityLocked(top, myReason);
}
在此处查找处于最顶端的运行中的activity,即开头所提到的任意非launcher activity,并把其设置为FocusedActivity,导致了退出之前闪了一下,接下来继续执行之前的finish步骤,把ActivityRecord{7fafecf u0 com.google.android.googlequicksearchbox/com.google.android.launcher.GEL t18}设置为FocusedActivity,最终返回到launcher界面。
(5)若经过步骤(1)后,经过几秒等待启动界面从history list中移除后再点击主界面下方小字,等待GPA弹出后立即按back键退出,则CMS.index = 0, GAP.index = 1, activities.size() = 2, mActivities.get(0).frontOfTask = true, 即CMS.frontOfTask = true, 代码正常执行,显示正常。
(6)若经过步骤(1)后,经过几秒等待启动界面从history list中移除后再点击主界面下方小字,等待GPA弹出后等待GPA也从history list中移除,则CMS.index = 0,activities.size() = 1, CMS.frontOfTask依然为true,再按back键退出,显示正常。
四、解决方案
此问题应该属于项目代码缺陷,没有针对bug中的测试方法进行处理,在finish Activity的时候从history list中移除过于缓慢,针对Compass这个特定例子,通过修改代码:
“`
if ((next == null || next.task != task) && r.frontOfTask){ ===>修改为
if ((next == null || next.task != task) && (r.frontOfTask || (r+””).contains(“CompassMainActivity”))){
`
强制性引导代码执行到正常代码块,以解决文中问题。
通用性修改的话,个人觉得应该在从history list中移除Activity的逻辑上进行修改,使Activity能够及时从history list中移除,次之则是在finishActivityLocked(ActivityStack.java)中执行setFrontOfTask()方法的判断条件这个地方,或者在setFrontOfTask()方法的逻辑上进行修改。“