​​Activity 任务指挥官 (ActivityStackSupervisor)​​ 和 ​​Activity 栈长 (ActivityStack)​​

112 阅读18分钟

我们继续安卓王国的故事,这次聚焦于大管家 AMS 手下的两位得力干将:​​Activity 任务指挥官 (ActivityStackSupervisor)​​ 和 ​​Activity 栈长 (ActivityStack)​​,以及它们如何管理王国里千千万万的 Activity “房间”(任务栈)。

​故事梗概:​​ 想象安卓王国里,每个 App 就像一座功能各异的建筑(比如微信大厦、淘宝商场)。每座建筑里有很多房间(Activity),这些房间不是随意堆放的,而是按照特定的“楼层”(任务栈 - Task)和“建筑规划”(任务栈管理规则)来组织的。大管家 AMS 麾下的指挥官和栈长,就是负责确保这些房间被正确建造、摆放、切换和拆除的。

​核心角色:​

  • ​大管家 - AMS (ActivityManagerService):​​ 王国总管,协调一切。
  • ​任务指挥官 - ActivityStackSupervisor (ASS):​​ AMS 的左膀右臂,​​监督所有栈长 (ActivityStack)​​,管理多个“屏幕”(Display)上的任务栈。它在 AMS 出生时就被任命(new ActivityStackSupervisor(this))。
  • ​栈长 - ActivityStack (AS):​​ ​​负责管理一个或多个任务栈 (TaskRecord)​​。每个栈长手下有一批“楼层管理员”(TaskRecord),每个管理员管一层楼(一个任务栈),楼里有多个房间(ActivityRecord)。栈长负责记录栈里 Activity 的状态(Resumed, Paused 等),处理 Activity 的生命周期转换。
  • ​楼层管理员 - TaskRecord (TR):​​ 代表一个任务栈(Task)。它记录了这个栈里所有的房间(ActivityRecord)以及这个栈的归属(taskAffinity)。
  • ​房间记录员 - ActivityRecord (AR):​​ 记录一个 Activity 的所有信息(属于哪个 App,在哪个 Task 里,启动模式,状态等)。一个 Activity 对应一个 AR。
  • ​启动规划师 - ActivityStarter (ASr):​​ 负责解析启动 Activity 的意图(Intent)和参数(Flags, LaunchMode),规划新 Activity 该放到哪个栈的哪个位置。它是处理启动请求的核心逻辑执行者。

