Activity 的启动模式

而如果用户在打开应用 B 时并没有回到桌面,而是直接通过应用 A 启动了应用 B 的话,用户点击返回键的过程中依次展现的页面就会是 Activity 4 -> Activity 3 -> Activity 2 -> Activity 1 -> 桌面

返回栈所表示的就是当用户不断回退页面时所能看到的一系列 Activity 的集合,而这些页面可能是处于多个不同的任务栈中。在第一种情况中,返回栈只包含 Task B 一个任务栈,所以当 Task B 被清空后就会直接回到桌面。在第二种情况中,返回栈中包含 Task A 和 Task B 两个任务栈,所以当 Task B 被清空后也会先切回到 Task A,等到 Task A 也被清空后才会回到桌面

需要注意的是,返回栈中包含的多个任务栈之间并没有强制的先后顺序,多个任务栈之间的叠加关系可以随时发现变化。例如,当应用 A 启动了应用 B 后,Task B 是处于 Task A 之上,但之后如果应用 B 又反向启动了应用 A 的话,Task A 就会重新成为前台 Task 并覆盖在 Task B 之上

3、taskAffinity

返回栈这个概念对应的就是 taskAffinity,是 Activity 在 AndroidManifest 文件中声明的一个属性值。taskAffinity 翻译为“亲和性”,用于表示特定 Activity 倾向于将自身存放在哪个任务栈中

在默认情况下,同一应用中的所有 Activity 会具有相同的亲和性,所有 Activity 默认会以当前应用的 applicationId 作为自己的 taskAffinity 属性值。我们可以手动为应用内的部分 Activity 指定特定的 taskAffinity,从而将这部分 Activity 进行分组

<activity

android:name=“.StandardActivity”

android:launchMode=“standard”

android:taskAffinity=“task.test1” />

<activity

android:name=“.SingleTopActivity”

android:launchMode=“singleTop”

android:taskAffinity=“task.test2” />

<activity

android:name=“.SingleTaskActivity”

android:launchMode=“singleTask”

android:taskAffinity=“task.test3” />

<activity

android:name=“.SingleInstanceActivity”

android:launchMode=“singleInstance”

android:taskAffinity=“task.test4” />

从概念上讲,具有相同 taskAffinity 的 Activity 归属于同一任务栈(实际上并不一定)。从用户的角度来看则是归属于同一“应用”,因为每种 taskAffinity 在最近任务列表中会各自独占一个列表项,看起来就像一个个单独的应用,而实际上这些列表项可能是来自于同个应用

4、启动模式

Activity 的启动模式是一个很复杂的难点,其决定了要启动的 Activity 和任务栈还有返回栈之间的关联关系,直接影响到了用户的直观感受

启动模式就由 launchMode 和 Intent flag 这两者来共同决定,我们可以通过两种方式来进行定义:

  • 在 AndroidManifest 文件中为 Activity 定义 launchMode 属性值,一共包含四种类型的属性值

  • 当通过 startActivity(Intent) 启动 Activity 时,向 Intent 添加或设置 flag 标记位,通过该 flag 来定义启动模式

如果只看四个 launchMode 的话其实并不难理解,可是再考虑多应用交互还有 Intent flag 的话,情况就会变得复杂很多,其复杂性和难点主要就在于:单个任务栈包含的 Activity 可以是来自于不同的应用、单个应用也可以包含多个任务栈、返回栈包含的多个任务栈之间也可以进行顺序切换、甚至任务栈中的 Activity 也可以被迁移到另外一个任务栈、Intent flag 可以多个组合使用

有些启动模式可通过 launchMode 来定义,但不能通过 Intent flag 定义,同样,有些启动模式可通过 Intent flag 定义,却不能在 launchMode 中定义。两者互相补充,但不能完全互相替代,且 Intent flag 的优先级会更高一些

5、launchMode

