2. Activity任务栈模型详解:标准栈、singleTop、singleTask、singleInstance
Android 的任务栈(Task Stack)是 ActivityManager 管理 Activity 实例的核心数据结构。每个任务栈以后进先出(LIFO)方式组织 Activity,控制返回导航与生命周期。本章深入剖析四种启动模式在任务栈中的实际行为、系统判定逻辑、配置方法与调试手段。
2.1 任务栈基本结构与默认行为
默认情况下(standard 模式),每个新启动的 Activity 都会被压入发起者所在任务栈的栈顶,并成为前台 Activity。按 BACK 键时栈顶 Activity 出栈并销毁,直到栈空则返回 Home 屏幕。
关键数据结构:
ActivityStack 内部维护 ArrayList<ActivityRecord>,ActivityRecord 关联唯一的 TaskRecord。Android 10+ 引入 ActivityTaskManagerService 统一管理多 display 的任务栈。
查看当前任务栈状态(开发环境 USB 调试):
adb shell dumpsys activity activities | grep "Stack\|TaskRecord\|Hist"
2.2 四种启动模式核心差异
| 启动模式 | XML 属性值 | 栈行为 | 新实例创建规则 | 典型场景 |
|---|---|---|---|---|
| standard | android:launchMode="standard" |
每次启动都创建新实例,加入当前任务栈栈顶 | 始终创建 | 常规页面,如列表 → 详情 |
| singleTop | android:launchMode="singleTop" |
如果目标 Activity 已在栈顶,复用实例并回调 onNewIntent();否则创建新实例 |
仅在栈顶已有时不创建 | 通知栏点击、搜索框跳转 |
| singleTask | android:launchMode="singleTask" |
系统会为新任务创建独立栈(或复用已存在的栈),并将栈内该 Activity 之上的所有 Activity 全部出栈 | 如目标 Activity 所在栈已存在且栈顶不是它,则清除其上 Activity 并复用 | 主界面、浏览器主页 |
| singleInstance | android:launchMode="singleInstance" |
该 Activity 独占一个任务栈,且不会再有其他 Activity 进入该栈 | 当前栈已存在则复用,否则创建新栈 | 来电界面、闹钟响铃 |
常见误解:
singleTask 和 singleInstance 的“复用”并不仅限于栈顶。系统会检索 所有任务栈(不限于当前前台栈),如果找到匹配的 TaskRecord,则将其整个栈带到前台并执行 clearTop 操作。这可能导致用户丢失当前页面上下文。
2.3 任务栈切换流程图
2.4 配置方法
方式一:AndroidManifest.xml 中声明 launchMode:
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:taskAffinity="com.example.main">
...
</activity>
方式二:通过 Intent Flag 动态覆盖(优先级高于 XML):
Intent intent = new Intent(context, TargetActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
Flag 与 launchMode 联合规则:
FLAG_ACTIVITY_NEW_TASK 强制在新栈中启动;FLAG_ACTIVITY_CLEAR_TOP 等价于 singleTask 的清除栈顶行为;FLAG_ACTIVITY_SINGLE_TOP 等价于 singleTop。可同时组合使用。
2.5 调试与验证
- 使用 dumpsys 实时观察栈变化:
adb shell dumpsys activity activities > stack.log然后搜索TaskRecord的taskId和affinity。 - 日志标记:在
onCreate和onNewIntent中输出Log.d("StackDemo", "taskId=" + getTaskId() + " instance=" + this)。 - 开发者选项「不保留活动」:关闭后可测试活动复用场景,避免被系统回收干扰。
2.6 常见错误与性能风险
| 错误场景 | 原因 | 修复方案 |
|---|---|---|
| Flutter / React Native 混合栈中页面重复创建 | 平台插件默认 standard 模式,导致单例 Activity 被多次压栈 | 设置 singleTask 并指定 taskAffinity |
| 按 Back 键回到非预期页面 | singleInstance Activity 独占总栈后,后续页面被添加到其他栈,返回导航混乱 |
使用 finishAffinity() 或合理设计栈结构 |
| 多 taskAffinity 导致栈数量激增,短时内存飙升 | 每启动一个不同 affinity 的 Activity 就会创建新 TaskRecord | 控制 taskAffinity 种类,避免动态生成随机 affinity |
2.7 性能与稳定性优化要点
- 减少跨栈启动:跨栈会触发
resumeTopActivityInnerLocked中的栈切换开销(如窗口动画、焦点转移),核心场景(如首页跳转)可使用 singleTop 避免新栈。 - 避免
clearTop时大量 Activity 销毁:若 singleTask 目标 Activity 下压了 20+ 个 Activity,系统会依次调用onPause→onStop→onDestroy,可能导致 UI 线程卡顿。建议控制栈深度不超过 10 层。 - 使用
ActivityManager#getAppTasks()监控栈状态(API 21+):ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); List<ActivityManager.AppTask> tasks = am.getAppTasks(); for (ActivityManager.AppTask task : tasks) { Log.d("StackInfo", "taskId=" + task.getTaskInfo().taskId); } - 多进程场景:不同进程的 Activity 会拥有独立的栈,注意使用
android:process属性时需显式设置taskAffinity避免栈归属混乱。