6、LaunchMode 深度解析:standard、singleTop、singleTask、singleInstance 原理与场景
说起 LaunchMode,这可能是面试里被问烂了的话题。但说实话,很多人只是背了背四种模式的定义,真到项目里一用,各种奇奇怪怪的 bug 就冒出来了。我这些年排查过不少启动模式导致的问题,今天就把我的理解掰开了讲给你听。
为什么需要 LaunchMode?
先想一个问题:你点开一个 App 里的通知,跳到一个详情页,然后按返回键——你希望回到哪里?
是回到桌面?还是回到 App 的主页?
不同的场景,对「任务栈」的管理要求完全不同。LaunchMode 就是用来控制 Activity 在任务栈中的行为规则的。说白了,它决定了「新 Activity 是新建实例,还是复用已有的实例」。
四种模式逐一拆解
1. standard:默认模式,来一个建一个
这是默认模式,也是最简单的。每次启动 Activity,系统都会创建一个新的实例,压入当前任务栈的栈顶。
<activity android:name=".DemoActivity" android:launchMode="standard" />
举个例子:你连续三次 startActivity 启动同一个 Activity,栈里就会有三个一模一样的实例。按返回键时,你得按三次才能退出。
我遇到过的一个坑: 有个同事在 Service 里用 standard 模式启动 Activity,结果每次收到推送都新建一个页面,用户按返回键时发现有一堆重复页面。这就是典型的「无节制创建实例」问题。
我的建议: standard 模式适合「每次都需要全新页面」的场景,比如输入表单、编辑页面。但要注意避免在非 Activity 上下文(如 Service、BroadcastReceiver)中启动,需要加上 FLAG_ACTIVITY_NEW_TASK。
2. singleTop:栈顶复用,省点资源
singleTop 和 standard 很像,但有一个关键区别:如果目标 Activity 已经在栈顶,就不会创建新实例,而是复用栈顶的那个,并回调它的 onNewIntent() 方法。
<activity android:name=".NotificationActivity" android:launchMode="singleTop" />
你想想看,通知栏点击跳转的场景就很适合用 singleTop。用户连续点两次通知,如果每次都新建页面,体验就很差。用 singleTop 的话,第二次点击只会触发 onNewIntent,页面不会重复创建。
注意: singleTop 只检查栈顶。如果目标 Activity 不在栈顶,哪怕栈里已经有它的实例,系统还是会新建一个。这一点很多人搞混。
我记得有一次排查 bug,发现某个页面在特定流程下出现了两个实例。查了半天,原来是用户从 A -> B -> A,B 在栈顶,A 不在栈顶,所以 singleTop 没起作用。嗯,这个细节一定要记住。
3. singleTask:栈内唯一,清除顶上
singleTask 是四种模式里最常用的,也是最容易出问题的。它的规则是:系统会寻找目标 Activity 所在的任务栈,如果栈里已经有它的实例,就把这个实例之上的所有 Activity 全部出栈,然后把这个实例拉到栈顶。
<activity android:name=".MainActivity" android:launchMode="singleTask" />
说白了,singleTask 保证了「栈内唯一」。比如你的 App 主页,用 singleTask 就很合适。用户从主页跳到详情页,再跳回主页时,详情页会被清除掉,用户按返回键就直接回到桌面了。
我曾经踩过的坑: 有个项目把登录页设成了 singleTask,结果用户从 A 页面跳登录,登录成功后想返回 A 页面,却发现 A 页面被清掉了。因为 singleTask 会把栈顶之上的 Activity 全部出栈。后来我改成了 singleTop + 手动处理返回逻辑才解决。
4. singleInstance:独占一个栈,与世隔绝
singleInstance 是最特殊的模式。它会让 Activity 独自占用一个任务栈,而且这个栈里只能有它一个 Activity。如果其他 App 或本 App 的其他组件想启动它,都会共享这个独立的栈。
<activity android:name=".LauncherActivity" android:launchMode="singleInstance" />
这个模式用得很少,但一旦用对地方就很强大。比如系统级的来电界面、闹钟提醒,它们需要独立于当前 App 的栈结构,不受其他页面干扰。
我的经验: 普通 App 开发中,我几乎不用 singleInstance。因为它会让任务栈变得复杂,用户在多任务切换时容易困惑。除非你有非常明确的「独立栈」需求,否则慎用。
四种模式对比一览
| 模式 | 创建新实例? | 复用条件 | 典型场景 |
|---|---|---|---|
| standard | 总是创建 | 无 | 普通页面、表单页 |
| singleTop | 栈顶时不创建 | 目标在栈顶 | 通知跳转、搜索页 |
| singleTask | 栈内唯一 | 目标在栈内任意位置 | 主页、首页 |
| singleInstance | 独占一个栈 | 全局唯一 | 来电界面、系统级弹窗 |
核心流程图:四种模式的任务栈行为
下面这张图展示了四种模式下,连续启动 Activity 时任务栈的变化。我画得比较直观,你可以对照着理解。
避坑指南:我踩过的那些坑
- singleTask 和 FLAG_ACTIVITY_CLEAR_TOP 的区别: 很多人以为它们一样,其实不然。FLAG_ACTIVITY_CLEAR_TOP 是 Intent 的 flag,可以配合 standard 模式使用;而 singleTask 是静态声明,两者行为有细微差别。我建议能用静态声明就用静态声明,代码里少写 flag,可读性更好。
- singleInstance 和 taskAffinity 的冲突: 如果你给 singleInstance 的 Activity 设置了不同的 taskAffinity,系统会创建新的任务栈。这时候 singleInstance 的「独占」特性可能会被打破。嗯,这个坑我调试了整整一个下午。
- onNewIntent 的调用时机: 只有复用已有实例时才会回调 onNewIntent。而且要注意,如果 Activity 被系统回收了,重新创建时走的是正常生命周期,不会走 onNewIntent。我建议在 onNewIntent 和 onCreate 里都做一下数据刷新,保证万无一失。
总结一下
LaunchMode 的选择没有绝对的对错,关键看场景。我个人习惯是:普通页面用 standard,通知跳转用 singleTop,主页用 singleTask,singleInstance 能不用就不用。当然,如果你在做系统级应用,singleInstance 还是很有用的。
最后提醒一句:LaunchMode 只是静态声明,你还可以通过 Intent Flag 在运行时动态覆盖。比如 FLAG_ACTIVITY_NEW_TASK、FLAG_ACTIVITY_CLEAR_TOP、FLAG_ACTIVITY_SINGLE_TOP 等。两者结合使用,才能应对各种复杂场景。
记住一句话:理解任务栈,才能用好 LaunchMode。 栈的结构决定了页面的返回行为,也决定了用户的操作体验。下次遇到页面跳转异常,先画一画栈图,问题往往一目了然。
公众号:蓝海资料掘金营,微信deep3321