Activity 的启动模式

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)

如果 Activity 的启动模式只由 launchMode 定义的话,那么在运行时 Activity 的启动模式就再也无法改变了,相当于被写死了,所以 launchMode 适合于那些具有固定情景的业务。而 Intent flag 存在的意义就是为了改变或者补充 launchMode,适合于那些大部分情况下固定,少数情况下需要动态进行变化的场景,例如在某些情况下不希望 singleInstance 模式的 Activity 被重用,此时就可以通过 Intent flag 来动态实现

而这也造成了 Intent flag 很难理清楚逻辑,因为 Intent flag 往往需要组合使用,且还需要考虑和 launchMode 的各种组合配置,两者并不是简单的进行替换

Intent flag 有很多个,比较常见的有四个,这里就简单介绍下这几种 Intent flag

  • FLAG_ACTIVITY_NEW_TASK

  • FLAG_ACTIVITY_SINGLE_TOP

  • FLAG_ACTIVITY_CLEAR_TOP

  • FLAG_ACTIVITY_CLEAR_TASK

FLAG_ACTIVITY_NEW_TASK

FLAG_ACTIVITY_NEW_TASK 应该是大多数开发者最熟悉的一个 flag,比较常用的一个场景就是用于在非 ActivityContext 环境下启动 Activity。Android 系统默认情况下是会将待启动的 Activity 加入到启动者所在的任务栈,而如果启动 Activity 的是 ServiceContext 的话,此时系统就不确定该如何存放目标 Activity 了,此时就会抛出一个 RuntimeException

java.lang.RuntimeException: Unable to start service github.leavesc.launchmode.MyService@e3183b7 with Intent { cmp=github.leavesc.demo/github.leavesc.launchmode.MyService }: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

从异常信息可以看出此时 Intent 需要添加一个 FLAG_ACTIVITY_NEW_TASK 才行,添加后 Activity 就可以正常启动了

总结

我最近从朋友那里收集到了2020-2021BAT 面试真题解析,内容很多也很系统,包含了很多内容:Android 基础、Java 基础、Android 源码相关分析、常见的一些原理性问题等等,可以很好地帮助大家深刻理解Android相关知识点的原理以及面试相关知识

这份资料把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节;还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

这里也分享给广大面试同胞们,希望每位程序猿们都能面试成功~

Android 基础知识点

Java 基础知识点

Android 源码相关分析

常见的一些原理性问题

腾讯、字节跳动、阿里、百度等BAT大厂 2019-2020面试真题解析

节;还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

这里也分享给广大面试同胞们,希望每位程序猿们都能面试成功~

Android 基础知识点

[外链图片转存中…(img-Qo0VboO5-1719203016556)]

Java 基础知识点

[外链图片转存中…(img-LK1kMBMp-1719203016557)]

Android 源码相关分析

[外链图片转存中…(img-x7CA5F42-1719203016557)]

常见的一些原理性问题

[外链图片转存中…(img-VCgvaOqF-1719203016557)]

腾讯、字节跳动、阿里、百度等BAT大厂 2019-2020面试真题解析

[外链图片转存中…(img-pL9JGVWd-1719203016558)]

  • 26
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值