前言
本文是从Activity 启动流程(二)—— AMS 处理阶段中拆出的 扩展篇文章。
代码基于 android-14.0.0_r9。
1. 一句话先说结论
startActivityInner() 核心流程包括:
- 初始化启动状态。
- 统一修正最终 Flag。
- 记录启动前现场。
- 查找可复用任务。
- 决定最终目标
Task。 - 决定复用旧页面还是创建新实例。
- 完成入栈与前台切换。
- 移交给
resume恢复链。
2. 方法主框架
// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord, ...) {
// 1. 初始化启动状态
setInitialState(...);
// 2. 统一修正最终 Flag
computeLaunchingTaskFlags();
mIntent.setFlags(mLaunchFlags);
// 3. 记录启动前现场
boolean dreamStopping = false;
for (ActivityRecord stoppingActivity : mSupervisor.mStoppingActivities) {
if (stoppingActivity.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_DREAM) {
dreamStopping = true;
break;
}
}
final Task prevTopRootTask = mPreferredTaskDisplayArea.getFocusedRootTask();
final Task prevTopTask = prevTopRootTask != null ? prevTopRootTask.getTopLeafTask() : null;
// 4. 查找可复用任务
final Task reusedTask = getReusableTask();
// 5. 决定最终目标 `Task`
final Task targetTask = reusedTask != null ? reusedTask : computeTargetTask();
final boolean newTask = targetTask == null;
mTargetTask = targetTask;
computeLaunchParams(r, sourceRecord, targetTask);
int startResult = isAllowedToStart(r, newTask, targetTask);
if (startResult != START_SUCCESS) {
return startResult;
}
// 6. 决定复用旧页面还是创建新实例
final ActivityRecord targetTaskTop =
newTask ? null : targetTask.getTopNonFinishingActivity();
if (targetTaskTop != null) {
startResult = recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants, balVerdict);
if (startResult != START_SUCCESS) {
return startResult;
}
} else {
mAddingToTask = true;
}
// 7. 完成入栈与前台切换
if (mTargetRootTask == null) {
mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, targetTask, mOptions);
}
if (newTask) {
setNewTask(...);
} else if (mAddingToTask) {
addOrReparentStartingActivity(targetTask, "adding to task");
}
mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask);
// 8. 移交给 `resume` 恢复链
final Task startedTask = mStartActivity.getTask();
final boolean isTaskSwitch = startedTask != prevTopTask;
mTargetRootTask.startActivityLocked(
mStartActivity, prevTopRootTask, newTask, isTaskSwitch, mOptions, sourceRecord);
mRootWindowContainer.resumeFocusedTasksTopActivities(
mTargetRootTask, mStartActivity, mOptions, mTransientLaunch);
return START_SUCCESS;
}
用户在桌面点击图标后真正感知到的:
- 是回到旧页面。
- 还是新开一个页面。
- 是热启动拉前台。
- 还是冷启动新建任务。
底层差异,绝大部分都集中在 startActivityInner()。executeRequest() 创建 ActivityRecord 之后,系统已经有了一个“可调度的服务端 Activity 模型”。但它还没有回答最关键的问题:这个 ActivityRecord 到底该进哪个任务结构?
3. 阶段一:初始化启动状态
初始化启动状态,重置内部状态机变量,将传入的参数保存为类的成员变量,为后续复杂的逻辑计算做准备。
源码里最关键的初始化如下:
// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
private void setInitialState(ActivityRecord r, ActivityOptions options, Task inTask,
TaskFragment inTaskFragment, int startFlags,
ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession,
IVoiceInteractor voiceInteractor, @BalCode int balCode, int realCallingUid) {
reset(false /* clearRequest */);
mStartActivity = r;
mIntent = r.intent;
mOptions = options;
mCallingUid = r.launchedFromUid;
mRealCallingUid = realCallingUid;
mSourceRecord = sourceRecord;
mSourceRootTask = mSourceRecord != null ? mSourceRecord.getRootTask() : null;
mBalCode = balCode;
mLaunchMode = r.launchMode;
mLaunchFlags = adjustLaunchFlagsToDocumentMode(
r, LAUNCH_SINGLE_INSTANCE == mLaunchMode,
LAUNCH_SINGLE_TASK == mLaunchMode, mIntent.getFlags());
...
}
这一步的本质不是“开始启动 Activity”,而是:
- 先把这次启动要用到的上下文全部装进
ActivityStarter。
后面所有判断都会围绕这些字段展开:
mStartActivitymIntentmSourceRecordmLaunchModemLaunchFlagsmCallingUidmRealCallingUid
所以 setInitialState(...) 的价值可以概括成一句话:它为后续所有 Task 复用、前台切换和 Flag 裁决建立统一上下文。
4. 阶段二:统一修正最终 Flag
先看 Launcher 侧一开始给出的启动 Intent:
// packages/apps/Launcher3/src/com/android/launcher3/model/data/AppInfo.java
public static Intent makeLaunchIntent(ComponentName cn) {
return new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER)
.setComponent(cn)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
}
// packages/apps/Launcher3/src/com/android/launcher3/views/ActivityContext.java
default RunnableList startActivitySafely(View v, Intent intent, @Nullable ItemInfo item) {
...
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
...
context.startActivity(intent, optsBundle);
}
再看 ActivityStarter 如何修正这些 Flag:
// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
private void computeLaunchingTaskFlags() {
...
if (mInTask == null) {
if (mSourceRecord == null) {
if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) {
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
} else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
} else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
}
if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
&& ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 || mSourceRecord == null)) {
mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT;
}
}
这里必须把几个来源分清楚:
computeLaunchingTaskFlags()会综合目标Activity的launchMode、来源mSourceRecord、是否指定了mInTask、以及当前已有的Intent flag,重新计算最终要生效的mLaunchFlags。- 对
singleTask、singleInstance、singleInstancePerTask这类模式,系统可能主动补上FLAG_ACTIVITY_NEW_TASK。
桌面点击 icon 的典型最终 flag 是什么?
- 对绝大多数普通桌面图标启动主页面场景,初始
Intent通常就是:FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED。 - 进入
computeLaunchingTaskFlags()之后,这组 flag 在大多数情况下会被保留下来。 - 然后执行
mIntent.setFlags(mLaunchFlags),把系统最终确认过的 flag 回写到真正要分发的Intent中。
mIntent.setFlags(mLaunchFlags) 的作用也不能忽略:
- 前一步只是在
ActivityStarter内部把最终Flag算出来。 - 这一步是把修正后的
Flag回写到真正要分发的Intent对象里。 - 后续如果出现
onNewIntent()、Task.setIntent()、日志记录、权限授予,都必须基于“修正后的Intent”。
5. 阶段三:记录启动前现场
先看 Dream 检查:
boolean dreamStopping = false;
for (ActivityRecord stoppingActivity : mSupervisor.mStoppingActivities) {
if (stoppingActivity.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_DREAM) {
dreamStopping = true;
break;
}
}
Dream 是 Android 的系统屏保 / 待机展示界面,对应特殊的 ACTIVITY_TYPE_DREAM。ATMS 里通过 mActiveDreamComponent / isDreaming() 追踪它是否处于活跃状态。之所以这里要额外判断 dreamStopping,是因为 Dream 通常位于普通应用窗口之上;如果它还没完全退场,后续是否允许把新 Task 直接提到最前面、是否要改成 launchTaskBehind,就必须参考这个状态,避免普通应用和 Dream 窗口发生错误抢焦点或闪烁。
再看启动前现场记录:
final Task prevTopRootTask = mPreferredTaskDisplayArea.getFocusedRootTask();
final Task prevTopTask = prevTopRootTask != null ? prevTopRootTask.getTopLeafTask() : null;
mPreferredTaskDisplayArea.getFocusedRootTask():取当前显示区域里真正有焦点的RootTask。- Android 14 已经是分层容器模型,
DisplayArea -> RootTask -> Leaf Task -> ActivityRecord,焦点是分显示区域管理的。 getTopLeafTask():继续拿到这个RootTask里最顶层、真正直接装ActivityRecord的叶子Task。
这两行保存的是“启动前现场”,用途主要有两个:
- 后面判断这次点击桌面图标是不是发生了真正的
Task切换。 - 如果是从一个 App 切到另一个 App,动画、焦点、可见性处理都会和“同一个
Task内跳页面”不一样。
6. 阶段四:查找可复用任务
final Task reusedTask = getReusableTask();
它的核心问题是:桌面点图标时,系统到底是新建一个任务,还是把历史任务直接拉起来。
看下这个方法的核心源码:
// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
private Task getReusableTask() {
if (mOptions != null && mOptions.getLaunchTaskId() != INVALID_TASK_ID) {
Task launchTask = mRootWindowContainer.anyTaskForId(mOptions.getLaunchTaskId());
if (launchTask != null && launchTask.isLeafTask()) {
return launchTask;
}
return null;
}
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 (putIntoExistingTask) {
if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) {
intentActivity = mRootWindowContainer.findActivity(mIntent, mStartActivity.info,
mStartActivity.isActivityTypeHome());
} else if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
intentActivity = mRootWindowContainer.findActivity(mIntent, mStartActivity.info,
!(LAUNCH_SINGLE_TASK == mLaunchMode));
} else {
intentActivity =
mRootWindowContainer.findTask(mStartActivity, mPreferredTaskDisplayArea);
}
}
return intentActivity != null ? intentActivity.getTask() : null;
}
可以按这几层来理解:
- 如果调用方显式指定了
launchTaskId,系统优先尝试复用那个特定Task。 - 否则,只有在满足“允许复用
Task”的前提下,系统才会继续往下找历史Task。这个前提通常是:带FLAG_ACTIVITY_NEW_TASK且没带FLAG_ACTIVITY_MULTIPLE_TASK,或者目标本身是singleTask/singleInstance。 - 但同时还要满足:
mInTask == null && mStartActivity.resultTo == null。也就是说,不能一边要求放进指定Task,或者一边还要走结果回调链,一边又想随意复用历史Task。
这里的三个分支,代表三种完全不同的“复用查找策略”:
LAUNCH_SINGLE_INSTANCE走findActivity(...):因为这种模式要求全局独占,系统直接按Activity实例去找,而不是先按Task去找。FLAG_ACTIVITY_LAUNCH_ADJACENT也走findActivity(...):因为分屏相邻启动更关心“这个Activity是否已经在某个相邻位置存在”,匹配粒度也偏向Activity。- 普通桌面点击
icon的主路径,通常会走最后这个else,也就是mRootWindowContainer.findTask(mStartActivity, mPreferredTaskDisplayArea)。这才是最常见的“按历史Task复用”逻辑。
也就是说,Launcher 点击图标时你看到的 findTask(...),本质上是在回答一个问题: 系统里是否已经存在一个属于这个应用、且足够匹配当前启动语义的旧 Task,可以直接拿来复用。
findTask(...) 的核心原理可以简述为:系统会先在当前首选的 TaskDisplayArea 里查找是否存在可复用的历史 Task,如果本区域没有理想结果,再去其它显示区域兜底;匹配时也不是简单按包名搜索,而是先过滤掉 activityType 不兼容、跨用户、语音会话、正在结束等不合法任务,然后优先按目标组件和文档 data 做精确匹配,只有精确匹配失败时,才退回到 taskAffinity 级别做候选匹配。也就是说,findTask() 本质上是在判断“系统里是否已经有一个足够符合当前启动语义的旧 Task 可以直接复用”,所以桌面点击 icon 时到底是回到上次页面,还是新建一个新任务,关键就取决于这里最终有没有找到匹配成功的历史 Task。
getReusableTask() 内部会结合以下信息查找历史 Task:
- 目标组件。
launchMode。taskAffinity。- 启动
Flag,例如NEW_TASK、CLEAR_TOP。 - 目标显示区域 / 窗口模式。
对 Launcher 点击图标来说,常见分支是:
- 应用之前没有任何历史任务:
reusedTask == null。 - 应用之前已有后台任务,且满足复用条件:
reusedTask != null。
把它代回到“桌面点击 icon”场景里,就很容易理解了:
- 如果系统里根本没有这个应用的历史
Task,或者历史Task不满足用户 / 组件 / 文档 / affinity / 显示区域等匹配条件,那么findTask()返回null,后面就只能新建Task,表现为冷启动。 - 如果系统里已经有一个历史
Task,且它的根Activity、documentData或taskAffinity命中了上面的规则,那么findTask()就会返回对应的ActivityRecord,getReusableTask()再取出它所在的Task,后面走任务复用逻辑。
所以用户感知上的“有时点图标回到上次页面,有时重新打开一个新页面”,底层差异并不在 Launcher,而在 findTask() 这一层到底有没有找到一个 足够匹配当前启动语义 的旧 Task。
7. 阶段五:决定最终目标 Task
下面这三行把“候选 Task”收束成最终结果:
final Task targetTask = reusedTask != null ? reusedTask : computeTargetTask();
final boolean newTask = targetTask == null;
mTargetTask = targetTask;
逐行解释:
reusedTask != null ? reusedTask : computeTargetTask():如果已经命中历史可复用Task,优先直接用它;否则再根据来源、inTask、NEW_TASK等规则推导目标Task。newTask = targetTask == null:只要目标Task还是null,就意味着后面必须调用setNewTask()真正创建一个新Task。mTargetTask = targetTask:把最终决策缓存起来,后续moveToFront、动画、权限、日志都依赖这个字段。
这里还有一个容易混淆的点:
reusedTask代表“历史复用命中结果”。targetTask代表“本次启动最终要落进去的任务”。- 二者很多时候相同,但语义不同,后续
recycleTask()会用到这个差异。
不过,“决定最终目标 Task” 还没有结束。因为系统还要继续确认两个问题:
- 这个目标应该落到哪个显示区域、窗口模式和边界。
- 当前调用者是否真的被允许在这里启动它。
computeTargetTask() 的核心源码如下:
// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
private Task computeTargetTask() {
if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
&& (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
return null;
} else if (mSourceRecord != null) {
return mSourceRecord.getTask();
} else if (mInTask != null) {
if (!mInTask.isAttached()) {
getOrCreateRootTask(mStartActivity, mLaunchFlags, mInTask, mOptions);
}
return mInTask;
} else {
final Task rootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, null /* task */,
mOptions);
final ActivityRecord top = rootTask.getTopNonFinishingActivity();
if (top != null) {
return top.getTask();
} else {
rootTask.removeIfPossible("computeTargetTask");
}
}
return null;
}
它的含义是:
- 命中
NEW_TASK、没有resultTo、没有inTask、也不是addToTask时,直接返回null。这不表示这里已经创建了新Task,而是表示后续应该走新建Task路径。桌面点击icon最常见就是这一支。 - 有
mSourceRecord时,默认沿用来源Activity所在的Task。这更像应用内跳转场景。 - 有
mInTask时,说明调用方显式指定了目标Task;如果它还没 attach,系统会先挂到合适的RootTask,但最终返回的仍然是这个叶子Task本身。 - 其他情况会先找一个合适的
RootTask容器,再从顶部非 finishingActivity反推出目标叶子Task;如果容器里没有有效Activity,就清掉并返回null。
7.1 计算启动参数与准入校验
在“决定最终目标 Task”之后,还要补两步,才能确认这次决策真的可落地:
-
先算“应该启动到哪里”。
-
再判断“允不允许启动”。
-
computeLaunchParams(...)负责计算启动位置和窗口参数,例如目标TaskDisplayArea、windowingMode、bounds等。普通桌面点击主页面启动,多数还是默认全屏显示区。 -
isAllowedToStart(...)是启动前总闸门,只要权限、安全策略、后台启动限制或显示策略中任一条件不满足,就会直接返回失败,不再继续挂栈。 -
对
Launcher点击图标来说,最常见的校验点就是:包名是否合法、后台启动限制BAL是否允许、Lock Task模式是否冲突,以及目标显示区域策略是否允许启动。 -
如果这里返回失败,
startActivityInner()就会直接结束;如果原来带有resultTo,系统还会回一个RESULT_CANCELED给调用方。
8. 阶段六:决定复用旧页面还是创建新实例
从这一段开始,代码正式进入“落栈与可见性阶段”。
8.1 目标 Task 非空时的保护与预处理
// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
if (targetTask != null) {
if (targetTask.getTreeWeight() > MAX_TASK_WEIGHT_FOR_ADDING_ACTIVITY) {
targetTask.removeImmediately("bulky-task");
return START_ABORTED;
}
if (!mAvoidMoveToFront && (mService.mHomeProcess == null
|| mService.mHomeProcess.mUid != realCallingUid)
&& r.mTransitionController.isTransientHide(targetTask)) {
mAvoidMoveToFront = true;
}
mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask());
}
如果 targetTask != null,先做几件事:
targetTask.getTreeWeight() > MAX_TASK_WEIGHT_FOR_ADDING_ACTIVITY):判断这个任务是不是已经过度膨胀。- 一旦超过上限,直接
removeImmediately("bulky-task")然后START_ABORTED。 - 这是典型的系统保护策略,防止单个任务挂太多
Activity/Window导致窗口树过重。
如果当前目标 Task 正处于瞬态隐藏过渡中,并且本次启动又不适合主动抢前台,系统就先把 mAvoidMoveToFront 设为 true,避免打断当前转场;同时记录目标 RootTask 上方原本的层级关系,供后续瞬态启动动画收集和层级恢复使用。
8.2 获取目标栈顶 targetTaskTop
final ActivityRecord targetTaskTop = newTask
? null : targetTask.getTopNonFinishingActivity();
- 如果本来就要新建
Task,那自然没有栈顶可复用页面,直接null。 - 如果要复用已有任务,就去拿这个任务里最顶层、且没有处于 finishing 状态的
ActivityRecord。
这里为什么特意强调 non-finishing?
- 因为有些页面虽然还在栈里,但已经进入 finishing 流程,不能再拿来执行
onNewIntent()或继续作为稳定的复用锚点。
8.3 目标 Task 非空且栈顶也有效:走 recycleTask()
// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
if (targetTaskTop != null) {
if (LAUNCH_SINGLE_INSTANCE == mLaunchMode && mSourceRecord != null
&& targetTask == mSourceRecord.getTask()) {
final ActivityRecord activity = mRootWindowContainer.findActivity(mIntent,
mStartActivity.info, false);
if (activity != null && activity.getTask() != targetTask) {
activity.destroyIfPossible("Removes redundant singleInstance");
}
}
recordTransientLaunchIfNeeded(targetTaskTop);
startResult =
recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants, balVerdict);
if (startResult != START_SUCCESS) {
return startResult;
}
}
如果 targetTaskTop != null,说明命中了“已有任务且栈内确实有活页面”的复用路径。
这段逻辑的含义是:
- 如果目标是
singleInstance,它在全系统理论上只能存在一个实例。 - 且当前又打算把它启动到
sourceRecord所在任务里。 - 那么系统就要全局搜索是否还有另一个同组件实例。
- 如果找到了,而且不在当前目标
Task中,就把那个冗余实例销毁。
然后执行:
recordTransientLaunchIfNeeded(targetTaskTop):如果这是一次瞬态启动,先把过渡信息记录起来。startResult = recycleTask(...):把复用逻辑一次性做完。
recycleTask() 这一段最好直接结合源码看,它的核心不是“简单复用旧 Task”,而是把“复用之后该怎么处理”一次性裁决完:
// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
int recycleTask(Task targetTask, ActivityRecord targetTaskTop, Task reusedTask,
NeededUriGrants intentGrants, BalVerdict balVerdict) {
if (targetTask.mUserId != mStartActivity.mUserId) {
mTargetRootTask = targetTask.getRootTask();
mAddingToTask = true;
return START_SUCCESS;
}
if (reusedTask != null) {
if (targetTask.intent == null) {
targetTask.setIntent(mStartActivity);
} else {
final boolean taskOnHome =
(mStartActivity.intent.getFlags() & FLAG_ACTIVITY_TASK_ON_HOME) != 0;
if (taskOnHome) {
targetTask.intent.addFlags(FLAG_ACTIVITY_TASK_ON_HOME);
} else {
targetTask.intent.removeFlags(FLAG_ACTIVITY_TASK_ON_HOME);
}
}
}
mRootWindowContainer.startPowerModeLaunchIfNeeded(false /* forceSend */, targetTaskTop);
setTargetRootTaskIfNeeded(targetTaskTop, balVerdict);
if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
...
resumeTargetRootTaskIfNeeded();
return START_RETURN_INTENT_TO_CALLER;
}
complyActivityFlags(targetTask,
reusedTask != null ? reusedTask.getTopNonFinishingActivity() : null, intentGrants);
if (mAddingToTask) {
mSupervisor.getBackgroundActivityLaunchController().clearTopIfNeeded(...);
return START_SUCCESS;
}
targetTaskTop = targetTaskTop.finishing
? targetTask.getTopNonFinishingActivity()
: targetTaskTop;
if (mMovedToFront) {
targetTaskTop.showStartingWindow(true /* taskSwitch */);
} else if (mDoResume) {
mTargetRootTask.moveToFront("intentActivityFound");
}
resumeTargetRootTaskIfNeeded();
...
return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;
}
按执行顺序看,它主要做 6 件事:
- 先做跨用户兜底:如果目标
Task属于别的用户,就不走“复用旧页面”语义,而是改成mAddingToTask = true,后续按“往现有Task里加新Activity”处理。 - 再修正被复用
Task的基础信息:如果这个Task之前没有base intent,现在补上;如果这次启动带了FLAG_ACTIVITY_TASK_ON_HOME,也要同步到Task自己的intent上。 - 然后调用
setTargetRootTaskIfNeeded(...):这一步决定旧Task要不要真的被拉到前台,也就是后面FLAG_ACTIVITY_BROUGHT_TO_FRONT的来源。 - 接着处理
START_FLAG_ONLY_IF_NEEDED:如果调用方明确要求“没必要就别重新启动”,而当前旧Task又已经能满足需求,就直接恢复目标RootTask并返回,不再继续创建实例。 - 然后进入
complyActivityFlags(...):这里才是真正落地CLEAR_TOP、SINGLE_TOP、NEW_TASK等Flag语义的地方,它会决定是清栈、投递onNewIntent(),还是需要继续新增实例。 - 最后根据结果收口:如果前面已经把旧
Task移到前台,就返回START_TASK_TO_FRONT;如果只是把Intent投递给了栈顶旧实例,就返回START_DELIVERED_TO_TOP;只有仍然需要新增Activity时,才返回START_SUCCESS让外层继续往下走。
FLAG_ACTIVITY_BROUGHT_TO_FRONT 只会在“复用旧 Task 后,准备把它重新带回前台”时追加。调用链是:
// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
startActivityInner(...)
-> recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants, balVerdict)
-> setTargetRootTaskIfNeeded(targetTaskTop, balVerdict)
真正加这个 Flag 的代码在 setTargetRootTaskIfNeeded() 里:
// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
final boolean differentTopTask;
...
if (differentTopTask && !mAvoidMoveToFront) {
mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
}
这段代码要结合上下文理解:
setTargetRootTaskIfNeeded()只会在recycleTask()里被调用。recycleTask()又只会出现在“命中了已有Task且这个Task里存在可复用栈顶Activity”的分支。- 所以
FLAG_ACTIVITY_BROUGHT_TO_FRONT天生就是 热启动 / 任务复用分支 的标记,不属于冷启动新建Task的基础Flag。
if (differentTopTask && !mAvoidMoveToFront) 里的两个条件分别在判断什么?
differentTopTask:当前前台焦点Task和即将复用拉起的旧Task是否不是同一个Task。不是同一个,才叫“带到前台”。!mAvoidMoveToFront:当前流程没有因为瞬态转场、锁屏遮挡、特殊动画等原因禁止moveToFront。
满足这两个条件时,系统才会补:
FLAG_ACTIVITY_BROUGHT_TO_FRONT。
它表达的语义也就很明确了:
- 这次显示目标应用,不是因为新建了一个
Activity实例。 - 而是因为系统把一个已经存在的旧
Task重新拉回了用户面前。
所以桌面点击 icon 时,Flag 的理解应该分两层:
- 基础启动语义:
FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED。 - 复用旧
Task并真正拉前台时:再额外追加FLAG_ACTIVITY_BROUGHT_TO_FRONT。
如果 recycleTask() 返回的不是 START_SUCCESS,startActivityInner() 会立刻结束。因为这说明:
- 要么已经把旧页面通过
onNewIntent()复用了。 - 要么只需要把旧
Task拉到前台。 - 要么命中了某种无需继续创建实例的提前返回路径。
8.4 没有可复用栈顶时:mAddingToTask = true
mAddingToTask = true;
如果 targetTaskTop == null,代码进入 else:
这表示:
- 虽然目标
Task存在,但里面没有一个合适的、可直接复用的栈顶页面。 - 所以后续不是“重用旧页面”,而是“往这个现有任务里再加一个新的
ActivityRecord”。
8.5 再检查一次当前前台栈顶是否可以 singleTop 复用
final Task topRootTask = mPreferredTaskDisplayArea.getFocusedRootTask();
if (topRootTask != null) {
startResult = deliverToCurrentTopIfNeeded(topRootTask, intentGrants);
if (startResult != START_SUCCESS) {
return startResult;
}
}
接下来这段非常容易漏掉:
为什么前面已经做过 recycleTask(),这里还要再检查一次?
- 因为前面的复用主要是围绕“目标
Task”展开。 - 这里检查的是“当前前台焦点
RootTask顶部页面”是否就是同一个组件,能不能直接singleTop复用。
deliverToCurrentTopIfNeeded() 成立的条件非常严格:
top != null- 栈顶组件和
mStartActivity是同一个ComponentName - 同一用户
- 目标进程已附着
attachedToProcess() - 且满足
FLAG_ACTIVITY_SINGLE_TOP或launchMode == singleTop - 如果是
Home Activity,还要求display area一致
一旦成立:
- 不再创建新实例。
- 直接
deliverNewIntent(top, intentGrants)。 - 必要时先
resumeFocusedTasksTopActivities()。 - 最终返回
START_DELIVERED_TO_TOP或START_RETURN_INTENT_TO_CALLER。
8.6 确保 RootTask 存在,再决定是新建 Task 还是加到现有 Task
if (mTargetRootTask == null) {
mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, targetTask, mOptions);
}
如果一路走到这里,说明还需要继续创建或入栈。
这一步的含义是:
- 不管你是新建任务还是复用旧任务,最终都必须有一个承载它的
RootTask。 - 如果前面还没有确定,就在这里按显示区域、窗口模式、目标任务归属动态创建或取得。
然后分两条路径:
-
if (newTask): -
先算
taskToAffiliate,只有launchTaskBehind且存在来源任务时才需要建立任务关联。 -
再
setNewTask(taskToAffiliate),真正分配TaskId、把ActivityRecord放进新的Task,并把Task挂到窗口容器树里。 -
else if (mAddingToTask): -
调用
addOrReparentStartingActivity(targetTask, "adding to task")。 -
把当前
ActivityRecord追加到现有Task顶部,或者在必要时做重挂接。
9. 阶段七:完成入栈与前台切换
// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
recordTransientLaunchIfNeeded(mLastStartActivityRecord);
if (!mAvoidMoveToFront && mDoResume) {
logOnlyCreatorAllowsBAL(balVerdict, realCallingUid, newTask);
mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask);
if (!mTargetRootTask.isTopRootTaskInDisplayArea() && mService.isDreaming()
&& !dreamStopping) {
mLaunchTaskBehind = true;
r.mLaunchTaskBehind = true;
}
}
mService.mUgmInternal.grantUriPermissionUncheckedFromIntent(intentGrants,
mStartActivity.getUriPermissionsLocked());
这一段对应主骨架里的“完成入栈与前台切换”:
recordTransientLaunchIfNeeded(mLastStartActivityRecord):入栈后再补一次瞬态记录,保证过渡动画收集对象准确。- 如果
!mAvoidMoveToFront && mDoResume: - 先
logOnlyCreatorAllowsBAL(...),记录特殊的后台启动限制日志。 - 再
mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask),真正把目标任务提到用户可见的最前层。
这里还有一个 Dream 分支:
每个条件的意思分别是:
!isTopRootTaskInDisplayArea():当前目标并不是显示区里最顶层的根任务。mService.isDreaming():系统正在屏保 / Dream 状态。!dreamStopping:Dream 还没有进入停止尾声。
此时不应该粗暴打断 Dream,而是把这次启动标成 launch behind,让 Activity 在 Dream 下面先创建并进入可见态。
同时还会顺手做两类权限处理:
grantUriPermissionUncheckedFromIntent(...):把Intent里携带的 URI 访问能力授予目标Activity。grantImplicitAccess(...):处理resultTo回调链或shareIdentity场景下的包可见性 / 隐式访问授权。
10. 阶段八:移交给 resume 恢复链
// frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
final Task startedTask = mStartActivity.getTask();
if (newTask) {
EventLogTags.writeWmCreateTask(mStartActivity.mUserId, startedTask.mTaskId,
startedTask.getRootTaskId(), startedTask.getDisplayId());
}
mStartActivity.logStartActivity(EventLogTags.WM_CREATE_ACTIVITY, startedTask);
mStartActivity.getTaskFragment().clearLastPausedActivity();
mRootWindowContainer.startPowerModeLaunchIfNeeded(false /* forceSend */, mStartActivity);
final boolean isTaskSwitch = startedTask != prevTopTask;
mTargetRootTask.startActivityLocked(mStartActivity, topRootTask, newTask, isTaskSwitch,
mOptions, sourceRecord);
if (mDoResume) {
...
mRootWindowContainer.resumeFocusedTasksTopActivities(
mTargetRootTask, mStartActivity, mOptions, mTransientLaunch);
}
mRootWindowContainer.updateUserRootTask(mStartActivity.mUserId, mTargetRootTask);
mSupervisor.mRecentTasks.add(startedTask);
mSupervisor.handleNonResizableTaskIfNeeded(startedTask,
mPreferredWindowingMode, mPreferredTaskDisplayArea, mTargetRootTask);
...
return START_SUCCESS;
最后这一段,本质上是在把控制权从 ActivityStarter 逐步移交给后续的窗口管理和 resume 恢复链:
EventLogTags.writeWmCreateTask(...):如果是新Task,写一条系统事件日志。mStartActivity.logStartActivity(...):记录WM_CREATE_ACTIVITY。clearLastPausedActivity():清理TaskFragment里缓存的lastPaused,避免状态污染。startPowerModeLaunchIfNeeded(false, mStartActivity):通知电源管理做启动加速。
先看几个启动前的收尾动作:
isTaskSwitch判断的是“启动后的startedTask是否不同于启动前的prevTopTask”。- 如果不同,就说明这是一次真正的任务切换,不只是当前应用内部页面跳转。
startActivityLocked(...)会把这个ActivityRecord纳入窗口和生命周期管理流程,属于“从任务决策阶段进入真正启动阶段”的分界线。
接着进入真正的 mDoResume 分支:
- 如果目标
RootTask顶部不可聚焦,或者顶部是一个task overlay
- 不能直接
resume。 - 改为
ensureActivitiesVisible(...)。 - 然后
executeAppTransition()让窗口动画照样执行。
- 否则走正常前台恢复路径
- 必要时先
mTargetRootTask.moveToFront("startActivityInner")。 - 最终调用:
mRootWindowContainer.resumeFocusedTasksTopActivities(
mTargetRootTask, mStartActivity, mOptions, mTransientLaunch);
这一步才是从 ActivityStarter 过渡到 RootWindowContainer / Task / TaskFragment 恢复链路的总入口。也就是说:
startActivityInner()决定“该把谁放进哪个任务、谁应该在最前面”。resumeFocusedTasksTopActivities()决定“真正让谁进入 resumed,并在需要时拉起目标进程”。
在恢复链交出去之后,系统还会补几步善后动作:
updateUserRootTask(...):更新该用户当前的根任务记录。RecentTasks.add(startedTask):把新启动或被拉起的任务更新到最近任务列表。handleNonResizableTaskIfNeeded(...):如果目标不支持多窗口,立即执行兼容策略。moveActivityToPinnedRootTask(...):如果请求launch into PiP且条件满足,直接转入画中画栈。onNewActivityLaunched(mStartActivity):通知后台启动控制器,这次启动已经真正发生。
最终返回 START_SUCCESS,表示 startActivityInner() 的职责已经完成。