​第一幕:指挥官与栈长的诞生与职责 (ActivityStackSupervisor & ActivityStack)​

  1. ​大管家任命指挥官 (AMS 初始化):​

    // frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
    public ActivityManagerService(Context systemContext) {
        ...
        // ★ 关键点 ★ ① 创建任务指挥官 (ActivityStackSupervisor)
        mStackSupervisor = new ActivityStackSupervisor(this); // AMS 出生时创建指挥官
        ...
    }
    
    • 大管家 AMS 一上任,立刻任命了它的得力助手——​​任务指挥官 (ActivityStackSupervisor - ASS)​​。指挥官直接向 AMS 汇报,负责管理王国里所有的“栈长”。
  2. ​指挥官手下有几位重要栈长 (ASS 的成员变量):​

    // frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
    public final class ActivityStackSupervisor implements DisplayListener {
        ...
        ActivityStack mHomeStack;    // 负责管理“家园”(Launcher)的栈长
        ActivityStack mFocusedStack; // 负责管理当前获得用户焦点(能接收输入)的栈长
        private ActivityStack mLastFocusedStack; // 负责管理上次获得焦点的栈长
        ...
        // ★ 关键点 ★ ② 如何找到当前焦点栈长
        ActivityStack getFocusedStack() {
            return mFocusedStack; // 指挥官直接返回当前焦点栈长
        }
        ...
    }
    
    • mHomeStack:​​ 专门负责管理王国的主屏幕——​​Launcher App​​(比如手机桌面)的所有房间(Activity)。这是王国启动后用户首先看到的“建筑”。
    • mFocusedStack:​​ 这是​​最重要的栈长​​!它管理着​​当前正在与用户交互​​的那个任务栈(比如你正在刷微信朋友圈,那么微信朋友圈所在的栈就由这个栈长管)。这个栈里的最顶上的房间(Activity)是用户正在看的。
    • mLastFocusedStack:​​ 记录上一个获得焦点的栈长(比如你刚刚从微信切换到淘宝,那么微信的栈长会被记录在这里)。
    • 指挥官提供了 getFocusedStack() 方法,方便王国其他部门(如 AMS)随时询问:“现在谁是焦点栈长?”
  3. ​栈长记录房间状态 (ActivityStack 的状态管理):​

    // frameworks/base/services/core/java/com/android/server/am/ActivityStack.java
    enum ActivityState {
        INITIALIZING, // 初始化中
        RESUMED,      // 已恢复 (用户可见可交互)
        PAUSING,      // 正在暂停 (准备让出焦点)
        PAUSED,       // 已暂停 (失去焦点但可见,比如被半透明Activity覆盖)
        STOPPING,     // 正在停止 (准备进入后台)
        STOPPED,      // 已停止 (完全不可见,在后台)
        FINISHING,    // 正在结束 (准备销毁)
        DESTROYING,   // 正在销毁 (执行onDestroy)
        DESTROYED     // 已销毁 (从内存移除)
    }
    
    • 栈长 (ActivityStack) 用这个 ActivityState 枚举来精确记录它管理的每一个房间(ActivityRecord)当前处于生命周期的哪个阶段。这对于正确调度生命周期方法 (onCreateonResumeonPause 等) 至关重要。

    • ​应用场景示例 (切换动画):​

      // frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
      @Override
      public void overridePendingTransition(IBinder token, String packageName, int enterAnim, int exitAnim) {
          ...
          synchronized (this) {
              // 1. 通过 token 找到要设置动画的 ActivityRecord (self)
              final ActivityRecord self = ActivityRecord.forTokenLocked(token);
              ...
              // 2. ★ 关键点 ★ ③ 检查状态:只有正在前台或正在暂停的Activity才能设置切换动画
              if (self.state == ActivityState.RESUMED || self.state == ActivityState.PAUSING) {
                  // 3. 调用窗口管理服务 (WMS) 设置动画
                  mWindowManager.overridePendingAppTransition(packageName, enterAnim, exitAnim, null);
              }
          }
          Binder.restoreCallingIdentity(origId);
      }
      
      • 当 App 调用 overridePendingTransition 想自定义 Activity 切换动画时,AMS 会:

        • 根据传入的 token (代表那个 Activity) 找到对应的房间记录 self (ActivityRecord)。
        • ​检查这个房间的状态 (self.state)​​:它必须是 RESUMED (用户正在看它) 或者 PAUSING (它正在让出焦点给新房间)。只有在这两种状态下,设置动画才有意义(比如从 A 跳到 B,A 会 PAUSING,B 会 RESUMED)。
        • 如果状态符合,AMS 就命令窗口管理服务 (WMS) 执行动画。
  4. ​栈长特别关注的特殊房间 (ActivityStack 的特殊成员):​

    // frameworks/base/services/core/java/com/android/server/am/ActivityStack.java
    ActivityRecord mPausingActivity = null;    // 正在执行 onPause() 的房间 (记录员)
    ActivityRecord mLastPausedActivity = null; // 上一个成功执行完 onPause() 的房间
    ActivityRecord mLastNoHistoryActivity = null; // 最近一个设置了 android:noHistory="true" 的房间 (退出不留痕迹)
    ActivityRecord mResumedActivity = null;    // 当前执行了 onResume() 的房间 (用户正在交互的顶层房间)
    ActivityRecord mLastStartedActivity = null; // 最近一个通过 startActivity() 启动的房间
    ActivityRecord mTranslucentActivityWaiting = null; // 等待转换的半透明Activity (影响后面Activity的可见性)
    
    • 栈长会特别关注一些处于关键状态或具有特殊属性的房间(ActivityRecord),记录它们的引用。这对协调多个 Activity 的生命周期转换和栈操作非常关键。例如:

      • 当用户按 Home 键时,栈长需要知道当前哪个房间是 mResumedActivity,以便让它暂停 (onPause)。
      • 当新房间启动时,栈长需要知道当前哪个房间是 mResumedActivity,以便让它暂停,同时将新房间置为 mResumedActivity
  5. ​栈长的管理清单 (ActivityStack 的 ArrayList):​

    // frameworks/base/services/core/java/com/android/server/am/ActivityStack.java
    final ArrayList<TaskRecord> mTaskHistory = new ArrayList<>(); // 1. 所有未被销毁的楼层 (Task)
    final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<>(); // 2. 正在运行的房间 (按最近最少使用排序,方便内存回收)
    final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<>(); // 3. 禁止动画的房间 (记录员列表)
    private final ArrayList<TaskGroup> mValidateAppTokens = new ArrayList<>(); // 4. 用于与窗口管理器验证令牌的Task组
    
    • mTaskHistory:​​ 这是栈长管理的​​核心清单​​!它按顺序记录了所有​​未被销毁的楼层 (TaskRecord)​​。顺序通常代表了它们的历史(最近使用的可能在后面)。每个 TaskRecord 又管理着自己楼里的房间 (ActivityRecord)。
    • mLRUActivities:​​ 记录所有​​正在运行​​的房间(ActivityRecord)。这个列表按 ​​LRU (Least Recently Used - 最近最少使用)​​ 排序。AMS 在内存不足时,会优先干掉列表​​最前面​​(最久没被使用的)房间所在的 App 进程。
    • mNoAnimActivities:​​ 记录那些被标记为“不需要切换动画”的房间。栈长在处理这些房间的进出栈时会跳过动画。
    • mValidateAppTokens:​​ (较底层) 用于和窗口管理服务 (WMS) 协作,验证应用窗口令牌的有效性。