launchMode 一共包含以下四种属性值:

  • standard。默认模式。系统会在启动该 Activity 的任务栈中创建一个目标 Activity 的新实例,使该目标 Activity 成为任务栈的栈顶。该模式下允许先后启动多个相同的目标 Activity,一个任务栈可以拥有多个目标 Activity 实例,且不同 Activity 实例可以属于不同的任务栈

  • singleTop。如果当前任务栈的顶部已存在目标 Activity 的实例,则系统会通过调用其 onNewIntent() 方法来将 Intent 转送给该实例并进行复用,否则会创建一个目标 Activity 的新实例。目标 Activity 可以多次实例化,不同实例可以属于不同的任务栈,一个任务栈可以拥有多个实例(此时多个实例不会连续叠放在一起)

  • singleTask。如果系统当前不包含目标 Activity 的目标任务栈,那么系统就会先创建出目标任务栈,然后实例化目标 Activity 使之成为任务栈的根 Activity。如果系统当前包含目标任务栈,且该任务栈中已存在该目标 Activity 的实例,则系统会通过调用其 onNewIntent() 方法将 Intent 转送给该现有实例,而不会创建新实例,并同时弹出该目标 Activity 之上的所有其它实例,使目标 Activity 成为栈顶。如果系统当前包含目标任务栈,但该任务栈不包含目标 Activity 实例,则会实例化目标 Activity 并将其入栈。因此,系统全局一次只能有一个目标 Activity 实例存在

  • singleInstance。与 singleTask 相似,唯一不同的是通过 singleInstance 启动的 Activity 会独占一个任务栈,系统不会将其和其它 Activity 放置到同个任务栈中,由该 Activity 启动的任何 Activity 都会在其它的任务栈中打开

四种 launchMode 还是很好理解的,当中比较特殊的应该属 singleTask,使用 singleTask 标记的 Activity 会有将自己存放在特定任务栈的倾向。如果目标任务栈和目标 Activity 都已经存在,则会进行复用,否则才会创建目标任务栈和目标 Activity。singleInstance 则是在 singleTask 的基础上多了一个“独占任务栈”的特性

采用 singleTask 启动的 Activity 添加到返回栈的过程就如下图所示。一开始返回栈中只包含 Activity 1 和 Activity 2 组成的任务栈,当 Activity 2 启动了处于后台的 Activity Y 时,Activity Y 和 Activity X 组成的任务栈就会被转到前台,覆盖住当前任务栈。最终返回栈中就变成了四个 Activity

再来写个 Demo 来验证下这四种 launchMode 的效果

声明四种不同 launchMode 的 Activity,每个 Activity 均声明了不同的 taskAffinity

<activity

android:name=“.StandardActivity”

android:launchMode=“standard”

android:taskAffinity=“task.a” />

<activity

android:name=“.SingleTopActivity”

android:launchMode=“singleTop”

android:taskAffinity=“task.b” />

<activity

android:name=“.SingleTaskActivity”

android:launchMode=“singleTask”

android:taskAffinity=“task.c” />

<activity

android:name=“.SingleInstanceActivity”

android:launchMode=“singleInstance”

android:taskAffinity=“task.d” />

通过打印 Activity 的 hashCode() 方法返回值来判断 Activity 的实例是否被复用了,再通过 getTaskId() 方法来判断 Activity 处于哪个任务栈中

/**

  • @Author: leavesC

  • @Date: 2021/4/16 16:38

  • @Desc:

  • @Github:https://github.com/leavesC

*/

abstract class BaseLaunchModeActivity : BaseActivity() {

override val bind by getBind()

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

bind.tvTips.text =

getTip() + “\n” + "hashCode: " + hashCode() + “\n” + "taskId: " + taskId

bind.btnStartStandardActivity.setOnClickListener {

startActivity(StandardActivity::class.java)

}

bind.btnStartSingleTopActivity.setOnClickListener {

startActivity(SingleTopActivity::class.java)

}

bind.btnStartSingleTaskActivity.setOnClickListener {

startActivity(SingleTaskActivity::class.java)

}

bind.btnStartSingleInstanceActivity.setOnClickListener {

startActivity(SingleInstanceActivity::class.java)

}

log(“onCreate”)

}

override fun onNewIntent(intent: Intent?) {

super.onNewIntent(intent)

log(“onNewIntent”)

}

override fun onDestroy() {

super.onDestroy()

log(“onDestroy”)

}

abstract fun getTip(): String

private fun log(log: String) {

Log.e(getTip(), log + " " + "hashCode: " + hashCode() + " " + "taskId: " + taskId)

}

}

class StandardActivity : BaseLaunchModeActivity() {

override fun getTip(): String {

return “StandardActivity”

}

}

四个 Activity 相继互相启动,查看输出的日志,可以看出 SingleTaskActivity 和 SingleInstanceActivity 均处于独立的任务栈中,而 StandardActivity 和 SingleTopActivity 处于同个任务栈中。说明 taskAffinity 对于 standard 和 singleTop 这两种模式不起作用

E/StandardActivity: onCreate hashCode: 31933912 taskId: 37

E/SingleTopActivity: onCreate hashCode: 95410735 taskId: 37

E/SingleTaskActivity: onCreate hashCode: 255733510 taskId: 38

E/SingleInstanceActivity: onCreate hashCode: 20352185 taskId: 39

