1.launchMode介绍
android:launchMode共有四种模式可与 Intent 对象中的 Activity 标记(FLAG_ACTIVITY_* 常量)协同工作,以确定在调用 Activity 处理 Intent 时应执行的操作。这些模式是:
- standard
- singleTop
- singleTask
- singleInstance
默认模式是“standard”。
这些模式可分为两大类:
- standard和singleTop 为一类,可多次进行实例化
使用standard或singleTop启动模式的 Activity 可多次进行实例化。实例可归属任何Task,并且可位于 Activity 堆栈中的任何位置。通常,它们会启动到名为 startActivity() 的任务中(除非 Intent 对象包含 FLAG_ACTIVITY_NEW_TASK 指令,在此情况下会选择其他任务 — 请参阅 taskAffinity 属性)。
- singleTask和singleInstance 为另一类。
singleTask和singleInstance 模式Activity 只能启动Task。它们始终位于Activity 堆栈的根位置。此外,设备一次只能保留一个 Activity 实例,即一次只允许一个此类任务。
具体的差别如下图:
standard和singleTop模式
standard和singleTop模式只有一处不同:在singleTop模式如果目标任务的 Activity 堆栈顶部已有一个 Activity 实例,则该实例会(通过调用 onNewIntent())接收新的 Intent,此时不会创建新实例。
在其他情况下(例如,如果“singleTop”Activity 的某个现有实例虽在目标任务内,但未处于堆栈顶部,或者虽然位于堆栈顶部,但不在目标任务中),系统会创建新实例并将其送入堆栈,和standard一样。
singleTask和singleInstance模式
singleTask和singleInstance模式同样只有一处不同:singleTask模式的Activity 允许其他 Activity 成为其Task的一部分。该 Activity 始终位于其任务的根位置,但其他 Activity(必然是“standard”和“singleTop”Activity)可以启动到该任务中。另一方面,singleInstance模式的Activity 不允许其他 Activity 成为其Task的一部分,它是Task中唯一的 Activity。如果它启动另一个 Activity,则系统会将该 Activity 分配给其他任务,就如同 Intent 中包含 FLAG_ACTIVITY_NEW_TASK 一样。
2.launchMode实现原理分析
在android里面的Activity体系结构(3)_ActivityRecord和Activity状态变化分析说明 有介绍,在
ActivityStarter里面startActivityUnchecked对launchMode进行处理
2.1 startActivityUnchecked
核心逻辑在startActivityUnchecked这个里面处理,分2类:
- singleTask和singleInstance模式:处理在getReusableIntentActivity,不会重复创建Activity,然后reusedActivity.getTaskRecord()找到可以复用的Task
- singleInstance模式:使用mRootActivityContainer.findActivity查找,因为在singleInstance模式下,只能存在一个Activity实例
- singleTask模式:使用mRootActivityContainer.findTask找到最合适的Task,因为在singleTask模式下,可能在多个Task中存在Activity实例
- standard和singleTop模式:singleTask和singleInstance模式处理完后,然后处理standard和singleTop模式。调用topStack.topRunningNonDelayedActivityLocked(mNotTop)获取当前top Activity,用作standard和singleTop分别处理
- singleTop模式:如果非空且为mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0,调用deliverNewIntent,直接启动
- standard模式:非以上3种场景,直接创建一个新的Activity
下面2.2、2.3、 2.4 分别介绍几种模式的处理,建议对着源代码一起看,方法很长,下面贴的代码有裁剪
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
ActivityRecord[] outActivity, boolean restrictedBgActivity) {
// 复用Activity
ActivityRecord reusedActivity = getReusableIntentActivity();
// 1.有可复用的Activity
if (reusedActivity != null) {
if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
|| isDocumentLaunchesIntoExisting(mLaunchFlags)
|| isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
// 找到可以复用的Task
final TaskRecord task = reusedActivity.getTaskRecord();
// In this situation we want to remove all activities from the task up to the one
// being started. In most cases this means we are resetting the task to its initial
// state.
final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,
mLaunchFlags);
// The above code can remove {@code reusedActivity} from the task, leading to the
// the {@code ActivityRecord} removing its reference to the {@code TaskRecord}. The
// task reference is needed in the call below to
// {@link setTargetStackAndMoveToFrontIfNeeded}.
if (reusedActivity.getTaskRecord() == null) {
reusedActivity.setTask(task);
}
// 要启动的Activity位于栈顶,直接调用deliverNewIntent,使用onNewIntent启动
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.getTaskRecord().setIntent(mStartActivity);
}
deliverNewIntent(top);
}
}
mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded
(false /* forceSend */, reusedActivity);
reusedActivity = setTargetStackAndMoveToFrontIfNeeded(reusedActivity);
final ActivityRecord outResult =
outActivity != null && outActivity.length > 0 ? outActivity[0] : null;
// When there is a reused activity and the current result is a trampoline activity,
// set the reused activity as the result.
if (outResult != null && (outResult.finishing || outResult.noDisplay)) {
outActivity[0] = 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) {
// The reusedActivity could be finishing, for example of starting an
// activity with FLAG_ACTIVITY_CLEAR_TOP flag. In that case, return the
// top running activity in the task instead.
outActivity[0] = reusedActivity.finishing
? reusedActivity.getTaskRecord().getTopActivity() : reusedActivity;
}
return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
}
}
}
// 2. reusedActivity为空
// 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 = mRootActivityContainer.getTopDisplayFocusedStack();
final ActivityRecord topFocused = topStack.getTopActivity();
final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
final boolean dontStart = top != null && mStartActivity.resultTo == null
&& top.mActivityComponent.equals(mStartActivity.mActivityComponent)
&& top.mUserId == mStartActivity.mUserId
&& top.attachedToProcess()
&& ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
|| isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK))
// This allows home activity to automatically launch on secondary display when
// display added, if home was the top activity on default display, instead of
// sending new intent to the home activity on default display.
&& (!top.isActivityTypeHome() || top.getDisplayId() == mPreferredDisplayId);
if (dontStart) {
// For paranoia, make sure we have correctly resumed the top activity.
topStack.mLastPausedActivity = null;
if (mDoResume) {
mRootActivityContainer.resumeFocusedStacksTopActivities();
}
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.getTaskRecord(), preferredWindowingMode,
mPreferredDisplayId, topStack);
return START_DELIVERED_TO_TOP;
}
boolean newTask = false;
final TaskRecord taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null)
? mSourceRecord.getTaskRecord() : null;
// Should this be considered a new task?
int result = START_SUCCESS;
if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
&& (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
newTask = true;
result = setTaskFromReuseOrCreateNewTask(taskToAffiliate);
} else if (mSourceRecord != null) {
result = setTaskFromSourceRecord();
} else if (mInTask != null) {
result = setTaskFromInTask();
} else {
// This not being started from an existing activity, and not part of a new task...
// just put it in the top task, though these days this case should never happen.
result = setTaskToCurrentTopOrCreateNewTask();
}
if (result != START_SUCCESS) {
return result;
}
mService.mUgmInternal.grantUriPermissionFromIntent(mCallingUid, mStartActivity.packageName,
mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.mUserId);
mService.getPackageManagerInternalLocked().grantEphemeralAccess(
mStartActivity.mUserId, mIntent, UserHandle.getAppId(mStartActivity.appInfo.uid),
UserHandle.getAppId(mCallingUid));
if (newTask) {
EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, mStartActivity.mUserId,
mStartActivity.getTaskRecord().taskId);
}
ActivityStack.logStartActivity(
EventLogTags.AM_CREATE_ACTIVITY, mStartActivity, mStartActivity.getTaskRecord());
mTargetStack.mLastPausedActivity = null;
mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded(
false /* forceSend */, mStartActivity);
mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask, mKeepCurTransition,
mOptions);
if (mDoResume) {
final ActivityRecord topTaskActivity =
mStartActivity.getTaskRecord().topRunningActivityLocked();
if (!mTargetStack.isFocusable()
|| (topTaskActivity != null && topTaskActivity.mTaskOverlay
&& mStartActivity != topTaskActivity)) {
// If the activity is not focusable, we can't resume it, but still would like to
// make sure it becomes visible as it starts (this will also trigger entry
// animation). An example of this are PIP activities.
// Also, we don't want to resume activities in a task that currently has an overlay
// as the starting activity just needs to be in the visible paused state until the
// over is removed.
mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS);
// Go ahead and tell window manager to execute app transition for this activity
// since the app transition will not be triggered through the resume channel.
mTargetStack.getDisplay().mDisplayContent.executeAppTransition();
} else {
// If the target stack was not previously focusable (previous top running activity
// on that stack was not visible) then any prior calls to move the stack to the
// will not update the focused stack. If starting the new activity now allows the
// task stack to be focusable, then ensure that we now update the focused stack
// accordingly.
if (mTargetStack.isFocusable()
&& !mRootActivityContainer.isTopDisplayFocusedStack(mTargetStack)) {
mTargetStack.moveToFront("startActivityUnchecked");
}
mRootActivityContainer.resumeFocusedStacksTopActivities(
mTargetStack, mStartActivity, mOptions);
}
} else if (mStartActivity != null) {
mSupervisor.mRecentTasks.add(mStartActivity.getTaskRecord());
}
mRootActivityContainer.updateUserStack(mStartActivity.mUserId, mTargetStack);
mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTaskRecord(),
preferredWindowingMode, mPreferredDisplayId, mTargetStack);
return START_SUCCESS;
}
2.2 getReusableIntentActivity 处理singleTask和singleInstance模式
在此方法里面,使用isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)判断 ,处理singleTask和singleInstance模式
方法里面的注释写的很清楚,不用单独作说明:
- 针对singleInstance模式,使用mRootActivityContainer.findActivity,因为在此模式下,只会存在一个Activity实例,通过Activity名称直接在ActivityStack里面查找
- 针对singleTask模式,mRootActivityContainer.findTask,因为在此模式下,可能在多个TaskRecord里面存在,所以需要找到合适的TaskRecord
从实现上来看,也体现了在singleTask和singleInstance模式里面的唯一差别
/**
* 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)
|| isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK);
// 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 = mRootActivityContainer.anyTaskForId(mOptions.getLaunchTaskId());
intentActivity = task != null ? task.getTopActivity() : null;
} else if (putIntoExistingTask) {
if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) {
// 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 = mRootActivityContainer.findActivity(mIntent, mStartActivity.info,
mStartActivity.isActivityTypeHome());
} 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 = mRootActivityContainer.findActivity(mIntent, mStartActivity.info,
!(LAUNCH_SINGLE_TASK == mLaunchMode));
} else {
// Otherwise find the best task to put the activity in.
intentActivity =
mRootActivityContainer.findTask(mStartActivity, mPreferredDisplayId);
}
}
if (intentActivity != null
&& (mStartActivity.isActivityTypeHome() || intentActivity.isActivityTypeHome())
&& intentActivity.getDisplayId() != mPreferredDisplayId) {
// Do not reuse home activity on other displays.
intentActivity = null;
}
return intentActivity;
}
在找到可以复用的Activity的时候,找到对应的TaskRecord,清除目标Task里面位于要启动的Activity之上的Activity
// 找到可以复用的Task
final TaskRecord task = reusedActivity.getTaskRecord();
// 清除目标Task里面位于要启动的Activity之上的Activity
final ActivityRecord top = task.performClearTaskForReuseLocked(mStartActivity,mLaunchFlags);
进行上面的操作后,目标Activity位于TaskRecord的栈顶,直接调用deliverNewIntent,调用top Activity的onNewIntent
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.getTaskRecord().setIntent(mStartActivity);
}
deliverNewIntent(top);
}
2.3 topRunningNonDelayedActivityLocked 处理singleTop模式
getReusableIntentActivity方法返回非空时,把singleTask和singleInstance模式处理完。返回空时,先获取当前FocusStack,然后获取top Activity
在topRunningNonDelayedActivityLocked方法里面,遍历ActivityStack里面的所有TaskRecord
ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
final ArrayList<ActivityRecord> activities = task.mActivities;
for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
ActivityRecord r = activities.get(activityNdx);
if (!r.finishing && !r.delayedResume && r != notTop && r.okToShowLocked()) {
return r;
}
}
}
return null;
}
如果当前要启动的Activity和当前Top Activity是一样的,通过dontStart变量来确认是否需要只启动一次
final boolean dontStart = top != null && mStartActivity.resultTo == null
&& top.mActivityComponent.equals(mStartActivity.mActivityComponent)
&& top.mUserId == mStartActivity.mUserId
&& top.attachedToProcess()
&& ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
|| isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK))
// This allows home activity to automatically launch on secondary display when
// display added, if home was the top activity on default display, instead of
// sending new intent to the home activity on default display.
&& (!top.isActivityTypeHome() || top.getDisplayId() == mPreferredDisplayId);
当启动模式为singleTop模式时,mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP,dontStart为true
if (dontStart) {
// For paranoia, make sure we have correctly resumed the top activity.
topStack.mLastPausedActivity = null;
if (mDoResume) {
mRootActivityContainer.resumeFocusedStacksTopActivities();
}
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.getTaskRecord(), preferredWindowingMode,
mPreferredDisplayId, topStack);
return START_DELIVERED_TO_TOP;
}
如果启动模式为singleTop模式,且当前top.mActivityComponent.equals(mStartActivity.mActivityComponent) 即top Activity和要启动的Activity是一致的时候,调用deliverNewIntent,调用onNewIntent方法,不会再创建一个新的Activity
2.4 处理stander模式
前面3种模式处理完后,最后处理stander模式。判断是否需要创建一个newTask,来保存启动的Activity
// Should this be considered a new task?
int result = START_SUCCESS;
if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
&& (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
newTask = true;
result = setTaskFromReuseOrCreateNewTask(taskToAffiliate);
}
后面返回START_SUCCESS后,即startActivity返回,后面会再走
android里面的Activity体系结构(3)_ActivityRecord和Activity状态变化分析说明的 4.2 第2阶段:DeskClock应用进入创建并启动
创建一个新的Activity