最近完成了一个新的项目,在测试的时候发现APP在启动页之后键入首页,按home键进入后台之后,在桌面再次点击进入该APP的时候,又重新开始走启动页的流程,开始以为是后台已经被"杀死"了,但是从后台进入该APP又是正常的,如果把后台APP清除掉,重新在桌面打开,一切都没问题。
由于第一次打开该APP是在安装好APP后,使用系统的安装器打开APP的,进入后台之后再去点击桌面,那么问题很可能的原因是出自于他们的Intent不一致导致的。
public boolean filterEquals(Intent other) {
if (other == null) {
return false;
}
if (!Objects.equals(this.mAction, other.mAction)) return false;
if (!Objects.equals(this.mData, other.mData)) return false;
if (!Objects.equals(this.mType, other.mType)) return false;
if (!Objects.equals(this.mPackage, other.mPackage)) return false;
if (!Objects.equals(this.mComponent, other.mComponent)) return false;
if (!Objects.equals(this.mCategories, other.mCategories)) return false;
return true;
}
从上面的图和代码可以分析得出,两者Intent的package不一致导致了它们Intent是不一样的,因为不一致就又导致了需要start的Activity再次被添加到Task上面了,主要的判断逻辑存在于ActivityStarter
的startActivityUnchecked()
方法内。
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
ActivityRecord[] outActivity) {
//省略其余代码
...
ActivityRecord reusedActivity = getReusableIntentActivity();
if (reusedActivity != null) {
...
reusedActivity = setTargetStackAndMoveToFrontIfNeeded(reusedActivity);
...
setTaskFromIntentActivity(reusedActivity);
//如果满足条件就直接返回,下面的方法都将不会被执行了
if (!mAddingToTask && mReuseTask == null) {
resumeTargetStackIfNeeded();
if (outActivity != null && outActivity.length > 0) {
outActivity[0] = reusedActivity;
}
return START_TASK_TO_FRONT;
}
}
}
这里主要的操作是获取一个可以复用的Task的栈顶的Activity,将该Task移动到前台,具体的分析可以看这里,重点看setTaskFromIntentActivity
方法。
private void setTaskFromIntentActivity(ActivityRecord intentActivity) {
if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
//若设置了该Flag,则清空Task,设置Intent
final TaskRecord task = intentActivity.getTask();
task.performClearTaskLocked();
mReuseTask = task;
mReuseTask.setIntent(mStartActivity);
mMovedOtherTask = true;
} else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
|| mLaunchSingleInstance || mLaunchSingleTask) {
ActivityRecord top = intentActivity.getTask().performClearTaskLocked(mStartActivity,
mLaunchFlags);
if (top == null) {
mAddingToTask = true;
mStartActivity.setTask(null);
mSourceRecord = intentActivity;
final TaskRecord task = mSourceRecord.getTask();
if (task != null && task.getStack() == null) {
mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
null /* bounds */, mLaunchFlags, mOptions);
mTargetStack.addTask(task,
!mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
}
}
} else if (mStartActivity.realActivity.equals(intentActivity.getTask().realActivity)) {
//此时重点看这个判断里面代码,当前未设置FLAG_ACTIVITY_SINGLE_TOP,所以走else方法
if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop)
&& intentActivity.realActivity.equals(mStartActivity.realActivity)) {
if (intentActivity.frontOfTask) {
intentActivity.getTask().setIntent(mStartActivity);
}
deliverNewIntent(intentActivity);
} else if (!intentActivity.getTask().isSameIntentFilter(mStartActivity)) {
mAddingToTask = true;
mSourceRecord = intentActivity;
}
} else if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
mAddingToTask = true;
mSourceRecord = intentActivity;
} else if (!intentActivity.getTask().rootWasReset) {
intentActivity.getTask().setIntent(mStartActivity);
}
}
在该方法内,isSameIntentFilter()
最后是调用到Intent的filterEquals()
方法,就是文章开始贴出来的那段代码,假如Intent的相同,那么mAddingToTask
依旧为false且mReuseTask也是为null,所以直接return START_TASK_TO_FRONT
,造成的效果就是把后台的Task移动前台;那么此时判断的Intent是不相同的,它将会继续走下面的将Activity添加到Task上的操作,就相当于前面所说的,又开始走启动页流程了,因为现在Task的栈顶Activity就是启动页Activity。
由于偷懒没有去找系统的安装器的源码,所以使用下面的代码复现了一下这个问题
Intent intent = getActivity().getPackageManager().getLaunchIntentForPackage("xxxxxx");
if (intent != null) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
在getLaunchIntentForPackage可以看到给Intent设置了Package
ApplicationPackageManager.java
@Override
public Intent getLaunchIntentForPackage(String packageName) {
Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
intentToResolve.addCategory(Intent.CATEGORY_INFO);
intentToResolve.setPackage(packageName);
List<ResolveInfo> ris = queryIntentActivities(intentToResolve, 0);
// Otherwise, try to find a main launcher activity.
if (ris == null || ris.size() <= 0) {
// reuse the intent instance
intentToResolve.removeCategory(Intent.CATEGORY_INFO);
intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
intentToResolve.setPackage(packageName);
ris = queryIntentActivities(intentToResolve, 0);
}
if (ris == null || ris.size() <= 0) {
return null;
}
Intent intent = new Intent(intentToResolve);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName(ris.get(0).activityInfo.packageName,
ris.get(0).activityInfo.name);
return intent;
}
所以解决办法就是就是手动将package设置为null,如果是不能直接修改的,则可以在启动页判断目前Activity的isTaskRoot,出现问题的情况下栈内的情况应该是A-B-A,此时把A finish了,就变成正常的A-B。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!isTaskRoot) {
finish()
}
}
下面是无关紧要的备注
从文章最开始的图片可以看到,第一个Intent的flag为0x10600000,第二个Intent的flag为0x10000000,开始的时候直接是系统启动器打开APP的,所以系统启动器打开APP附加FLAG_ACTIVITY_NEW_TASK
,桌面点击启动的时候会附加FLAG_ACTIVITY_NEW_TASK
、FLAG_ACTIVITY_RESET_TASK_IF_NEEDED(0x00200000)
,由于从后台转前台,再附加一个FLAG_ACTIVITY_BROUGHT_TO_FRONT(0x00400000)