​第二幕:栈管理的核心规则 - 房间的建造与摆放 (Launch Mode, Flags, taskAffinity)​

王国里建造房间(启动 Activity)不是随意的,必须遵守严格的建筑规划(栈管理规则)。这些规则主要由三个因素决定:

  1. ​建筑蓝图 (Launch Mode):​​ 写在 App 的 AndroidManifest.xml 中 (android:launchMode),定义了房间的“建造特性”。

    • standard (标准间):​​ 最常见的。每次启动都​​新建一个房间​​放在当前楼层的楼顶。同一个房间里可以住很多长得一样的(同一个 Activity 类)。

    • singleTop (楼顶单间):​​ 如果要启动的房间​​已经在当前楼层的楼顶​​,就不再新建,而是把楼顶那个房间叫醒(调用它的 onNewIntent)。如果不在楼顶,就新建一个放楼顶。

    • singleTask (独占楼层):​​ 这个房间​​要求独占一层楼​​(一个 Task)!

      • 如果王国里​​已经有​​它想要的那层楼 (taskAffinity 匹配的 Task),并且楼里有这个房间:

        • 把这层楼​​上面​​的所有房间都拆掉(清栈),把这个房间露出来成为楼顶。
        • 唤醒这个房间(onNewIntent),​​不新建​​。
      • 如果王国里​​已经有​​它想要的那层楼,但楼里​​没有​​这个房间:

        • 就在这层楼的楼顶​​新建​​一个房间。
      • 如果王国里​​没有​​它想要的那层楼:

        • ​新建一层楼​​ (Task),然后在楼顶​​新建​​这个房间。
    • singleInstance (超独栋):​​ 比 singleTask 更霸道!它要求独占一层楼,并且这层楼​​只能有它自己一个房间​​!启动逻辑类似 singleTask,但确保新栈只放它一个。

  2. ​临时施工许可证 (Intent Flags):​​ 在启动房间时通过 Intent.setFlags() 动态指定的规则。​​如果和蓝图 (Launch Mode) 冲突,以许可证 (Flags) 为准!​

    • FLAG_ACTIVITY_NEW_TASK (开新楼):​​ 效果类似 singleTask。最常用,用来启动其他 App 的 Activity 时通常需要这个。

    • FLAG_ACTIVITY_SINGLE_TOP (楼顶单间):​​ 效果等同 singleTop

    • FLAG_ACTIVITY_CLEAR_TOP (清楼顶):​​ 如果要启动的房间​​已经在当前楼里​​,则把它​​上面​​的所有房间都拆掉,让它成为楼顶。singleTask 默认自带此效果。

    • ​其他有用 Flag:​

      • FLAG_ACTIVITY_NO_HISTORY (不留名): 启动的房间一旦退出(按 Back 或 finish()),就像从未存在过一样,不会留在历史栈里。也可以在 XML 设 android:noHistory="true"
      • FLAG_ACTIVITY_MULTIPLE_TASK (多开新楼): 必须和 NEW_TASK 一起用。即使蓝图 (launchMode) 或 taskAffinity 相同,也​​强制开新楼​​ (新 Task)。
      • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS (不入历史): 该房间所在楼层不会出现在“最近任务列表”(长按 Home 键看到的列表)。
      • FLAG_ACTIVITY_BROUGHT_TO_FRONT (被带到前台): 通常不是 App 自己设的,是系统在 singleTask 场景下自动加的,表示这个 Activity 是被从栈里带到前台的,不是新启动的。
      • FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY (历史启动): 通常不是 App 自己设的,表示这个 Activity 是从“最近任务列表”启动的。
      • FLAG_ACTIVITY_CLEAR_TASK (清楼): 必须和 NEW_TASK 一起用。在启动新 Activity 前,​​清空它要加入的整层楼​​(Task)里所有的旧房间!然后才把新房间放进去。
  3. ​房间归属证 (taskAffinity):​​ 写在 AndroidManifest.xml (android:taskAffinity)。默认情况下,一个 App 的所有房间有相同的 taskAffinity (通常等于 App 包名)。它决定了:

    • ​和谁一起住 (与 NEW_TASK/singleTask 配合):​​ 当启动模式或 Flag 要求新 Activity 去一个新栈时,系统会寻找一个 taskAffinity 与新 Activity 的 taskAffinity ​​相同​​的栈。如果找到,就放进去;找不到,才新建一个栈。

    • ​搬家能力 (与 allowTaskReparenting 配合):​

      • 假设社交 App (A) 启动了一个发邮件的 Activity (B),B 的 taskAffinity 被设为邮箱 App (C) 的包名(它更“亲近”邮箱 App),并且 B 设置了 android:allowTaskReparenting="true"
      • 最初,B 是放在社交 App A 所在的栈里的(因为是从 A 启动的)。
      • 后来,当用户打开了邮箱 App C(它的栈到了前台),神奇的事情发生了:​​系统会把 Activity B 从社交 App A 的栈里“搬走”,迁移到邮箱 App C 的栈里!​​ 这样 B 就回到了它“归属”的地方。

