Activity 启动流程扩展篇(一)—— startActivityInner 任务决策全解析

11 阅读20分钟

前言

本文是从Activity 启动流程(二)—— AMS 处理阶段中拆出的 扩展篇文章

代码基于 android-14.0.0_r9

1. 一句话先说结论

startActivityInner() 核心流程包括:

  1. 初始化启动状态。
  2. 统一修正最终 Flag。
  3. 记录启动前现场。
  4. 查找可复用任务。
  5. 决定最终目标 Task
  6. 决定复用旧页面还是创建新实例。
  7. 完成入栈与前台切换。
  8. 移交给 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

后面所有判断都会围绕这些字段展开:

  • mStartActivity
  • mIntent
  • mSourceRecord
  • mLaunchMode
  • mLaunchFlags
  • mCallingUid
  • mRealCallingUid

所以 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() 会综合目标 ActivitylaunchMode、来源 mSourceRecord、是否指定了 mInTask、以及当前已有的 Intent flag,重新计算最终要生效的 mLaunchFlags
  • singleTasksingleInstancesingleInstancePerTask 这类模式,系统可能主动补上 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_INSTANCEfindActivity(...):因为这种模式要求全局独占,系统直接按 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_TASKCLEAR_TOP
  • 目标显示区域 / 窗口模式。

Launcher 点击图标来说,常见分支是:

  • 应用之前没有任何历史任务:reusedTask == null
  • 应用之前已有后台任务,且满足复用条件:reusedTask != null

把它代回到“桌面点击 icon”场景里,就很容易理解了:

  • 如果系统里根本没有这个应用的历史 Task,或者历史 Task 不满足用户 / 组件 / 文档 / affinity / 显示区域等匹配条件,那么 findTask() 返回 null,后面就只能新建 Task,表现为冷启动。
  • 如果系统里已经有一个历史 Task,且它的根 ActivitydocumentDatataskAffinity 命中了上面的规则,那么 findTask() 就会返回对应的 ActivityRecordgetReusableTask() 再取出它所在的 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,优先直接用它;否则再根据来源、inTaskNEW_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 容器,再从顶部非 finishing Activity 反推出目标叶子 Task;如果容器里没有有效 Activity,就清掉并返回 null

7.1 计算启动参数与准入校验

在“决定最终目标 Task”之后,还要补两步,才能确认这次决策真的可落地:

  • 先算“应该启动到哪里”。

  • 再判断“允不允许启动”。

  • computeLaunchParams(...) 负责计算启动位置和窗口参数,例如目标 TaskDisplayAreawindowingModebounds 等。普通桌面点击主页面启动,多数还是默认全屏显示区。

  • 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_TOPSINGLE_TOPNEW_TASKFlag 语义的地方,它会决定是清栈、投递 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_SUCCESSstartActivityInner() 会立刻结束。因为这说明:

  • 要么已经把旧页面通过 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_TOPlaunchMode == singleTop
  • 如果是 Home Activity,还要求 display area 一致

一旦成立:

  • 不再创建新实例。
  • 直接 deliverNewIntent(top, intentGrants)
  • 必要时先 resumeFocusedTasksTopActivities()
  • 最终返回 START_DELIVERED_TO_TOPSTART_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 分支:

  1. 如果目标 RootTask 顶部不可聚焦,或者顶部是一个 task overlay
  • 不能直接 resume
  • 改为 ensureActivitiesVisible(...)
  • 然后 executeAppTransition() 让窗口动画照样执行。
  1. 否则走正常前台恢复路径
  • 必要时先 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() 的职责已经完成。