Activity 的 launchMode 与 Intent 的 flags

Activity 的 launchMode 与 Intent 的 flags

task

也就是任务(栈),是一系列 Activity 的集合,每打开一个 Activity 都会将其放入到对应的任务栈中。按返回键时栈中的 Activity 会依次出栈,从而达到了逐级返回上一个界面的效果。当我们按下手机上的多任务键(菜单键)时,系统的多任务界面就会展示最近 task,注意:这里展示的并不是应用,而是 task,如果一个应用当前有 2 个 task,就会在这个界面有 2 个同名截屏页,如下图所示:

图片名称
当两个 Activity 之间切换时如果是跨 task 切换,如下文中的 singleTask 或 singleInstance,会发现切换动画明显不一样,类似于应用之间的切换动画。仔细想想这是非常合理的,因为跨 task 之间切换 Activity,大部分都是应用之间的交互,比如应用 A 打开 QQ 的分享界面,系统使用应用之间的切换动画,明确的告知用户“你现在跳到别的 APP 了,而不再是原来的 APP 了”。所以判断前后两个 Activity 是否在同一个任务栈,有个简单办法就是看切换动画:如果切换动画是轻量级的 Activity 之间的切换动画,那就是同一个任务栈;如果切换动画是应用之间的切换动画,那就是不同的任务栈,当然以上都是基于没有自定义 Activity 之间切换动画的前提下。也可以通过 Activity 的 `getTaskId()` 方法获取 taskId 来判断是否属于一个 task。
Log.i(TAG, "当前taskId为$taskId");

Task分为前台Task和后台Task,前台Task之间可以叠加,并会在回退时依次露出来。前台Task进入后台后,将不会叠加。前台Task进入后台,常见操作有2种,按下home键和按下多任务键。举个例子:现在从上到下依次有4个ActivityD、C、B、A,A和B在一个任务栈,上方的C和D是一个任务栈,直接按返回键,将会是D、C、B、A依次出栈并回到桌面,如果按了一下多任务键再切回来,此时按返回键,因为此时A和B所在的任务栈已经转为后台任务栈了,所以将会是D、C依次出栈就回到桌面了。

launchMode

也就是 Activity 的启动模式,可以在 manifest 为 Activity 指定。有如下 4 种:

launchMode说明
standard默认模式,每当有一次 Intent 请求,就会创建一个新的 Activity 实例
singleTop栈顶复用模式, 如果当前 Activity 正在调用者的栈顶,就直接复用,并使用 onNewIntent 刷新数据
singleTask栈内复用模式。被启动时如果需要的任务栈已存在并且站栈内有这个 Activity 的示例,则直接复用,并且会同时清掉栈内这个 Activity 上边的所有 Activity;如果被启动时只有需要的任务栈,而没有 Activity,那就创建 Activity;如果被启动时没有需要的任务栈,则新建任务栈并新建 Activity 示例。
singleInstance单示例模式,独占一个任务栈,并且栈内复用

1 standard

默认模式,每当有一次 Intent 请求,就会创建一个新的 Activity 实例。

使用场景:大部分没有特殊需求的 Activity 都是这种模式。

2 singleTop

栈顶复用模式, 如果当前 Activity 正在调用者的栈顶,就直接复用,并使用 onNewIntent 回调刷新数据。因为没有创建,所以不会走 onCreate、onStart 等生命周期方法。如果 Activity 已存在但是不位于栈顶,仍然会创建新的实例。

3 singleTask

栈内复用模式, 可以使用 taskAffinity 指定任务栈名称。启动时分为三种情况:

① 被启动时如果需要的任务栈已存在并且站栈内有这个 Activity 的示例,则直接复用,并且会同时清掉栈内这个 Activity 上边的所有 Activity;

② 如果被启动时只有需要的任务栈,而没有 Activity,那就创建 Activity;

③ 如果被启动时没有需要的任务栈,则新建任务栈并新建 Activity 示例。

使用场景:用于给其他应用提供一些通用功能,如分享、文件处理等等。

4 singleInstance

单示例模式,独占一个任务栈,并且栈内复用。

使用场景类似于singleTask。

taskAffinity

指定了 Activity 所需要的的任务栈的名称,如果不设置则默认为应用 package name。由于 standard 和 singleTop 的任务栈由启动它的 Activity 决定(与启动它的 Activity 的栈相同),所以 standard 和 singleTop 的 Activity 设置这个属性是没有用的。我们设置这个属性的目的一般有 2 种:① 指定某个 Activity 与其他 Activity 栈不同;② 指定几个 Activity 共用相同的栈。而 singleInstance 的 Activity 无论如何栈都不会与其他 Activity 相同,并且也不会与其他 Activity 共用栈,所以这个属性在 singleInstance 模式下也是无意义的。综上所述, 这个属性只会与 singleTask 模式配合使用,以及与 allowTaskReparenting 配合使用。使用FLAG_ACTIVITY_NEW_TASK 的Intent启动Activity时,这个属性是有意义的,用于指定 Activity 所需要的的任务栈的名称

allowTaskReparenting

是否允许 Activity 重新移动,默认 false。一个 Activity 实例默认只会在一个栈中不会发生转移,当设置 allowTaskReparenting 为 true ,并且与这个 Activity 的 taskAffinity 相同的 Task 启动时,就可以发生转移。

