技术复核 · 2026-10-13。 本文保留 2019 年四种启动模式的操作记录,按 AOSP android-9.0.0_r1、android-16.0.0_r1 核对任务选择、affinity 与最近任务行为,并补充 Android 12(API 31)的 singleInstancePerTask。旧记录未注明设备完整版本,本次没有真机重跑,不能当作当前系统的逐步实测。
Android 中的 Activity 有几种重要的启动模式:standard、singleTop、singleTask、singleInstance。网上讲这些模式的文章不少,我这里写了个 Demo,想从任务中的 Activity 排列给大家看它们的区别;较新的 singleInstancePerTask 放在后面补充。
普通任务内导航通常后进先出。下方 A -> B 表示操作顺序;“栈内容”按顶部在前列出实际排列。留意每一步之后的变化,边操作边对照会好理解一些。重复发起启动不一定增加实例。Task 是系统管理的任务,不等于进程,也不等于最近任务列表中的一张卡片。
Demo 我也放到了 GitHub:AndroidLunchModeTest 。本次核对的快照为 eaa1f987e332e0f85187a911ad9ae01cd20fff05 。工程 compile/target SDK 29、min SDK 26;源码包名为 com.example.lunchmodetest,与下方保留的旧日志 com.example.launchmodetest 不同。
运行前需要按每个案例核对所有跳转目标:快照中 MainActivity.startSingleTopActivity 实际跳到 SingleTaskActivity,StandardActivity 默认跳到 SingleInstanceWithAffinityActivity,仅改一个目标无法自动跑完全部案例。类名 WithAffinity 表示旧命名,实际 manifest 配置为空 affinity;SingleTopWithAffinityActivity 甚至未声明 singleTop,不能凭类名判断模式。本次只核查代码,没有改动或编译这个示例工程。
下面先按同一用户、普通前台 Activity 发起、无额外 Intent flags 的条件解释。若把旧工程升级到 target SDK 31+,还需为带 intent-filter 的组件显式配置 android:exported ,其中 Launcher 入口设为 true。
Standard 标准模式 最基本的模式。在本文“从 Activity 发起、没有额外改变复用语义的 Intent flags”的条件下,每次启动会创建新实例。Manifest 中的 standard 不能脱离 flags 单独解释所有结果。
1 android:launchMode="standard"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // 1. 启动 Activity MainActivity //栈内容 com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 2. 启动 StandardActivity MainActivity -> StandardActivity //栈内容 com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 3. 启动 StandardActivity MainActivity -> StandardActivity -> StandardActivity //栈内容 com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity
SingleTop 栈顶复用模式 1 android:launchMode="singleTop"
如果目标任务的栈顶已经是要启动的 Activity 实例,则复用并投递 onNewIntent();位于同一任务但不在顶部,仍会新建。这里的“顶部”是所选 Task 的顶部,不是整个设备只有一个栈顶。
测试1 : SingleTopActivity 不在栈顶 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 // 1. 启动 MainActivity MainActivity //栈内容 com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 2. 启动 StandardActivity MainActivity -> StandardActivity //栈内容 com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 3. 启动 SingleTopActivity MainActivity -> StandardActivity -> SingleTopActivity //栈内容 com.example.launchmodetest/.SingleTopActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 4. 启动 StandardActivity MainActivity -> StandardActivity -> SingleTopActivity -> StandardActivity //栈内容 com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.SingleTopActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 5. 启动 SingleTopActivity: MainActivity -> StandardActivity -> SingleTopActivity -> StandardActivity -> SingleTopActivity //栈内容 com.example.launchmodetest/.SingleTopActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.SingleTopActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity //这里由于第三个 SingleTopActivity 不在栈顶,栈顶是 StandardActivity ,所以启动新的 SingleTopActivity 时会重新创建 SingleTopActivity
测试2 : SingleTopActivity 在栈顶 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // 1. 启动 MainActivity MainActivity //栈内容 com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 2. 启动 StandardActivity MainActivity -> StandardActivity //栈内容 com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 3. 启动 SingleTopActivity MainActivity -> StandardActivity -> SingleTopActivity //栈内容 com.example.launchmodetest/.SingleTopActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 4. 启动 SingleTopActivity MainActivity -> StandardActivity -> SingleTopActivity -> SingleTopActivity //栈内容 com.example.launchmodetest/.SingleTopActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity //SingleTopActivity 收到 am_new_intent ,而不是创建新的 Activity
SingleTask 栈内复用模式 1 android:launchMode="singleTask"
系统会按启动条件寻找可复用的任务;范围不只是调用者当前的 Task。未找到合适任务时再创建新 Task。
找到任务后,如果其中已有目标实例,复用它并投递新 Intent,清除它上方的 Activity;如果没有目标实例,可能在选中的同 affinity 任务里加入新实例。
原例中未声明 taskAffinity 的 Activity 继承应用默认 affinity,因此可能落入已有任务。这不是“未写 affinity 就永远留在当前 Task”,也不能说 singleTask 无条件永远在任务根部。
Android 9 的 ActivityStarter.getReusableIntentActivity 与 ActivityStack.findTaskLocked 会结合组件、用户、文档 Intent 和 rootAffinity 查找。Android 16 的 ActivityStarter 仍区分任务选择、清栈和实例复用。
测试1:SingleTask(Without Affinity) 这里的旧称 “Without Affinity” 实际是未显式设置属性,使用默认 affinity 。下面保留原文的操作与任务记录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 // 1. 启动 MainActivity: MainActivity //栈内容: com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 2. 启动 StandardActivity MainActivity -> StandardActivity //栈内容: com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 3. 启动 SingleTaskActivity MainActivity -> StandardActivity -> SingleTaskActivity //栈内容: com.example.launchmodetest/.SingleTaskActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 4. 启动 StandardActivity MainActivity -> StandardActivity -> SingleTaskActivity -> StandardActivity //栈内容: com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.SingleTaskActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 5. 启动 SingleTaskActivity MainActivity -> StandardActivity -> SingleTaskActivity -> StandardActivity -> SingleTaskActivity //栈内容: com.example.launchmodetest/.SingleTaskActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity //备注:SingleTaskActivity 收到 am_new_intent ,将其上面的 StandardActivity Clear 调
测试2:SingleTask(WithAffinity) 示例中的 SingleTaskWithAffinity 实际设置的是 android:taskAffinity="",即无 affinity,类名容易造成误解。空值不能通过共同 affinity 匹配普通任务,所以首次启动、且没有可复用目标任务时会新建 Task;再次启动仍可能通过目标组件匹配复用已有 Task,并非每次新建。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 // 1. 启动 MainActivity MainActivity //栈0内容: com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 2. 启动 StandardActivity MainActivity -> StandardActivity //栈0内容: com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 3. 启动 SingleTaskWithAffinity MainActivity -> StandardActivity -> SingleTaskWithAffinity //栈1内容: com.example.launchmodetest/.SingleTaskWithAffinity //栈0内容: com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 4. 启动 StandardActivity MainActivity -> StandardActivity -> SingleTaskWithAffinity -> StandardActivity //栈1内容: com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.SingleTaskWithAffinity //栈0内容: com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 5. 启动 SingleTaskWithAffinity MainActivity -> StandardActivity -> SingleTaskWithAffinity -> StandardActivity -> SingleTaskWithAffinity //栈1内容: com.example.launchmodetest/.SingleTaskWithAffinity //栈0内容: com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity -------------------------------------------------------------------
总结
本例两种配置选中了不同的 Task;Task 不等于 framework 内部的 ActivityStack,不能据此说必定新建 ActivityStack。
从 SingleTaskWithAffinity 普通启动 StandardActivity 时,后者可以加入该 Task,即使其默认 affinity 不同。
从另一个 Task 再次启动 SingleTaskWithAffinity,可复用目标任务和实例,并清除目标上方的 Activity。
是否在最近任务列表显示独立卡片,还受 Recents 管理、排除标记与系统实现影响,不能只由类名或空 affinity 推断。
SingleInstance 单实例模式 1 android:launchMode="singleInstance"
singleInstance 的任务只容纳该 Activity;它启动的其他 Activity 必须进入其他 Task。对同一用户下的同一目标组件,系统会查找并复用已有实例;不能把它理解为跨用户、跨工作资料或进程死亡后 Java 对象仍“全局常驻”。
下面日志括号里的“多任务里面有/没有 Task”保留了我当时的界面观察,指的是最近任务卡片,不是 Task 是否存在;也不是 launchMode 的固定保证。
测试1:SingleInstance (Without Affinity) 未显式设置 taskAffinity,使用默认值。下方 StandardActivity 回到 Task 0,是该示例中的任务匹配结果;一般情况也可能选中其他已有任务或新建任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 taskAffinity=com.example.launchmodetest // 1. 启动 MainActivity MainActivity //栈0内容 com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 2. 启动 StandardActivity MainActivity -> StandardActivity //栈0内容 com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 3. 启动 SingleInstanceActivity MainActivity -> StandardActivity -> SingleInstanceActivity //栈1内容(多任务里面没有 Task) com.example.launchmodetest/.SingleInstanceActivity //栈0内容 com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 4. 启动 StandardActivity MainActivity -> StandardActivity -> SingleInstanceActivity -> StandardActivity //栈1内容(多任务里面没有 Task) com.example.launchmodetest/.SingleInstanceActivity //栈0内容: com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 5. 启动 SingleInstanceActivity MainActivity -> StandardActivity -> SingleInstanceActivity -> StandardActivity -> SingleInstanceActivity //栈1内: (多任务里面没有 Task) com.example.launchmodetest/.SingleInstanceActivity //栈0内容 com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 6. 启动 StandardActivity MainActivity -> StandardActivity -> SingleInstanceActivity -> StandardActivity -> SingleInstanceActivity -> StandardActivity //栈1内: (多任务里面没有 Task) com.example.launchmodetest/.SingleInstanceActivity //栈0内容 com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 7. 启动 SingleInstanceActivity MainActivity -> StandardActivity -> SingleInstanceActivity -> StandardActivity -> SingleInstanceActivity -> StandardActivity -> SingleInstanceActivity //栈1内容 (多任务里面没有 Task) com.example.launchmodetest/.SingleInstanceActivity //栈0内容 com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity
总结1
singleInstance 独占一个 Task,但不保证隐藏最近任务卡片。
复用需要匹配用户和目标组件;已有 Activity 可收到 onNewIntent。配置变化或进程重建仍可能重新创建客户端 Activity 对象。
从它启动其他 Activity 会走其他任务的选择逻辑,不是“永远回到上一个 Task”。
例如,Android 9 RecentTasks.findRemoveIndexForAddTask 和 Android 16 RecentTasks 对普通任务会比较非空 affinity、基础 Intent 等条件进行条目去重/裁剪。这能解释为何相同 affinity 可能影响卡片,但不能仅凭旧记录确定每次设备界面的具体原因。
测试2: SingleInstance (With Affinity) 此处旧称 “With Affinity” 仍指 android:taskAffinity="",即无 affinity 。原记录出现独立最近任务卡片,只作为当时观察保留;空 affinity 不会取消 singleInstance 的复用规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 // 1. 启动 MainActivity MainActivity //栈0内容: com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 2. 启动 StandardActivity MainActivity -> StandardActivity //栈0内容: com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 3. 启动 SingleInstanceWithAffinityActivity MainActivity -> StandardActivity -> SingleInstanceWithAffinityActivity //栈1内容:(多任务里面有 Task) com.example.launchmodetest/.SingleInstanceWithAffinityActivity //栈0内容: com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 4. 启动 StandardActivity MainActivity -> StandardActivity -> SingleInstanceWithAffinityActivity -> StandardActivity //栈1内容:(多任务里面有 Task) com.example.launchmodetest/.SingleInstanceWithAffinityActivity //栈0内容: com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 5. 启动 SingleInstanceWithAffinityActivity MainActivity -> StandardActivity -> SingleInstanceWithAffinityActivity -> StandardActivity -> SingleInstanceWithAffinityActivity //栈1内容:(多任务里面有 Task) com.example.launchmodetest/.SingleInstanceWithAffinityActivity //栈0内容: com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 6. 启动 StandardActivity MainActivity -> StandardActivity -> SingleInstanceWithAffinityActivity -> StandardActivity -> SingleInstanceWithAffinityActivity -> StandardActivity //栈1内容:(多任务里面有 Task) com.example.launchmodetest/.SingleInstanceWithAffinityActivity //栈0内容 com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity ------------------------------------------------------------------- // 7. 启动 SingleInstanceWithAffinityActivity MainActivity -> StandardActivity -> SingleInstanceWithAffinityActivity -> StandardActivity -> SingleInstanceWithAffinityActivity -> StandardActivity -> SingleInstanceWithAffinityActivity //栈1内容:(多任务里面有 Task) com.example.launchmodetest/.SingleInstanceWithAffinityActivity //栈0内容: com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.StandardActivity com.example.launchmodetest/.MainActivity
Android 12 起:singleInstancePerTask Android 12(API 31)新增 singleInstancePerTask 。它要求目标 Activity 位于任务根部,同一 Task 只有一个该实例,但该 Task 可以继续容纳其他 Activity。
普通重复启动可以复用已有任务并清除目标上方的 Activity;使用 MULTIPLE_TASK 或 NEW_DOCUMENT 等符合条件的启动方式,可以让同一组件在多个 Task 各有实例。它与 singleInstance 的“任务内只有一个 Activity”不同。具体文档模式与 flags 仍要一起看,不能把单一 flag 当成无条件多开保证。原 2019 Demo 没有实现这个模式。
Intent flags 与 onNewIntent Manifest 之外,调用方的 flags 也影响行为。下面只列最容易影响本文实验的组合:
启动方式
影响
SINGLE_TOP
目标已在所选 Task 顶部时可复用,不会自动搜索并清掉中间所有页面
NEW_TASK
进入查找/创建任务的路径,不保证每次新建 Task
CLEAR_TOP
找到目标后清除其上方页面;目标为 standard 且没有 SINGLE_TOP 等复用条件时,目标本身也可能被结束后重建
NEW_TASK + CLEAR_TASK
清除所选任务的旧内容,让新 Activity 成为新的根;不是 CLEAR_TOP 的别名
这些分支可对照 Android 16 ActivityStarter 与 Task.clearTopActivities 。应用从后台启动还需满足对应版本的启动限制;launchMode 本身不授予后台启动权限。
复用不等于自动处理新参数。按照 Activity API ,getIntent() 默认仍返回原 Intent;需要更新它时调用 setIntent,并在回调中处理这次参数。例如在已有 Activity 类内加入:
1 2 3 4 5 6 @Override protected void onNewIntent (Intent intent) { super .onNewIntent(intent); setIntent(intent); }
这个片段只展示 Intent 更新,不是完整 Demo。记录 onCreate/onNewIntent、getTaskId 和操作前后任务内容,才能区分“新 Task”“新 Activity”和“旧实例收到新 Intent”;原日志里的 am_new_intent 是历史系统事件名,不保证新版本继续使用同名日志。
一些概念 TaskAffinity taskAffinity 表示 Activity 与任务的亲和关系。它是任务选择或允许 reparenting 时的一个条件,不是“相同 affinity 的 Activity 必定同属一个 Task”。普通 Activity 内的 startActivity 可以把不同 affinity 的 Activity 放进调用者任务;同一应用也可以拥有多个任务。
配置
含义
不写 activity 的 taskAffinity
继承 <application> 的值;应用未指定时默认是包名
android:taskAffinity=""
无 affinity;dump 中通常表示为 null
android:taskAffinity="com.example.launchmodetest.detail"
一个显式非空 affinity,不是 Task ID
NEW_TASK 相关查找及 allowTaskReparenting 会使用 affinity,具体还要结合组件、用户、flags 和任务状态。它不定义权限,也不让不同应用自动共享进程。属性定义见 activity manifest 文档 。
ActivityRecord、TaskRecord、ActivityStack 之间的关系 这些名称对应旧 AMS 容器结构,适合阅读本文历史图;不能原样套到所有新版本。
ActivityRecord 是 system_server 侧的一条 Activity 记录。同一个 Activity 类可有多条记录;记录不等于应用进程中某个 Java 对象永久存活。
TaskRecord 管理一个任务中的 ActivityRecord。普通导航通常表现为后进先出,清栈、重排和跨任务启动会改变这个过程。
ActivityStack 管理多个 TaskRecord;ActivityDisplay 管理对应显示上的 stacks,ActivityStackSupervisor 协调它们。显示对象不是全系统永远只有一个,stack 数量也不固定为 Home/App/Recents 三个。
原图引用的 Gityuan 文章 发布于 2017 年。第二张图中的旧层级可帮助理解容器关系,但不是现代多窗口结构图。
下面保留原文 adb shell dumpsys activity containers 的一次历史输出。图中显示 display 0、numStacks=5,已经说明“只有三个 stack”过于简化;其中 Task 671 包含多个 ActivityRecord,Task ID、stack ID、PID 是不同标识。
以 android-16.0.0_r1 为例,容器体系使用 RootWindowContainer 、DisplayContent、TaskDisplayArea、Task 与 TaskFragment 。Task 本身继承 TaskFragment;root task/leaf task、嵌套 Task 和嵌入式 Activity 使层级不能机械替换成旧图的三层。旧 TaskRecord/ActivityStack 名称不是跨版本 API。dump 子命令和格式也按设备版本查看,本次没有生成新的设备 dump。
Activity 的几种类型 以下是 Android 9 WindowConfiguration 中的 activity type 常量,不是 launchMode 。例如 type=standard 与 android:launchMode=”standard” 是两个维度。
1 2 3 4 5 6 7 8 9 10 public static final int ACTIVITY_TYPE_UNDEFINED = 0 ;public static final int ACTIVITY_TYPE_STANDARD = 1 ;public static final int ACTIVITY_TYPE_HOME = 2 ;public static final int ACTIVITY_TYPE_RECENTS = 3 ;public static final int ACTIVITY_TYPE_ASSISTANT = 4 ;
Android 16 WindowConfiguration 还定义了 ACTIVITY_TYPE_DREAM = 5。这些是 framework 内部的角色分类,不是让应用通过 launchMode 声明自己为 Home、Recents 或 Assistant 的入口。
如果觉得文章有帮助, 欢迎分享到社交网站 , 希望能帮到大家.
关于我 && 博客 下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!
博主个人介绍 :里面有个人的微信和微信群链接。
本博客内容导航 :个人博客内容的一个导航。
个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
Android性能优化知识星球 :付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。
一个人可以走的更快 , 一群人可以走的更远