目录
前言
这也是一个面试过程中经常会被问到的问题,网上的面试题也很多,但是对源码进行分析的文章比较难找,所以我就自己分析了一下,也希望这篇文章能对面试者有所帮助~
一.进入主题 思考问题
分析源码之前呢,咱先思考几个问题,分析完成后,看能不能解决掉~
- 若两个应用A和B,A中有两个Activity,A1和A2,B中有一个Activity B1,这三个Activity都是SingleTask模式,启动顺序是A1 -> B1 -> A2,然后点击返回按钮,会如何?
- 接着前面问题,如果A2是Standard模式 + FlAG_ACTIVITY_NEW_TASK呢?
- FLAG_ACTIVITY_NEW_TASK 有什么用?taskAffinity又是啥?
二.启动模式源码分析
启动模式相关源码都在ActivityStarter.java文件的startActivityUnchecked这个方法, 我们拆解这个函数来分析一下:( Android 9.0 代码为例 )
注意下两个很重要的属性 mLaunchFlags,mLaunchMode,后面的源码分析也主要围绕着两个属性在讨论
mLaunchFlags 包含启动的flag,比如FLAG_ACTIVITY_NEW_TASK,FLAG_ACTIVITY_CLEAR_TASK等,作用是规定了如何去启动一个Activity,对栈的选择和处理。
mLaunchMode 表示启动模式,比如LAUNCH_SINGLE_INSTANCE,LAUNCH_SINGLE_TASK等。
2.1 初始化工作
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
ActivityRecord[] outActivity) {
// 初始化 mLaunchFlags mLaunchMode
setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
voiceInteractor);
// 一.计算 mLaunchFlags
computeLaunchingTaskFlags();
//赋值 mSourceTask
computeSourceStack();
mIntent.setFlags(mLaunchFlags);
... ... ...
看 computeLaunchingTaskFlags 方法
private void computeLaunchingTaskFlags() {
... ... ...
// mSourceRecod 指的是 启动者,(注意区别于 被启动者 mStartActivity) mSourceRecord为null,表示我们不是从一个Activity来启动的
// 可能是从 一个Service 或者 ApplicationContext 来的
if (mSourceRecord == null) {
if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) {
//mInTask mSourceRecord 都为null, 表示 不是从一个Activity 去启动另外一个Activity,所以不管什么
//都加上 FLAG_ACTIVITY_NEW_TASK
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
} else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
// 如果 启动者 自己是 SINGLE_INSTANCE , 那么不管被启动的Activity是什么模式,mLaunchFlags 都加上 FLAG_ACTIVITY_NEW_TASK,
// 这个新 Activity 需要运行在 自己的 栈内
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
} else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
//如果launchMode是 SINGLE_INSTANCE 或者 SINGLE_TASK; mLaunchFlags 添加 FLAG_ACTIVITY_NEW_TASK
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
}
2.1 小结一下
注意图中有3个地方为mLaunchFlags添加了FLAG_ACTIVITY_NEW_TASK
- 如果启动者mSourceRecord为null,比如在一个Service启动了一个Activity,那么会为mLaunchFlags增加FLAG_ACTIVITY_NEW_TASK
- 如果启动者mSourceRecord是一个 SingleInstance 类型的Activity,那么被启动者的mLaunchFlags就会加上 FLAG_ACTIVITY_NEW_TASK
- 如果被启动者mStartActivity是SINGLE_INSTANCE或者SINGLE_TASK 类型的Activity,被启动者的mLaunchFlags都会加上FLAG_ACTIVITY_NEW_TASK
这里可以思考一个问题
一般在Service或者Application启动一个Activity,和在Activity内启动一个Activity有啥区别,要注意什么?(Actvivity重写了父类ContextWrapper的startActivity方法,Service调用的本质上还是ContextImpl的startActivity方法,可以从这个角度去看源码理解)
2.2 getResuableIntentActivity
这一部分还是startActivityUnchecked方法的一个片段,紧接着第一部分的源码
//还是在 startActivityUnchecked 里面
... ... ...
// 1. 为SINGLE_INSTANCE查找可以复用的Activity
// 2. 为 只有FLAG_ACTIVITY_NEW_TASK并且没有MULTI_TASK的
// 3. SINGLE_TASK 查找可以添加的栈
ActivityRecord reusedActivity = getReusableIntentActivity();
... ... ...
我们顺便也跳出startActivityUnchecked方法,去看一看getResuableIntentActivity方法。
private ActivityRecord getReusableIntentActivity() {
//putIntoExistingTask为true的条件
//(1)当启动模式为SingleInstance;
//(2)当启动模式为SingleTask;
//(3)使用了Intent.FLAG_ACTIVITY_NEW_TASK标签,并且没有使用FLAG_ACTIVITY_MULTIPLE_TASK标签
boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
(mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
|| isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK);
putIntoExistingTask &= mInTask == null && mStartActivity.resultTo == null;
ActivityRecord intentActivity = null;
if (mOptions != null && mOptions.getLaunchTaskId() != -1) {
... ... ...
} else if (putIntoExistingTask) { //putIntoExistingTask为true时的策略
if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) {
// SINGLE_INSTANCE 模式下去寻找,这里目的是findActivityRecord
intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info,
mStartActivity.isActivityTypeHome());
} else if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
... ...
} else {
//这里要区别于singleInstance调用的方法!!!这里目的是findTaskRecord
// Otherwise find the best task to put the activity in.
intentActivity = mSupervisor.findTaskLocked(mStartActivity, mPreferredDisplayId);
}
}
return intentActivity;
}
2.2 小结一下
下图表示的是三种情况下putIntoExistingTask为true
putIntoExistingTask为true时,继续看代码我们还能得出另外一个结论:
mLaunchMode为SingleInstance时,走mSupervisor.findActivityLocked;其他情况下,比如我们的mStartActivity是一个standard模式的Activity,且只加上了FLAG_ACTIVITY_NEW_TASK的flag,会走mSupervisor.findTaskLocked
这里我们可以隐约根据方法名字猜出来出来,被启动者为SinglgeInstance的情况下,我们是寻找相等的ActivityRecord;而其他情况下,我们找的是一个最合适的栈(从注释也可以看出来)。实际上我们猜的很对,对于SingleInstance的情况,源码上也是遍历查找相同的ActivityRecord。但是对于其他情况呢?我们先思考一个问题,什么叫最合适的栈?它需要满足什么样的条件?
2.3 最合适的可复用栈
从mSupervisor.findTaskLocked进入,我们最后追踪到ActivityStack.java
void findTaskLocked(ActivityRecord target, FindTaskResult result) {
... ... ...
// 注意看这里是倒序遍历 mTaskHistory
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
... ... ...
} else if (!isDocument && !taskIsDocument
&& result.r == null && task.rootAffinity != null) {
//检查 是不是 相同的 taskAffinity
if (task.rootAffinity.equals(target.taskAffinity)) {
//当我们找到taskAffinity符合的栈之后,并没有立马break,而是继续去寻找,说明task的index越小,表示更适合
result.r = r;
result.matchedByRootAffinity = true;
}
} else if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Not a match: " + task);
}
}
2.3 小结一下
最合适的栈 满足两个条件
1.Activity的taskAffinity和我们的task的rootAffinity相等
2.不同的task的rootAffinity可能是相等的,倒序遍历找到index最小的,也是最合适的
2.4 reusedActivity的处理
我们接着分析startActivityUnchecked代码
// 三. 利用可以 复用的Activity 或者 复用栈
if (reusedActivity != null) {
... ... ...
// 如果是 SingleInstance 或者 SingleTask 或者 含有 FLAG_ACTIVITY_CLEAR_TOP 标识
//我们可以判断出来 SingleInstance 或者 SingleTask 含有 FLAG_ACTIVITY_CLEAR_TOP 的效果
if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
|| isDocumentLaunchesIntoExisting(mLaunchFlags)
|| isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
//拿 reuseActivity 的栈
final TaskRecord task = reusedActivity.getTask();
// 比如 SingleTask 移除要启动的Activity之前的所有Activity
final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,
mLaunchFlags);
if (reusedActivity.getTask() == null) {
reusedActivity.setTask(task);
}
if (top != null) {
if (top.frontOfTask) {
// Activity aliases may mean we use different intents for the top activity,
// so make sure the task now has the identity of the new intent.
top.getTask().setIntent(mStartActivity);
}
//这里是 SingleInstance 或者 SingleTask ,会执行onNewIntent
deliverNewIntent(top);
}
}
... ... ...
//启动者和被启动者是同一个
if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
// We don't need to start a new activity, and the client said not to do anything
// if that is the case, so this is it! And for paranoia, make sure we have
// correctly resumed the top activity.
resumeTargetStackIfNeeded();
return START_RETURN_INTENT_TO_CALLER;
}
if (reusedActivity != null) {
//这里会去判断几种情况 singleTask singleInstance 和 singleTop
setTaskFromIntentActivity(reusedActivity);
if (!mAddingToTask && mReuseTask == null) {
//singleInstance singleTask 都会走这里
//1.比如要启动的Activity是singleTask,且刚好在reusedActivity的栈内
//2.或者一个singleInstance模式的Activity再次被启动
resumeTargetStackIfNeeded();
if (outActivity != null && outActivity.length > 0) {
outActivity[0] = reusedActivity;
}
return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
}
}
}
继续来看一下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_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TASK
final TaskRecord task = intentActivity.getTask();
//清空task
task.performClearTaskLocked();
mReuseTask = task;
mReuseTask.setIntent(mStartActivity);
} else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
|| isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
//如果是 SingleInstance 或者 SingleTask 走这里,清空栈内要启动的Activity之前的所有Activity们。
ActivityRecord top = intentActivity.getTask().performClearTaskLocked(mStartActivity,
mLaunchFlags);
//如果top == null 继续走,不为null,就结束了这个方法
if (top == null) {
... ... ...
}
} else if (mStartActivity.realActivity.equals(intentActivity.getTask().realActivity)) {
// 判断是否是 SingleTop 模式
// 这种情况如何复现? SingleTop + FLAG_ACTIVITY_NEW_TASK + taskAffinity。
// FLAG_ACTIVITY_NEW_TASK + taskAffinity去指定一个特定存在的栈,且栈顶是我们要启动的singleTop模式的activity
if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
|| LAUNCH_SINGLE_TOP == mLaunchMode)
&& intentActivity.realActivity.equals(mStartActivity.realActivity)) {
if (intentActivity.frontOfTask) {
intentActivity.getTask().setIntent(mStartActivity);
}
deliverNewIntent(intentActivity);
} else if (!intentActivity.getTask().isSameIntentFilter(mStartActivity)) {
... ... ...
}
} else if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
... ... ...
} else if (!intentActivity.getTask().rootWasReset) {
... ... ...
}
}
2.4 小结一下
前提是reusedActivity不为null,看两种典型情况:
1.如果是SingleTask或者是SingleInstance模式的Activity,则执行performClearTaskLocked方法,把要启动的Activity之前的所有Activity都清除掉。
2.reusedActivity的启动模式恰好是SingleTop,且也是我们要启动的Activity,执行 deliverNewIntent。
上述的两种情况能够满足下面的if判断 !mAddingToTask && mReuseTask == null ,然后return结束。
思考一个问题,情况1和2做例子来浮现一下?
2.5 判断SingleTop模式
继续看 startActivityUnchecked 后面的代码,这一部分是针对SingleTop模式的处理。
注意哦,之前我们也遇到了SingleTop模式的处理,就在上面的setTaskFromIntentActivity方法里。这两个有什么区别呢?
区别在这里的SingleTop模式判断的栈是我们当前展示的栈。而setTaskFromIntentActivity里的判断前提条件是在我们的reusedActivity不为空的情况下,对reusedActivity进行的判断,reusedActivity可能并不是当前的栈。
... ... ...
//下面这段英文解释的很好(SingleTop模式),当我们要启动的Actiivty恰好是当前栈顶的Activity,检查是否只需要被启动一次
// If the activity being launched is the same as the one currently at the top, then
// we need to check if it should only be launched once.
final ActivityStack topStack = mSupervisor.mFocusedStack;
final ActivityRecord topFocused = topStack.getTopActivity();
final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
final boolean dontStart = top != null && mStartActivity.resultTo == null
&& top.realActivity.equals(mStartActivity.realActivity)
&& top.userId == mStartActivity.userId
&& top.app != null && top.app.thread != null
&& ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
|| isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK));
// SINGLE_TOP SINGLE_TASK,要启动的Activiy恰好在栈顶
// dontStart为true,表示不会去启动新的Activity,复用栈顶的Activity
if (dontStart) {
// For paranoia, make sure we have correctly resumed the top activity.
topStack.mLastPausedActivity = null;
if (mDoResume) {
// resume Activity
mSupervisor.resumeFocusedStackTopActivityLocked();
}
ActivityOptions.abort(mOptions);
if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
// We don't need to start a new activity, and the client said not to do
// anything if that is the case, so this is it!
return START_RETURN_INTENT_TO_CALLER;
}
deliverNewIntent(top);
// Don't use mStartActivity.task to show the toast. We're not starting a new activity
// but reusing 'top'. Fields in mStartActivity may not be fully initialized.
mSupervisor.handleNonResizableTaskIfNeeded(top.getTask(), preferredWindowingMode,
preferredLaunchDisplayId, topStack);
return START_DELIVERED_TO_TOP;
}
2.5 小结一下
这部分的代码主要处理SingleTop模式的Activity,要启动的Activity是SingleTop模式,且也恰好在当前栈的顶部,执行deliverNewIntent。
思考一个小问题,启动一个SingleTop模式的Activity,然后再次启动一次它,它的生命周期如何变化呢?
答案是 onCreate -> onStart -> onResume -> onPause -> onNewIntent -> onResume。
2.6 栈的复用和新建
继续分析startActivityUnChecked方法的最后一部分,这部分主要是关于栈是否需要新建。
... ... ...
//必要条件是有FLAG_ACTIVITY_NEW_TASK
if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
&& (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
//要建立新栈或者使用已经存在的栈,FLAG_ACTIVITY_NEW_TASK是必要条件
newTask = true;
result = setTaskFromReuseOrCreateNewTask(taskToAffiliate, topStack);
} else if (mSourceRecord != null) {
//把被启动的mStartActivity放在启动者mSourceRecord所在的栈上
result = setTaskFromSourceRecord();
}
... ... ...
setTaskFromReuseOrCreateNewTask 方法
private int setTaskFromReuseOrCreateNewTask(
TaskRecord taskToAffiliate, ActivityStack topStack) {
mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions);
if (mReuseTask == null) {
//新建一个栈
final TaskRecord task = mTargetStack.createTaskRecord(
mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId),
mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
mVoiceInteractor, !mLaunchTaskBehind /* toTop */, mStartActivity, mSourceRecord,
mOptions);
addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
updateBounds(mStartActivity.getTask(), mLaunchParams.mBounds);
} else {
//用旧栈
addOrReparentStartingActivity(mReuseTask, "setTaskFromReuseOrCreateNewTask");
}
... ... ...
2.6 小结一下
我们可以看出,如果需要我们去新建一个栈或者把我们要启动的Activity放在已经存在某个栈中,FLAG_ACTIVITY_NEW_TASK是必要条件。
三. 启动模式流程图
对于第二部分的描述我用一个图来总结一下。
四. 总结
我们先总结一下几个重要的结论:
-
FLAG_ACTIVITY_NEW_TASK是新建栈或者复用栈的必要条件。SingleTask,SingleInstance会为mLaunchFlags自动添加FLAG_ACTIVITY_NEW_TASK。也就是说他们都有存在不使用当前栈的可能。SingleInstance是很好理解的,SingleTask需要注意下,关于SingleTask我会详细说明,不用担心。
-
新建栈或者复用已经存在栈的充要条件是什么?
(1) FLAG_ACTIVITY_NEW_TASK + taskAffinity(taskAffinity必须与当前显示的栈的rootAffinity不相同,taskAffinity默认是包名)。
(2) FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_MULTIPLE_TASK 这是必定会新建一个栈的
-
SingleTask可以理解成FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TOP + (taskAffinity == 该应用包名)。解释一下,FLAG_ACTIVITY_CLEAR_TOP的作用是啥?比如我们要启动的SingleTask模式的Activity已经在栈内,且不在栈的头部,会把之前的Activity全部出栈。
-
SingleInstance比较特殊,首先SingleInstance模式的Activity会被加上FLAG_ACTIVITY_NEW_TASK,这一点和SingleTask一样。特殊的地方其一在于如果启动过了,会去遍历找相等的Activity,查找过程不一样。而不像SingleTask是去找"合适的"栈(2.3最合适的可复用栈),根据taskAffinity来查找。其二在于SingleInstance一个栈只能存放一个Activity,能做到这个的原因是我们在根据taskAffinity找到合适的栈的时候,如果发现是SingleInstance模式Activity的栈,直接忽略。
回答问题
到这里也算是完结了,我们回过头来思考一下最开始提出的几个问题。
-
若两个应用A和B,A中有两个Activity,A1和A2,B中有一个Activity B1,这三个Activity都是SingleTask模式,启动顺序是A1 -> B1 -> A2,然后点击返回按钮,返回到的界面是A1。因为SingleTask模式本身含有FLAG_ACTIVITY_NEW_TASK,这里由于taskAffinity也和当前展示的栈不相同(taskAffinity默认是包名),所以会去找"合适的"栈(2.3最合适的可复用栈)放入。
-
接着前面问题,如果A2是Standard模式 + FlAG_ACTIVITY_NEW_TASK呢? 会回到A1,因为A2和A1在一个栈,类似第一种情况。
-
FLAG_ACTIVITY_NEW_TASK 有什么用?taskAffinity又是啥?这个问题可以看上面的小结。
最后
关于启动模式的问题非常多,这里分析了一些常见的情况。
我们知道SingleInstance模式的Activity在一个应用内是不能当做中间Activity,比如A1 、 A2 、A3 这三个Activity中,A2是SingleInstance模式,A1,A2是Standard模式,如果启动顺序是A1 -> A2 -> A3,然后点击返回键,会返回到A1。假如者三个Activity都是Standard模式,想要实现一样的效果如何做?
对比了下同类型的文章,我的这篇文章没有上来就介绍Standard,SingleTask,SingleInstance,SingleTop模式的基本概念和使用介绍,而是更偏向源码性的研究,建议把每种模式在这段代码里面走一遍,就能从本质上看待启动模式之间的区别,对常说的启动模式概念理解更深刻。