​第三幕:启动规划师的计算 (ActivityStarter 的核心逻辑)​

当 App 调用 startActivity(),最终会走到 AMS,AMS 会把启动请求交给 ​​启动规划师 (ActivityStarter)​​ 来具体执行。规划师的核心工作是:根据启动请求(Intent, Flags, caller Activity 等)和现有栈结构,​​精确计算出新 Activity 应该放在哪个栈 (TaskRecord) 的哪个位置​​,以及如何处理现有的 Activity。这个过程非常复杂,我们看一个关键片段:

// frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord, ...) {
    // 1. 初始化状态 (重置各种标志,记录启动参数)
    setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, ...); // ★ 关键点 ★ ①

    // 2. ★ 关键计算 ★ 决定是否需要新Task,设置正确的Launch Flags
    computeLaunchingTaskFlags(); // ★ 关键点 ★ ②

    // 3. 计算源栈 (sourceRecord 所在的栈)
    computeSourceStack();

    // 4. 将计算出的最终 Flags 设置回 Intent (影响后续决策)
    mIntent.setFlags(mLaunchFlags); // ★ 关键点 ★ ③

    // ... (极其复杂的后续逻辑:查找/创建目标栈,处理现有实例,安排生命周期等)
}
  • ​★ 关键点 ★ ① setInitialState(...):​​ 规划师首先根据传入的参数(新 Activity 记录 r,启动它的源 Activity 记录 sourceRecord,启动模式,Intent Flags 等)初始化自己的内部状态(如 mLaunchFlagsmInTask 等)。

  • ​★ 关键点 ★ ② computeLaunchingTaskFlags():​​ 这是​​最核心的计算之一​​!规划师根据初始状态,结合现有栈的情况,计算出​​最终​​应该使用的 mLaunchFlags。这个计算决定了​​新 Activity 是否应该放入一个新栈 (FLAG_ACTIVITY_NEW_TASK)​​。我们看一段简化逻辑:

    // frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java (简化)
    private void computeLaunchingTaskFlags() {
        ...
        // 情况1:没有指定目标栈 (mInTask == null)
        if (mInTask == null) {
            // 情况1.1:启动者不是Activity (sourceRecord == null,如从通知、快捷方式启动)
            if (mSourceRecord == null) {
                // ★ 规则 ★:非Activity上下文启动,强制加 NEW_TASK 标志 (必须开新栈或找归属栈)
                if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0) {
                    mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK; // ★ 关键点 ★ ④
                }
            }
            // 情况1.2:启动者是一个 singleInstance Activity
            else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
                // ★ 规则 ★:从 singleInstance 启动,必须新开一个栈 (避免污染原有独占栈)
                mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK; // ★ 关键点 ★ ⑤
            }
            // 情况1.3:新Activity的启动模式是 singleTask 或 singleInstance
            else if (mLaunchSingleInstance || mLaunchSingleTask) {
                // ★ 规则 ★:singleTask/singleInstance 模式本身要求可能在新栈
                mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK; // ★ 关键点 ★ ⑥
            }
        }
        ... // 其他情况(指定了目标栈等)处理
    }
    
    • 这段代码处理的是​​目标栈未指定 (mInTask == null)​​ 的情况(最常见)。
    • ​★ 关键点 ★ ④:​​ 如果启动请求​​不是从一个 Activity 发起的​​(比如从通知栏点击、桌面快捷方式、BroadcastReceiver),系统​​强制​​添加 FLAG_ACTIVITY_NEW_TASK 标志。​​这是为什么从通知启动 App 会开新栈的原因!​
    • ​★ 关键点 ★ ⑤:​​ 如果​​启动者 (sourceRecord) 本身在一个 singleInstance 栈里​​(这种栈只能有一个 Activity),那么新 Activity ​​必须​​放到一个新栈里(避免破坏 singleInstance 的独占性)。比如在 singleInstance 的 Activity A 里点链接打开浏览器,浏览器会在新栈。
    • ​★ 关键点 ★ ⑥:​​ 如果​​新 Activity 的 Launch Mode 是 singleTask 或 singleInstance​,规划师会添加 NEW_TASK 标志,触发系统为其寻找/创建匹配 taskAffinity 的栈。
  • ​★ 关键点 ★ ③ mIntent.setFlags(mLaunchFlags):​​ 将计算出的最终 Flags 设置回 Intent。后续的栈操作(如查找目标 Task)会依据这个最终的 Flags 进行。

