我们继续安卓王国的故事,这次聚焦于大管家 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)
-
大管家任命指挥官 (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 汇报,负责管理王国里所有的“栈长”。
-
指挥官手下有几位重要栈长 (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)随时询问:“现在谁是焦点栈长?”
-
-
栈长记录房间状态 (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)当前处于生命周期的哪个阶段。这对于正确调度生命周期方法 (onCreate,onResume,onPause等) 至关重要。 -
应用场景示例 (切换动画):
// 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) 执行动画。
- 根据传入的
-
-
-
栈长特别关注的特殊房间 (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。
- 当用户按 Home 键时,栈长需要知道当前哪个房间是
-
-
栈长的管理清单 (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)不是随意的,必须遵守严格的建筑规划(栈管理规则)。这些规则主要由三个因素决定:
-
建筑蓝图 (Launch Mode): 写在 App 的
AndroidManifest.xml中 (android:launchMode),定义了房间的“建造特性”。-
standard(标准间): 最常见的。每次启动都新建一个房间放在当前楼层的楼顶。同一个房间里可以住很多长得一样的(同一个 Activity 类)。 -
singleTop(楼顶单间): 如果要启动的房间已经在当前楼层的楼顶,就不再新建,而是把楼顶那个房间叫醒(调用它的onNewIntent)。如果不在楼顶,就新建一个放楼顶。 -
singleTask(独占楼层): 这个房间要求独占一层楼(一个 Task)!-
如果王国里已经有它想要的那层楼 (
taskAffinity匹配的 Task),并且楼里有这个房间:- 把这层楼上面的所有房间都拆掉(清栈),把这个房间露出来成为楼顶。
- 唤醒这个房间(
onNewIntent),不新建。
-
如果王国里已经有它想要的那层楼,但楼里没有这个房间:
- 就在这层楼的楼顶新建一个房间。
-
如果王国里没有它想要的那层楼:
- 新建一层楼 (Task),然后在楼顶新建这个房间。
-
-
singleInstance(超独栋): 比singleTask更霸道!它要求独占一层楼,并且这层楼只能有它自己一个房间!启动逻辑类似singleTask,但确保新栈只放它一个。
-
-
临时施工许可证 (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)里所有的旧房间!然后才把新房间放进去。
-
-
房间归属证 (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 就回到了它“归属”的地方。
- 假设社交 App (A) 启动了一个发邮件的 Activity (B),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 等)初始化自己的内部状态(如mLaunchFlags,mInTask等)。 -
★ 关键点 ★ ②
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)
核心角色介绍
- 大管家AMS:王国总管,协调一切
- 任务指挥官(ActivityStackSupervisor):AMS的左膀右臂
- 栈长(ActivityStack):管理一组任务栈的"包工头"
- 楼层管理员(TaskRecord):管理单个任务栈的"楼层经理"
- 房间记录员(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"/>
✨ 神奇搬家功能:
- 微信启动邮箱Activity(此时在微信栈)
- 用户打开邮箱APP
- 邮箱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; // 找到匹配栈!
}
}
}
🧠 决策场景分析:
- 从通知启动Activity:强制添加
NEW_TASKjava 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; }
技术架构全景图
关键内存结构:
java
Copy
// 栈长管理的主要数据结构
final ArrayList<TaskRecord> mTaskHistory = new ArrayList<>(); // 所有存活的任务栈
final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<>(); // 按LRU排序的Activity
总结:精密的栈管理系统
安卓王国的任务栈管理系统通过四大核心机制协同工作:
- 层级化管理:AMS > StackSupervisor > ActivityStack > TaskRecord
- 状态精确跟踪:9大ActivityState + 特殊状态标记
- 多维度规则:LaunchMode(静态) + Intent Flags(动态)+ taskAffinity(归属)
- 智能匹配算法:rootAffinity匹配 + LRU管理 + 状态决策
正是这套精密系统,使得我们能在抖音看视频时跳转微信回复消息,再返回抖音时无缝衔接,仿佛一切从未中断。就像魔术师手中的卡牌,看似简单的界面切换背后,是安卓系统工程师精心设计的栈管理魔术。