举个例子:应用 A 调用了邮件 APP 里的编写邮件 Activity(allowTaskReparenting 为 true),此时编写邮件 Activity 所属的 Task 与它的调用者相同,如果再从桌面点击邮件 APP 的图标,正常情况下看到的是邮件 APP 的 MainActivity,由于此时编写邮件 Activity 已经被打开了,所以它会被转移到邮件 APP 的 Task 里,用户点击邮件 APP 直接看到之前编写了一半的内容,体验比较好。
如果一个 Activity 既要被别的 APP 打开,又需要在自己 APP 打开时直接看到,用这个属性就比较合适。然而这个属性在 Android 9.0 和 Android10.0 上不好使,要慎用。

Intent 的 Flags

使用 Intent 启动 Activity 时,通过设置 Intent,同样可以影响 Activity 的启动模式。例如:

val intent = Intent(this, Activity1::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent);

通过 java 代码设置 Intent 的 Flags 和 manifest 中指定都可以设置 Activity 的启动模式,但是 java(kotlin)代码运行时设置优先级更高,二者同时存在时,以 java 代码为准。manifest无法设置FLAG_ACTIVITY_CLEAR_TOP的效果,Java代码无法实现singleInstance的效果。下面介绍几种常用的 Flag:

FLAG_ACTIVITY_NEW_TASK

同 singleTask 为什么划线,是因为这个说法是错的,官方文档错了,很多照着官方文档写博客的也错了。

FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP 同 singleTask 这也是错误的说法。下边描述几种代码实验结果证明为什么是错的。

情况 1:ActivityA 和 B 都未设置 taskAffinity 和 launchMode,A 使用 FLAG_ACTIVITY_NEW_TASK 打开 B,B 再使用 FLAG_ACTIVITY_NEW_TASK 打开 A,最终栈中从上到下依次为: A B A。结果显示 A 并没有被复用,所以 A 没有执行 onNewIntent,A 被重新创建了。如果 launchMode 是 singleTask,那么最终的结果应该是栈中只有 A,因为 B 被退栈了,同时由于复用了 A,所以 A 的 onNewIntent 会被执行。

情况 2:ActivityA 和 B 都设置 taskAffinity 为 test(与包名不同),都未设置 launchMode,先由 MainActivity 使用 FLAG_ACTIVITY_NEW_TASK 打开 A,然后 A 使用 FLAG_ACTIVITY_NEW_TASK 打开 B,此时一共有 2 个 Task,MainActivity 所在的默认 Task 以及 A 和 B 所在的 test 的 Task,一切合乎预期。如果此时在 B 中继续用 FLAG_ACTIVITY_NEW_TASK 打开 A,你会发现什么效果都没有,A 没有被复用,A 也没被重新打开,B 也没有退栈。如果是 singleTask,此时应该有 2 个 Task,第一个是 Main 所在的 Task,第二个 Task 中只有 A,因为 B 出栈了,并且 A 会被复用。
这两种情况下,FLAG_ACTIVITY_NEW_TASK 和 singleTask 都不一致,并且第一种情况和第二种情况下,FLAG_ACTIVITY_NEW_TASK 自己的结果都不一致(两种情况下,singleTask 表现是一致的)。 所以说官方文档说的 FLAG_ACTIVITY_NEW_TASK 同 singleTask 是错误的

下边再用代码实验结果证明为什么 singleTask 与 FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP 也不相同。
类似于上边的情况 2,同样是 ActivityA 和 B 都设置 taskAffinity 为 test(与包名不同),都未设置 launchMode,以下打开操作都是使用 FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP,A 先打开 B,B 再打开 A,结果是 B 出栈了栈中只剩 A,看起来确实跟 singleTask 很像,然而这里的 A 是重新创建的 A,并不是复用之前的 A,不会执行 onNewIntent,跟 singleTask 还是不一样。

看到这个结果,我一开始以为我的小米手机系统被魔改了,导致的 bug,后来用 AS 自带模拟器试了一下,在 Android7.0 和 Android 11 上也是这个结果,这就让人摸不着头脑了。写到这里,我依旧没有搞清楚 FLAG_ACTIVITY_NEW_TASK 到底是个什么规律,好在目前这个特性用的不是很多,主要用来从 Service 等非 Activity Context 下启动 Activity。官方文档靠不住,很多博客也是抄的官方文档,看了只能读源码了。

FLAG_ACTIVITY_SINGLE_TOP

singleTop 

FLAG_ACTIVITY_CLEAR_TOP

官方文档描述是错误的。

正确描述为:具有此标记位的 Activity,当它启动时,在同一个任务栈中包括自己以及所有位于它上面的 Activity 都要出栈,同时会在栈顶再次创建一个实例。FLAG_ACTIVITY_CLEAR_TOP 最常与 FLAG_ACTIVITY_NEW_TASK 结合使用,将这两个标记结合使用,可以查找其他任务中的现有 Activity,并将它本身及以上的 Activity 全部出栈,同时在栈顶再次创建一个实例。使用这个标记位,无法达到复用 Activity 的目的。

FLAG_ACTIVITY_FORWARD_RESULT

使用这个标记位启动 Activity 时,新启动的 Activity 的 setResult 会直接返回给上一级。举例说明比较直观:A startActivityForResult 打开 B,B 通过这个标志位 打开 C,C 的 setResult 会跨过 B,直接回传给 A(onActivityResult 接收)。需要注意的是 使用这个标志位启动Activity时不能用startActivityForResult , 因为使用这个标记位就代表着当前 Activity 不想处理返回结果,请直接返回给上一级,与 startActivityForResult 是矛盾的。如果有一长串的 startActivityForResult 就可以用这个标志位进行简化,中间的 Activity 无须处理 onActivityResult。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值