​规划师如何查找匹配的栈 (taskAffinity 的应用):​

规划师在决定使用 NEW_TASK 后,或者在处理 singleTask/singleInstance 时,需要根据 taskAffinity 寻找匹配的栈。这通常通过调用 ActivityStackSupervisor.findTaskLocked() 或其内部方法(最终会遍历 ActivityStack.mTaskHistory)来实现:

// frameworks/base/services/core/java/com/android/server/am/ActivityStack.java (简化)
void findTaskLocked(ActivityRecord target, FindTaskResult result) {
    // 倒序遍历所有未被销毁的 Task (mTaskHistory)
    for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { // ★ 关键点 ★ ①
        final TaskRecord task = mTaskHistory.get(taskNdx); // 取出一个 TaskRecord (楼层)
        ... // 其他匹配条件检查 (root Intent, document 等)
        // ★ 关键匹配 ★:检查 Task 的 rootAffinity 是否等于目标 Activity 的 taskAffinity
        if (task.rootAffinity.equals(target.taskAffinity)) { // ★ 关键点 ★ ②
            ... // 找到匹配的 Task!
            result.r = ...; // 记录结果
            result.matchedByRootAffinity = true;
            break;
        }
    }
}
  • ​★ 关键点 ★ ①:​​ 遍历栈长 (ActivityStack) 管理的所有未被销毁的 Task (mTaskHistory),从最近可能使用的开始(栈顶)向最老的(栈底)查找。这更可能快速找到可用的匹配栈。

  • ​★ 关键点 ★ ②:​​ ​​核心匹配条件​​:检查当前遍历到的 TaskRecord 的 rootAffinity (这个 Task 创建时的初始 taskAffinity,通常等于根 Activity 的 taskAffinity) ​​是否等于​​ 要启动的目标 Activity (target) 的 taskAffinity

    • 如果相等 (task.rootAffinity.equals(target.taskAffinity)),说明这个 Task ​​正是​​目标 Activity 想要归属的那个栈!
    • 将匹配结果存储在 FindTaskResult 对象 (result) 中返回。规划师就知道该把新 Activity 放进这个已有的 Task 里了。