再依次启动 SingleTaskActivity 和 SingleTopActivity。可以看到 SingleTaskActivity 被复用了,且在 38 这个任务栈上启动了一个新的 SingleTopActivity 实例。之所以没有复用 SingleTopActivity,是因为之前的 SingleTopActivity 是在 37 任务栈中,并非当前任务栈

E/SingleTaskActivity: onNewIntent hashCode: 255733510 taskId: 38

E/SingleTopActivity: onCreate hashCode: 20652250 taskId: 38

再启动一次 SingleTopActivity,两次 StandardActivity。可以看到 SingleTopActivity 的确在当前任务栈中被复用了,并均创建了两个新的 StandardActivity 实例。说明 singleTop 想要被复用需要当前任务栈的栈顶就是目标 Activity,而 standard 模式每次均会创建新实例

E/SingleTopActivity: onNewIntent hashCode: 20652250 taskId: 38

E/StandardActivity: onCreate hashCode: 252563788 taskId: 38

E/StandardActivity: onCreate hashCode: 25716630 taskId: 38

再依次启动 SingleTaskActivity 和 SingleInstanceActivity。可以看到 SingleTaskActivity 和 SingleInstanceActivity 均被复用了,且 SingleTaskActivity 之上的三个 Activity 均从任务栈中被弹出销毁了,SingleTaskActivity 成为了 task 38 新的栈顶 Activity

E/StandardActivity: onDestroy hashCode: 252563788 taskId: 38

E/SingleTopActivity: onDestroy hashCode: 20652250 taskId: 38

E/SingleTaskActivity: onNewIntent hashCode: 255733510 taskId: 38

E/StandardActivity: onDestroy hashCode: 25716630 taskId: 38

E/SingleInstanceActivity: onNewIntent hashCode: 20352185 taskId: 39

再依次启动 StandardActivity 和 SingleTopActivity。可以看到创建了一个新的任务栈,且启动的是两个新的 Activity 实例。由于 SingleInstanceActivity 所在的任务栈只会由其自身所独占,所以 StandardActivity 启动时就需要创建一个新的任务栈用来容纳自身

E/StandardActivity: onCreate hashCode: 89641200 taskId: 40

E/SingleTopActivity: onCreate hashCode: 254021317 taskId: 40

可以做个总结:

  • standard 和 singleTop 这两种模式下 taskAffinity 属性均不会生效,这两种模式启动的 Activity 总会尝试加入到启动者所在的任务栈中,如果启动者是 singleInstance 的话则会创建一个新的任务栈

  • standard 模式的 Activity 每次启动都会创建一个新的实例,不会考虑任何复用

  • singleTop 模式的 Activity 想要被复用,需要启动者所在的任务栈的栈顶就是该 Activity 实例

  • singleTask 模式的 Activity 事实上是系统全局单例,只要实例没有被回收就会一直被复用。singleTask 可以通过声明 taskAffinity 从而在一个特定的任务栈中被启动,且允许其它 Activity 一起共享同一个任务栈。如果不声明 taskAffinity 的话就会尝试寻找或者主动创建 taskAffinity 为 applicationId 的任务栈,然后在该任务栈中创建或复用 Activity

  • singleInstance 可以看做是 singleTask 的加强版,singleInstance 在任何时候都会独占一个任务栈,不管是否声明了 taskAffinity。在 singleInstance 任务栈中启动的其它 Activity 都会加入到其它任务栈中

需要注意的是,以上结论只适用于没有主动添加 Intent flag 的情况,如果同时添加了 Intent flag 的话就会出现很多奇奇怪怪的现象了

6、Intent flag

在启动 Activity 时,我们可以通过在传送给 startActivity(Intent) 方法的 Intent 中设置多个相应的 flag 来修改 Activity 与其任务栈的默认关联,即 Intent flag 的优先级会比 launchMode 高

Intent 提供的设置 flag 的方法有以下两个,一个是覆盖设置,一个是增量添加

private int mFlags;

public @NonNull Intent setFlags(@Flags int flags) {

mFlags = flags;

return this;

}

public @NonNull Intent addFlags(@Flags int flags) {

mFlags |= flags;

return this;

}

通过如下方式来添加 flag 并启动 Activity

val intent = Intent(this, StandardActivity::class.java)

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)

startActivity(intent)

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

【附】相关架构及资料

源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,和技术大牛一起讨论交流解决问题。

image

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!**

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

[外链图片转存中…(img-zunmRN2A-1713705955389)]

【附】相关架构及资料

源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,和技术大牛一起讨论交流解决问题。

[外链图片转存中…(img-k7l2bPww-1713705955389)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值