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 任务栈切换流程图

Activity 启动模式 – 任务栈决策流程 启动 Activity 解析启动模式 standard 创建新实例 加入当前栈顶 singleTop 栈顶复用? singleTask 清除栈顶Activity 复用该实例 singleInstance 复用或创建新栈 栈内唯一实例 Activity 进入前台,生命周期回调执行 系统通过 ActivityRecord 中的 launchMode 字段和 Intent Flag 联合决策

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 避免栈归属混乱。