​总结:​

大管家 AMS 依靠 ​​任务指挥官 (ActivityStackSupervisor)​​ 管理着几个关键的 ​​栈长 (ActivityStack)​​。每个栈长用 ​​楼层管理员 (TaskRecord)​​ 管理着代表任务栈的“楼层”,楼里每个 ​​房间记录员 (ActivityRecord)​​ 精确记录了一个 Activity 的信息和状态。​​启动规划师 (ActivityStarter)​​ 是执行启动请求的“大脑”,它依据建筑蓝图 (Launch Mode)、临时许可证 (Intent Flags) 和房间归属证 (taskAffinity),精确计算出新 Activity 该放在哪个栈的哪个位置,并指挥栈长完成具体的入栈、出栈、生命周期调度工作。这套精密的栈管理系统,确保了 Android 多任务、跨应用 Activity 启动的复杂场景能够有序、高效、符合开发者预期的运行。

安卓王国任务栈管理大揭秘:指挥官、楼层与房间规划

我们将通过一个建筑王国的故事,解析Android系统中Activity任务栈管理的核心机制。这个故事基于AMS的第二篇解析,重点讲解ActivityStackSupervisor、ActivityStack以及任务栈管理的奥秘。

故事背景:安卓建筑王国

想象一个庞大的安卓王国,每个APP就像一座功能各异的建筑(微信大厦、淘宝商城等)。每座建筑里有无数房间(Activity),这些房间不是随意堆放的,而是通过精密的​​任务栈系统​​来管理的:

Copy
🏢 微信大厦
├── 📁 聊天任务栈 (TaskRecord)
│   ├── 💒 聊天列表 (Activity A)
│   └── 💒 聊天窗口 (Activity B)
└── 📁 朋友圈任务栈 (TaskRecord)
    └── 💒 朋友圈 (Activity C)

核心角色介绍

  1. ​大管家AMS​​:王国总管,协调一切
  2. ​任务指挥官(ActivityStackSupervisor)​​:AMS的左膀右臂
  3. ​栈长(ActivityStack)​​:管理一组任务栈的"包工头"
  4. ​楼层管理员(TaskRecord)​​:管理单个任务栈的"楼层经理"
  5. ​房间记录员(ActivityRecord)​​:记录每个Activity的详细信息

第一章:指挥官与栈长的诞生

​当大管家AMS上任时​​,第一件事就是任命任务指挥官:

java
Copy
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public ActivityManagerService(Context systemContext) {
    // 创建任务指挥官(ActivityStackSupervisor)
    mStackSupervisor = new ActivityStackSupervisor(this);
}

​指挥官手下有三大栈长​​:

java
Copy
public class ActivityStackSupervisor {
    ActivityStack mHomeStack;     // 管理"家园"(Launcher)的栈长
    ActivityStack mFocusedStack;  // 当前焦点栈长(用户正在交互)
    ActivityStack mLastFocusedStack; // 上次焦点栈长
}

✅ ​​示例场景​​:当用户从微信切换到淘宝,微信栈长成为mLastFocusedStack,淘宝栈长成为mFocusedStack


第二章:栈长的日常工作

每个栈长都需要精确管理Activity的生命周期状态:

java
Copy
enum ActivityState {
    INITIALIZING,  // 初始化中
    RESUMED,       // 活跃状态(用户可见)
    PAUSING,       // 正在暂停
    PAUSED,        // 已暂停
    STOPPING,      // 正在停止
    STOPPED,       // 已停止(后台)
    FINISHING,     // 结束中
    DESTROYING,    // 销毁中
    DESTROYED      // 已销毁
}

​栈长特别关注的特殊房间​​:

java
Copy
// 特殊状态的Activity记录
ActivityRecord mPausingActivity = null;    // 正在暂停的Activity
ActivityRecord mResumedActivity = null;    // 当前活跃的Activity
ActivityRecord mLastStartedActivity = null; // 最近启动的Activity

