当面试官要你说一下 Activity 的启动模式时,怎么回答最合适?标准答案在这里
值得一提的是由于返回栈存储结构的特殊性,外部只能访问到栈顶的 Activity,也就是最后入栈的那个。所以一个 Activity 想要能显示在屏幕上那么它必须存在于栈顶位置。
进栈与出栈
当前 Activity 启动另一个 Activity 时,新的 Activity 会被推送到堆栈顶部,成为焦点显示在屏幕上。 前一个 Activity 仍保留在堆栈中,但是处于停止状态。
用户按“返回”按钮时,当前 Activity 会从堆栈顶部弹出(Activity 被销毁),而前一个 Activity 恢复执行。如果用户继续按“返回”,堆栈中的相应 Activity 就会弹出,以显示前一个 Activity,直到用户返回主屏幕为止(或者,返回任务
开始时正在运行的任意 Activity)。 当所有 Activity 均从堆栈中移除后,任务即不复存在。栈也就会被回收掉。
特殊的任务
通过前面的了解,我们知道如果要打开新的界面需要把 Activity 实例放到当前任务对应的返回栈的栈顶。该操作是不管该 Activity 之前有没有实例化过或者栈中是否已经存在了的。
但是,有些特殊情况下,我们会发现一些“例外”。
例 1:当来自多个不同任务中的应用选择使用系统浏览器访问网页的时候,浏览器应用并不会在每个任务的返回栈中都创建 Activity,而是将所有网页以选项卡的形式展示在同一个界面中。
本例中浏览器应用的 Activity 如果已经实例化过了就不会重新创建。
例 2:小明在微信中向你分享了一条微博内容,你打开后跳转到了微博 APP 中的该条微博详情页,当你看完内容后按返回键退出该界面发现并不是回到了微信聊天界面,而是来到了微博主页(或上一次在微博中停留的界面)。
本例中微博详情页的 Activity 虽然是由微信应用所在的任务启动,但是没有加入到微信应用的任务中,而是加入到了微博的任务栈中。
管理任务
很显然上述两个例子在实际使用中并不少见,对于这种特殊的情况我们需要针对性的管理任务,而众所周知的启动模式仅仅是其中的一种。
定义启动模式定义 Activity 的启动模式其实就是定义一个 Activity 的新实例如何(是否)与当前任务做关联。以什么样的方式进入到当前(或其他)任务中。如果你只说 Activity 的启动模式有四种,其实是不准确的,因为我们可以通过两种方法定义不同的启动模式:
使用 AndroidManifest.xml 中定义 在 AndroidManifest.xml 中<activity>标签下使用 lauchMode 属性来指定当前这个 activity 的启动模式。
使用 Intent 标志定义 在调用 startActivity(Intent intent)前,通过调用 intent.addFlags()或者 intent.setFlags()方法为 Intent 添加一个标志,用于为将要启动的 Activity 声明启动模式。
那两者有什么区别呢?
上述两种方法均可以为 activity 声明启动模式,只是使用情景不同。
如果我们希望某个 activity 在任何情况下都会执行一种特殊的启动模式,我们就可以采用 AndroidManifest.xml 的方法声明。
2.如果我们希望某个 activity 大多数情况下正常启动,而少数情况下执行特殊的启动模式,我们就可以在需要执行特殊启动模式时在 Intent 中添加标志声明。
如果一个 activity 两种方式都声明了的话,使用 Intent 标志的方式要比 AndroidManifest.xml 的优先级高。
4.两种方式中定义的启动模式有些是不一样的,Intent 标志中定义的某些启动模式 AndroidManifest.xml 中没有,反之一样。
我们常说的四种启动模式其实说的是 AndroidManifest.xml 中定义的。
使用 AndroidManifest.xml 声明启动模式
在清单文件中声明 Activity 时,您可以使用<activity>元素的 ][launchMode 属性指定 Activity 应该如何与任务关联。
您可以分配给 launchMode 属性的启动模式共有四种: - standard - singleTop - singleTask - singleInstance
先不用管他们具体的操作是什么,我们首先要知道这四种启动模式可以分为两大类:
standard 和 singleTop 该类启动模式的 activity 可以被多次的实例化,它们的实例可以放到任何任务中,并且可以位于返回栈的任何位置。
singleTask 和 singleInstance 带有此类启动模式的 activity,它们只能有一个实例存在,且实例只能存在于单独的任务中。
standard:标准模式
默认启动模式,启动 activity 时直接创建新的实例并压入启动它的任务栈顶。
singleTop:栈顶复用模式
该模式唯一与 standard 不同的就是,如果启动 singleTop 模式的 activity 时发现当前任务的栈顶已经存在着这个 activity 的实例,那么就不会创建新的实例,而是调用该实例的 onNewIntent()方法。其他的跟标准模式一样。
singleTask:栈内复用模式
这个模式有些特殊一点,我们先按使用情景介绍它,当我们将要启动该模式的 activity 时,系统会判断当前是否有它想要的任务栈: 1. 没有它要的任务栈 系统会新创建一个任务,并将该 activity 实例化作为该任务的根 activity。
1.有它要的任务栈 这时候系统会找到该任务栈,如果任务栈里只有它自己则直接调用该 activity 实例的 onNewIntent()方法。如果任务栈中它的上方还存在别的 activity,那么这些 activity 会被全部弹出栈。
至于什么是“它想要的任务栈”,我们会在下面单独分析。
singleInstance:单例模式
基本上跟 singleTask 相同,会为 activity 单独创建一个任务并能够复用。但是该模式的 activity 不允许其他 activity 跟自己存在于同一个任务中,由此 activity 启动的任何 activity 均会被在其他的任务中打开。
使用 Intent 标志声明启动模式
此方式可以通过调用 intent.addFlags(int flags)或者 intent.setFlags(int flags)方法为 Intent 添加一个标志,用于为将要启动的 Activity 声明启动模式。
在开始介绍前,先进行几点扫盲科普:
一个 Intent 可以设置多个标志,这就是为啥有 addflags()和 setFlags()两个方法的原因了。
为 Intent 设置标志的参数都是 Intent 类的静态常量。
设置 Intent 标志不光只有设置 activity 启动模式这一个功能,设置不同的参数还有其他功能。
Intent 标志中可以对 activity 启动模式进行操作的标志可多了,我们只介绍特别典型的三种。
Intent.FLAG_ACTIVITY_SINGLE_TOP 同 AndroidManifest.xml 方式中的 singleTop 启动模式。
Intent.FLAG_ACTIVITY_NEW_TASK 同 AndroidManifest.xml 方式中的 singleTask 启动模式。
Intent.FLAG_ACTIVITY_CLEAR_TOP 如果即将启动的 activity 已经存在于当前任务栈中,则会弹出销毁它上方的所有 activity,并调用该 activity 实例的 onNewIntent()方法,而不是启动该 Activity 的新实例。
跟 singleTask 有点像但不一样,在 AndroidManifest.xml 方式中没有与此对应的值。
singleTask 默认就包含了 FLAG_ACTIVITY_CLEAR_TOP 的功能。
关联任务
在分析 singleTask 时有提到过该模式下启动 activity 前会去找“它想要的任务栈”,那么如何去找呢?这就引出了 AndroidManifest.xml 中<activity>标签下的 taskAffinity 属性。
taskAffinity 属性
taskAffinity 属性学名任务相关性,说白了其实就是这个参数可以指定当前 activity 所属任务栈的名字,该属性的值为字符串:
例:android:taskAffinity="com.test.demo.task1"
如果你在<activity>标签没指定这个属性,那么它就用<application>标签的 taskAffinity 属性,如果<application>标签下也没指定,它就应用包名当做默认值。
taskAffinity 与 singleTask
了解到 taskAffinity 属性后我们在重新梳理一下 singleTask 启动模式。
1.如果我们指定了 taskAffinity 属性的值,那么就跟之前分析的一样,创建新任务等等...
2.如果我们未指定 taskAffinity 属性的值,新 activity 就与当前任务的 taskAffinity 属性值一样,所以新 activity 的实例会被放置到当前的任务栈中。
除了 singleTask 呢?
现在我们知道了 taskAffinity 属性可以强行指定 activity 所属的任务栈,那么这个属性在其他启动模式情况下是什么样的呢?网上好多人都说没有效果,我不信就亲自测试了一下得出以下结论:
刚介绍 SingleInstance 的时候说它跟 singleTask 一样都会新建一个任务,既然 singleTask 是根据 taskAffinity 属性来决定是否需要新建任务的,那么 singleInstance 是不是也需要指定这个属性呢? 答案是否!只要启动模式为 singleInstance 它一定会单独开一个任务。
2.SingleTop 模式下指定了 taskAffinity 属性的值后,他就会神奇的切换到指定的那个任务栈中,除此之外跟原来一样。
3.最神奇的就是 Standard,它也同样受到了 taskAffinity 属性的影响,也会切换到指定的那个任务栈中,但当我们多次启动这个 activity 时它不会再多次的创建实例,而是拉起了之前启动过的实例,更特殊的是,其他三种启动模式在复用之前实例时都会调用 onNewIntent()方法,他却不会调用该方法。
taskAffinity 的其他作用
taskAffinity 还有一个功能就是可以重新定向所属任务,意思就是这个 activity 原来是属于任务 A 的,当有一个跟该 activity 的 taskAffinity 属性值相同的任务 B 被创建后,这个 activity 就会从任务 A 中转移到任务 B 中。
想要实现这个功能我们还需要 allowTaskReparenting 属性的配合:
1.我们在清单文件中给 taskAffinity="A"的 activity 标签下添加属性 android:allowTaskReparenting=true。
2.在 taskAffinity="B"的任务下启动这个 activity,此时这个 activity 存在于任务 B 中。
3.当 taskAffinity="A"的任务被创建或者被置于前台,该 activity 将被转移到其任务栈中,位于栈顶位置。
清理任务
如果用户长时间离开任务,则系统会清除所有 Activity 的任务,根 Activity 除外。 当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。
您可以使用下列几个 Activity 属性修改此行为:
**alwaysRetainTaskState **如果在任务的根 Activity 中将此属性设置为 "true",则不会发生刚才所述的默认行为。即使在很长一段时间后,任务仍将所有 Activity 保留在其堆栈中。
**clearTaskOnLaunch **如果在任务的根 Activity 中将此属性设置为 "true",则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。 即使只离开任务片刻时间,用户也始终会返回到任务的初始状态。
**finishOnTaskLaunch **类似于 clearTaskOnLaunch,但是更狠一些,当用户离开任务再回来的时候,整个任务的 activity 都会清除,连根 activity 也是,相当于第一次启动这个任务。
启动模式的实际应用
个人觉得常见的四种启动模式中要属 singleTop 不是很好理解,其他的还好。
singleTop
singleTop 模式的 activity 特点就是除了外部可以启动它显示信息外,它也可以用同样的方式启动自己更新显示信息,这样就减少了冗余代码,降低了维护成本。
评论