本文试图说明 Activity launchMode 原理,各种常用的 Intent Flag 原理 ,以及launchMode 和 Intent Flag 的区别,相似点等;以及有可能的使用场景(源码基于 7.0 分析)。
SingleTask VS Intent.FLAG_NEW_TASK
Activity 中有四大启动模式,较常用的一种为SingleTask官方注释如下:
"singleTask" can only begin a task. They are always at the root of
the activity stack. Moreover, the device can hold only one instance of
the activity at a time — only one such task.
大致翻译如下:如果一个Activity设置为SingleTask,那么它必须在当前栈的底部,并且在当前栈中,只能有一个该Activity的实例。
上述说法其实是错误的,一个被SingleTask标识的Activity是可以不在栈底的。并且在一个栈中,只能有一个该Activity实例。那么 SingleTask 的原理是什么呢。
不同应用的Activity以及Activity的跳转是由AMS来完成的,当启动一个Activity时,最后都是由 AMS 来完成,最后会调用到 ActivityStarter的startActivityUnchecked()方法,该方法代码如下:
// Note: This method should only be called from {@link startActivity}.
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
ActivityRecord[] outActivity) {
// 1.1 解析 LaunchMode , 设置初始化状态
setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
voiceInteractor);
// 1.2 实现 LaunchMode 到 LaunchFlag 转换操作
computeLaunchingTaskFlags();
...
// 1.3 此处为关键点,寻找可重用的 ActivityRecord实例
ActivityRecord reusedActivity = getReusableIntentActivity();
...
// 1.4 如果 reusedActivity 不为 null ,走相关逻辑
if(reusedActivity != null){
//后续具体分析..
}
...
}
应用在真正启动一个 Activity 之前,会先走上述1.1~1.3的初始化过程,下面分别来解释一下 1.1 ~1.3具体做了哪些工作。
1.1:setInitialState() 初始化工作
在 setInitialState() 过程中,会首先做一些初始化的操作,如记录该 ActivityRecord 的启动模式,activityResult 校验等等,代码如下:
private void setInitialState(ActivityRecord r, ActivityOptions options, TaskRecord inTask,
boolean doResume, int startFlags, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) {
reset();
mStartActivity = r;
mIntent = r.intent;
mOptions = options;
mCallingUid = r.launchedFromUid;
mSourceRecord = sourceRecord;
mVoiceSession = voiceSession;
mVoiceInteractor = voiceInteractor;
mLaunchBounds = getOverrideBounds(r, options, inTask);
// 常见的启动模式的标识,赋值给成员变量
// 标识将要启动的 Activity 的 LaunchMode
mLaunchSingleTop = r.launchMode == LAUNCH_SINGLE_TOP;
mLaunchSingleInstance = r.launchMode == LAUNCH_SINGLE_INSTANCE;
mLaunchSingleTask = r.launchMode == LAUNCH_SINGLE_TASK;
mLaunchFlags = adjustLaunchFlagsToDocumentMode(
r, mLaunchSingleInstance, mLaunchSingleTask, mIntent.getFlags());
mLaunchTaskBehind = r.mLaunchTaskBehind
&& !mLaunchSingleTask && !mLaunchSingleInstance
&& (mLaunchFlags & FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
// 此处也是一个重要方法,与 startActivityForResult() 密切相关
sendNewTaskResultRequestIfNeeded();
...
}
从上述初始化操作可以看出,将目标Activity的Launch Mode 赋值给相关的成员变量,并且在之后有一步 sendNewTaskResultRequestIfNeeded() 的操作,那么这个操作是干什么的呢?该方法代码如下:
private void sendNewTaskResultRequestIfNeeded() {
if (mStartActivity.resultTo != null && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0
&& mStartActivity.resultTo.task.stack != null) {
// For whatever reason this activity is being launched into a new task...
// yet the caller has requested a result back. Well, that is pretty messed up,
// so instead immediately send back a cancel and let the new task continue launched
// as normal without a dependency on its originator.
Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result.");
mStartActivity.resultTo.task.stack.sendActivityResultLocked(-1, mStartActivity.resultTo,
mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED, null);
mStartActivity.resultTo = null;
}
}
从该方法注释可以看出,如果以startActivityForResult() 方式启动(RequestCode > 0),并且Intent Flag 有设置 FLAG_ACTIVITY_NEW_TASK 标识,那么当前 Activity 会立即收到 CANCEL 回调。
上述问题也是一个比较常见的问题,从代码可以看出,如果想要正确的收到 onActivityResult() 回调,那么在启动 Activity 的 Intent 中,一定不能加 FLAG_ACTIVITY_NEW_TASK 标识。
分析完上述 1.1 之后,接着来看 1.2 computeLaunchingTaskFlags() 具体做了什么事情。
1.2 : computeLaunchingTaskFlags()
代码如下:
private void computeLaunchingTaskFlags() {
// If the caller is not coming from another activity, but has given us an explicit task into
// which they would like us to launch the new activity, then let's see about doing that.
// 此处不在本文的分析范围内,如果用户是通过 Application Context 或者
// Service 启动的 目标 Activity ,并且有指定要宿主到哪个 task
// 该判断条件才会成立。
if (mSourceRecord == null && mInTask != null && mInTask.stack != null) {
final Intent baseIntent = mInTask.getBaseIntent();
final ActivityRecord root = mInTask.getRootActivity();
if (baseIntent == null) {
ActivityOptions.abort(mOptions);
throw new IllegalArgumentException("Launching into task without base intent: "
+ mInTask);
}
// If this task is empty, then we are adding the first activity -- it
// determines the root, and must be launching as a NEW_TASK.
if (mLaunchSingleInstance || mLaunchSingleTask) {
if (!baseIntent.getComponent().equals(mStartActivity.intent.getComponent())) {
ActivityOptions.abort(mOptions);
throw new IllegalArgumentException("Trying to launch singleInstance/Task "
+ mStartActivity + " into different task " + mInTask);
}
if (root != null) {
ActivityOptions.abort(mOptions);
throw new IllegalArgumentException("Caller with mInTask " + mInTask
+ " has root " + root + " but target is singleInstance/Task");
}
}
// If task is empty, then adopt the interesting intent launch flags in to the
// activity being started.
if (root == null) {
final int flagsOfInterest = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK
| FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_RETAIN_IN_RECENTS;
mLaunchFlags = (mLaunchFlags & ~flagsOfInterest)
| (baseIntent.getFlags() & flagsOfInterest);
mIntent.setFlags(mLaunchFlags);
mInTask.setIntent(mStartActivity);
mAddingToTask = true;
// If the task is not empty and the caller is asking to start it as the root of
// a new task, then we don't actually want to start this on the task. We will
// bring the task to the front, and possibly give it a new intent.
} else if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
mAddingToTask = false;
} else {
mAddingToTask = true;
}
mReuseTask = mInTask;
} else {
mInTask = null;
// Launch ResolverActivity in the source task, so that it stays in the task bounds
// when in freeform workspace.
// Also put noDisplay activities in the source task. These by itself can be placed
// in any task/stack, however it could launch other activities like ResolverActivity,
// and we want those to stay in the original task.
if ((mStartActivity.isResolverActivity() || mStartActivity.noDisplay) && mSourceRecord != null
&& mSourceRecord.isFreeform()) {
mAddingToTask = true;
}
}
// 重点分析此处 , 如果启动目标 Activity 为 Activity Context ,那么会为 mSourceRecord
// 赋值,如果是从非 Activity 启动,比如 Application Context 等,那么mSourceRecord
// 为 null 。
if (mInTask == null) {
if (mSourceRecord == null) {
// This activity is not being started from another... in this
// case we -always- start a new task.
// 如果是从非 Activity 入口过来,强制加上 FLAG_NEW_TASK 标识。
if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) {
Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
"Intent.FLAG_ACTIVITY_NEW_TASK for: " + mIntent);
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
} else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
// The original activity who is starting us is running as a single
// instance... this new activity it is starting must go on its
// own task.
// 如果 当前 Activity Launch Mode 为 singleInstance,代表不允许目标
// Activity 与宿主 Activity 栈实例共用一个 task 栈,所以也会强制加上
// FLAG_ACTIVITY_NEW_TASK 标识。
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
} else if (mLaunchSingleInstance || mLaunchSingleTask) {
// The activity being started is a single instance... it always
// gets launched into its own task.
// 如果目标 Activity 的 LaunchMode 为 singleTask 或者 singleInstance
// 那么也强制加上 FLAG_ACTIVITY_NEW_TASK 标识。
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
}
}
该方法的说明已经在上述代码中注释的比较清楚,总结一些,可以罗列为以下几点:
- 如果是从非 Activity 入口过来,比如 Application Context 、Service 等 , 强制加上 FLAG_ACTIVITY_NEW_TASK 标识。
- 如果目标 Activity 启动模式为 SingleTask 或者 SingleInstance ,那么也强制加上 FLAG_ACTIVITY_NEW_TASK 标识。
- 如果源 Activity 启动模式为 SingleInstance , 那么肯定不允许目标 Activity 与源 Activity 公用一个 Task 栈,所以也强制加上 FLAG_ACTIVITY_NEW_TASK 标识。
1.3 :寻找可重用的 ActivityRecord 实例
getReusableIntentActivity() 方法代码如下:
/**
* Decide whether the new activity should be inserted into an existing task. Returns null
* if not or an ActivityRecord with the task into which the new activity should be added.
*/
private ActivityRecord getReusableIntentActivity() {
// We may want to try to place the new activity in to an existing task. We always
// do this if the target activity is singleTask or singleInstance; we will also do
// this if NEW_TASK has been requested, and there is not an additional qualifier telling
// us to still place it in a new task: multi task, always doc mode, or being asked to
// launch this as a new task behind the current one.
boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
(mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
|| mLaunchSingleInstance || mLaunchSingleTask;
// If bring to front is requested, and no result is requested and we have not been given
// an explicit task to launch in to, and we can find a task that was started with this
// same component, then instead of launching bring that one to the front.
putIntoExistingTask &= mInTask == null && mStartActivity.resultTo == null;
ActivityRecord intentActivity = null;
if (mOptions != null && mOptions.getLaunchTaskId() != -1) {
final TaskRecord task = mSupervisor.anyTaskForIdLocked(mOptions.getLaunchTaskId());
intentActivity = task != null ? task.getTopActivity() : null;
} else if (putIntoExistingTask) {
if (mLaunchSingleInstance) {
// There can be one and only one instance of single instance activity in the
// history, and it is always in its own unique task, so we do a special search.
intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info, false);
} else if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
// For the launch adjacent case we only want to put the activity in an existing
// task if the activity already exists in the history.
intentActivity = mSupervisor.findActivityLocked(mIntent, mStartActivity.info,
!mLaunchSingleTask);
} else {
// Otherwise find the best task to put the activity in.
intentActivity = mSupervisor.findTaskLocked(mStartActivity);
}
}
return intentActivity;
}
从上述代码注释可以看出,该方法主要作用为判断目标 Activity 是否要插入到一个已经存在的栈中,主要判断依据罗列如下:
- 对于 LaunchMode 为 singleTask 或 singleInstance 的 Activity,强制尝试去寻找是否已经存在一个已经存在的 Task。
- 对于 含有 FLAG_ACTIVITY_NEW_TASK 标识的 Intent,也强制尝试去寻找。
- 若目标 Activity LaunchMode 为 singleInstance ,强制去找是否已经存在相同的 ActivityRecord 实例。
- 若LaunchMode 不为 singleInstance,则强制去寻找已经存在的 Task 实例,特别说明的是,此处寻找是否存在相同的 task 实例是通过判断 taskAffinity 来完成的,具体代码逻辑在 mSupervisor.findTaskLocked(mStartActivity) 方法中,此处不再详细说明。
通过上述方法可以看出,如果一个 Activity 的 LaunchMode 被标识为 singleInstance , 那么它在 AMS 中永远只存在唯一的一份实例。对于 singleTask 或 FLAG_ACTIVITY_NEW_TASK , 都尝试先根据该 Activity 对应的 taskAffinity 去找是否已经存在相关的栈,如果存在,则将目标 Activity 放入该栈中。
从上述1.1 ~ 1.3 的分析,发现 launchMode 为 singleTask 与 Intent FLAG_ACTIVITY_NEW_TASK 并没有什么区别,singleTask 在 1.2 computeLaunchingTaskFlags() 方法中也会强制加上 FLAG_ACTIVITY_NEW_TASK 标识,那么两者的区别到底是什么呢?
1.4 : reusedActivity 不为 null 处理
从1.3 处分析可知,对于 launchMode 为 singleTask 或者 Intent Flag 为 FLAG_ACTIVITY_NEW_TASK 的标识,都会首先尝试去找一个已经存在的栈,此处如果可以找到,则将栈中的一个 ActivityRecord 实例赋值给 reusedActivity ,然后继续走接下来的流程,代码如下:
if (reusedActivity != null) {
...
// 重点分析此处方法
setTaskFromIntentActivity(reusedActivity);
if (!mAddingToTask && mReuseTask == null) {
// We didn't do anything... but it was needed (a.k.a., client don't use that
// intent!) And for paranoia, make sure we have correctly resumed the top activity.
resumeTargetStackIfNeeded();
if (outActivity != null && outActivity.length > 0) {
outActivity[0] = reusedActivity;
}
return START_TASK_TO_FRONT;
}
}
重点分析 setTaskFromIntentActivity(reusedActivity) 方法,其中 reusedActivity 代表 1.3 找到的 ActivityRecord 实例, 该方法代码如下:
private void setTaskFromIntentActivity(ActivityRecord intentActivity) {
if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
// The caller has requested to completely replace any existing task with its new
// activity. Well that should not be too hard...
// 如果 Intent flag 同时设置有 FLAG_ACTIVITY_CLEAR_TASK 与 FLAG_ACTIVITY_NEW_TASK
// 那么会把当前的栈清空,重新开启一个栈
mReuseTask = intentActivity.task;
// 清空栈操作
mReuseTask.performClearTaskLocked();
mReuseTask.setIntent(mStartActivity);
// When we clear the task - focus will be adjusted, which will bring another task
// to top before we launch the activity we need. This will temporary swap their
// mTaskToReturnTo values and we don't want to overwrite them accidentally.
mMovedOtherTask = true;
} else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
|| mLaunchSingleInstance || mLaunchSingleTask) {
// 如果目标 Activity launchMode 为 singleTask或
// 者singleInstance , 或者 Intent flag 含有 FLAG_CLEAR_TOP 标识 ,那么首先
// 去清除 栈里面的元素,直到找到可重用的 ActivityRecord 为止。
// 此处是一个关键点
ActivityRecord top = intentActivity.task.performClearTaskLocked(mStartActivity,
mLaunchFlags);
if (top == null) {
// A special case: we need to start the activity because it is not currently
// running, and the caller has asked to clear the current task to have this
// activity at the top.
mAddingToTask = true;
// Now pretend like this activity is being started by the top of its task, so it
// is put in the right place.
mSourceRecord = intentActivity;
final TaskRecord task = mSourceRecord.task;
if (task != null && task.stack == null) {
// Target stack got cleared when we all activities were removed above.
// Go ahead and reset it.
mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
null /* bounds */, mLaunchFlags, mOptions);
mTargetStack.addTask(task,
!mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
}
}
} else if (mStartActivity.realActivity.equals(intentActivity.task.realActivity)) {
// In this case the top activity on the task is the same as the one being launched,
// so we take that as a request to bring the task to the foreground. If the top
// activity in the task is the root activity, deliver this new intent to it if it
// desires.
if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop)
&& intentActivity.realActivity.equals(mStartActivity.realActivity)) {
ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity,
intentActivity.task);
if (intentActivity.frontOfTask) {
intentActivity.task.setIntent(mStartActivity);
}
intentActivity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
mStartActivity.launchedFromPackage);
} else if (!intentActivity.task.isSameIntentFilter(mStartActivity)) {
// In this case we are launching the root activity of the task, but with a
// different intent. We should start a new instance on top.
mAddingToTask = true;
mSourceRecord = intentActivity;
}
} else if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
// In this case an activity is being launched in to an existing task, without
// resetting that task. This is typically the situation of launching an activity
// from a notification or shortcut. We want to place the new activity on top of the
// current task.
mAddingToTask = true;
mSourceRecord = intentActivity;
} else if (!intentActivity.task.rootWasReset) {
// In this case we are launching into an existing task that has not yet been started
// from its front door. The current task has been brought to the front. Ideally,
// we'd probably like to place this new task at the bottom of its stack, but that's
// a little hard to do with the current organization of the code so for now we'll
// just drop it.
intentActivity.task.setIntent(mStartActivity);
}
}
从上述代码的注释,可以很清楚的得到以下结论:
- 如果 Intent flag 同时设置有 FLAG_ACTIVITY_CLEAR_TASK 与 FLAG_ACTIVITY_NEW_TASK 那么会把当前的栈清空。目标Activity 启动的时候,与栈内之前已经存在的 Activity 不是一个实例。
- 如果目标 Activity launchMode 为 singleTask或者 singleInstance ,那么首先清除栈里面的元素,直到栈顶元素为将要启动的目标 ActivityRecord 实例为止。
- 若Intent Flag 中包含 FLAG_ACTIVITY_CLEAR_TOP , 则也执行清栈操作,但是会把已经存在的 ActivityRecord 首先 finish掉,然后重新启动目标 ActivityRecord 实例。