💡 ​​案例解析​​:当设置Activity切换动画时,系统会检查当前状态:

java
Copy
if (self.state == ActivityState.RESUMED || 
    self.state == ActivityState.PAUSING) {
    // 只有在前台或暂停状态才能设置动画
    mWindowManager.overridePendingAppTransition(...);
}

第三章:建筑规划的核心规则

规则一:建筑蓝图(Launch Mode)

模式说明
standard默认模式,每次新建房间(Activity)
singleTop如果目标房间在楼顶,则复用(调用onNewIntent)
singleTask要求独占一层楼,如果存在匹配楼层则清空楼上房间
singleInstance独占一层楼且只允许一个房间

🌰 ​​示例​​:微信中点击邮箱地址:

  • 邮箱Activity设置singleTask+taskAffinity="com.email"
  • 系统创建新的邮箱任务栈,而不是放在微信栈里

规则二:临时施工证(Intent Flags)

java
Copy
// 动态设置的启动规则(优先级高于Launch Mode)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);  // 开启新任务栈
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // 清空目标上方的Activity

​Flags冲突解决原则​​:当临时施工证(Flags)与建筑蓝图(LaunchMode)冲突时,以Flags为准!

规则三:房间归属证(taskAffinity)

xml
Copy
<!-- 指定Activity的"籍贯" -->
<activity 
    android:name=".EmailActivity"
    android:taskAffinity="com.android.email"/>

✨ ​​神奇搬家功能​​:

  1. 微信启动邮箱Activity(此时在微信栈)
  2. 用户打开邮箱APP
  3. 邮箱Activity自动"搬家"到邮箱栈
java
Copy
// 实现关键:allowTaskReparenting="true"
<activity android:name=".EmailActivity"
          android:allowTaskReparenting="true"/>

第四章:启动规划师的工作

当发起startActivity()时,​​启动规划师(ActivityStarter)​​开始精密计算:

java
Copy
private int startActivityUnchecked(...) {
    // 阶段1:初始化启动参数
    setInitialState(r, options, inTask, doResume, startFlags, sourceRecord);
    
    // 阶段2:计算任务栈规则(核心!)
    computeLaunchingTaskFlags(); // ★ 关键计算
    
    // 阶段3:应用计算出的Flags
    mIntent.setFlags(mLaunchFlags);
}

​栈匹配的核心算法​​:

java
Copy
void findTaskLocked(ActivityRecord target, FindTaskResult result) {
    // 遍历所有任务栈(从最新到最旧)
    for (int i = mTaskHistory.size()-1; i >= 0; i--) {
        TaskRecord task = mTaskHistory.get(i);
        
        // 关键匹配:任务栈的rootAffinity vs Activity的taskAffinity
        if (task.rootAffinity.equals(target.taskAffinity)) {
            result.matchedByRootAffinity = true;
            break; // 找到匹配栈!
        }
    }
}

🧠 ​​决策场景分析​​:

  1. ​从通知启动Activity​​:强制添加NEW_TASK
java
Copy
if (mSourceRecord == null) { // 非Activity上下文启动
    mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK; 
}

2. ​​singleInstance栈中启动​​:必须开新栈

java
Copy
else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
    mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}

技术架构全景图

36610e8eb66a48.png

​关键内存结构​​:

java
Copy
// 栈长管理的主要数据结构
final ArrayList<TaskRecord> mTaskHistory = new ArrayList<>(); // 所有存活的任务栈
final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<>(); // 按LRU排序的Activity

总结:精密的栈管理系统

安卓王国的任务栈管理系统通过四大核心机制协同工作:

  1. ​层级化管理​​:AMS > StackSupervisor > ActivityStack > TaskRecord
  2. ​状态精确跟踪​​:9大ActivityState + 特殊状态标记
  3. ​多维度规则​​:LaunchMode(静态) + Intent Flags(动态)+ taskAffinity(归属)
  4. ​智能匹配算法​​:rootAffinity匹配 + LRU管理 + 状态决策

正是这套精密系统,使得我们能在抖音看视频时跳转微信回复消息,再返回抖音时无缝衔接,仿佛一切从未中断。就像魔术师手中的卡牌,看似简单的界面切换背后,是安卓系统工程师精心设计的栈管理魔术。