前段时间去面试的时候,有被问到 Activity 的启动模式。虽然这些东西都了解过,但是实际开发中并没有怎么应用过。所以被问到应用场景的时候,答的并不好。趁着有空,打算复习巩固一下。
四种启动模式与应用场景
standard
活动默认的标准启动模式。
每当启动一个新的活动,无论活动的实例是否存在,都会创建一个新的活动的实例,在返回栈中入栈,并处于栈顶的位置。
这个没什么好说的,一般的活动都是这个启动模式。
singleTop
栈顶复用模式。
当要启动的活动在返回栈中没有实例,或者有实例但不位于栈顶位置,则与 standard 模式相同,创建一个新的活动实例并入栈;
如果要启动的活动在返回栈中有实例并且恰好位于栈顶位置,则复用该实例,这个时候,依次执行的方法为:onPause()
-> onNewIntent(Intent intent)
-> onResume()
。
可以看到 singleTop 和 standard 模式最主要的区别就是 singleTop 模式下,如果活动实例存在并恰好位于栈顶则复用,而 standard 则是无论什么情况,都会创建新的实例。
使用场景:推送页,比如优酷的推荐视频、电商的推荐商品等。
singleTask
栈内复用模式。
启动活动时,系统首先会检查返回栈中是否存在该活动的实例,如果存在,则复用该实例。如果该实例处在非栈顶位置,将它上面所有的活动实例出栈,从而位于栈顶位置。这个时候就有两种情况,分别调用的方法也不同,以下执行的方法只针对页面 A 。
(1)要启动的活动恰好已经在栈顶,比如 A -> A
onPause()
-> onNewIntent(Intent intent)
-> onResume()
(2)要启动的活动不在栈顶,比如 A -> B -> A
onNewIntent(Intent intent)
-> onRestart()
-> onStart()
-> onResume()
使用场景:最常见的应该就是 app 的主页,当我们打开了很多页面需要返回主页时,可以利用这种方法。
singleInstance
单实例模式。
如果将一个活动的 launchMode 设置成了 singleInstance,那么启动它的时候,系统会单独给它创建一个新的任务栈,并且该栈只允许这一个活动在其中运行。如果再反复启动该活动,不会创建新的任务栈或者实例,但是会调用 onPause()
-> onNewIntent(Intent intent)
-> onResume()
方法。
这里还有一个需要注意的地方,假如有三个活动 A B C,其中 A B 是默认的启动模式,C 的启动模式是 singleInstance,如果当前的页面启动顺序为 A -> B -> C -> B,那么按下系统返回键,出栈的顺序为 B -> B -> A -> C。这是因为 A B B 在一个任务栈中,而 C 在单独的任务栈中,按照一个栈中先进后出的规则,需要 B B A 全部出栈完毕,才会轮到 C 所在的栈中的元素出栈。
使用场景:一般很少用到,比如说闹钟的提示页、锁屏页等。
使用方式
- 在 AndroidManifest.xml 文件中配置,比如:
<activity
android:name=".ThirdActivity"
android:launchMode="singleInstance">
</activity>
这是最常用的配置方式。
- 使用
intent.addFlags
动态设置,比如:
Intent it = new Intent(ThirdActivity.this, SecondActivity.class);
it.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(it);
注意:两种设置方式还是有区别的。
(1)优先级:第 2 种方式的优先级要比第一种高。我曾经遇到过,在清单文件给活动设置了 singleTop ,但是在快速重复启动活动的时候,发现还是会打开两个页面,如果遇到了这种情况,下文中有解决办法。
(2)区别:第 1 种方式只能给活动设置 4 种基本的启动模式,而第二种方式更加灵活。
Intent.FLAG_#
FLAG 不仅能够设置活动的启动模式,还能够改变获得的运行状态。关于活动的标志位大概有 20 多个,这里只讲几个常用的。
1. FLAG_ACTIVITY_NEW_TASK
Intent it = new Intent(SecondActivity.this, SecondActivity.class);
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(it);
从字面上的意思来看,它会创建一个新的任务栈来存放要启动的活动实例,但是仅仅单独设置了这个标志位的话是没有任何效果的,并不会创建一个新的任务栈,仅仅只是创建了一个新的活动实例,和默认的启动模式的表现上完全一致,你们可以自己去试试。
2. FLAG_ACTIVITY_CLEAR_TASK
Intent it = new Intent(SecondActivity.this, SecondActivity.class);
it.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(it);
同样的,从字面上的意思来看,它的作用是清空任务栈,但是仅仅单独设置了这个标志位的话,也是没有任何效果的,栈不会被清空,仅仅只是创建了一个新的活动并入栈。
3. FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK
Intent it = new Intent(SecondActivity.this, SecondActivity.class);
it.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(it);
虽然他们两个单独使用没有效果,但是放在一起使用就能看到明显的效果了。
假设当前的任务栈的 id 是 2,并且我们将要启动一个新的活动,无论这个活动实例是否已经存在于栈内,都会清空任务栈中的所有元素,创建一个新的活动实例并放到一个新的任务栈中。但是我发现这个新的任务栈的 id 还是 2,不太清楚是直接用了之前的任务栈,还是销毁了之前的任务栈,恰好新的任务栈的 id “刚好排到了 2 号”。看手机上页面的切换效果,应该是后者。有了解的同学,希望能科普一下。
适用场景:适用于需要重新登录的场景,可以直接销毁所有活动然后创建一个新的登录页。
4. FLAG_ACTIVITY_SINGLE_TOP
Intent it = new Intent(ThirdActivity.this, SecondActivity.class);
it.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(it);
与 singleTop 完全相同,不再多说。
5. FLAG_ACTIVITY_CLEAR_TOP
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
这里我们分几种情况讨论,以下每次启动页面时都添加了 FLAG_ACTIVITY_CLEAR_TOP
:
(1)A -> B
AonPause
-> BonCreate
-> BonStart
-> BonResume
-> AonSaveInstanceState
-> AonStop
可以看到,当要启动活动在栈中不存在实例时,和默认启动模式的效果一致。
(2)A -> A
A1onPause
-> A2onCreate
-> A2onStart
-> A2onResume
-> A1onStop
-> A1onDestroy
用 A1 和 A2 方便区分,在这种情况下,在 活动 A 里面再启动一个 活动 A ,会创建一个新的活动实例,并且当新的活动启动完成后,将之前的活动销毁。
(3)A -> B -> A
从活动 B 要启动活动 A 开始依次执行的方法:
BonPause
-> A1onDestroy
-> A2onCreate
-> A2onStart
-> A2onResume
-> BonStop
-> BonDestroy
我测试了更多的页面,比如 A -> B -> C -> D -> E -> B,发现系统创建新的 B 的实例,并且销毁 B C D E。从而可以总结得出 FLAG_ACTIVITY_CLEAR_TOP
的作用:当要启动的活动实例不存在时,创建新的活动实例入栈;当要启动的活动实例已经存在时,创建新的活动实例入栈的同时,会把已存在的活动实例以及栈中它上面所有的活动都销毁掉。
下面是我在开发的时候遇到的一个问题,大家可以参考一下:
<activity
android:name=".ThirdActivity"
android:launchMode="singleTop">
</activity>
在清单文件中给活动设置了 singleTop 的模式,但是实际启动的时候,由于某些原因非常快速的启动了两次,结果发现居然是启动了两个活动出现了两个页面,完全不是想要的结果。那么如果保证只出现一个页面呢?
不要再清单文件设置启动模式了,动态设置如下:
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
如果 SINGLE_TOP 生效,那么就会复用实例执行 onNewIntent
方法;如果 SINGLE_TOP 没有生效,那么会再创建一个新的页面执行 onCreate
方法,在 CLEAR_TOP 的作用下,会将第一个页面销毁,从而保证了只有一个页面显示给用户。所以,要同时在 onNewIntent
和 onCreate
方法里面做处理。
taskAffinity
<activity
...
android:taskAffinity=".taskname">
</activity>
taskAffinity 的属性是设置活动和栈的依附关系。一般情况下,一个 app 的所有活动都在一个栈中,但是我们也可以通过设置 singleInstance 来创建新的任务栈。利用 taskAffinity 属性也可以实现这一功能。
每个活动都可以设置自己的 taskAffinity 属性,这个属性指出了它希望进入的栈。如果一个活动没有指明该活动的 taskAffinity 属性,那么它的这个属性就等于 Application 指明的 taskAffinity,如果 Application 也没有指明,那么该 taskAffinity 的值就等于包名。而栈也有自己的 affinity 属性,它的值等于它的根活动的 taskAffinity的值。所以,设置该属性的时候,要按照包名的格式来设置,用 . 号分隔。
下面我们通过几个具体的例子来看看它的效果。
1.我们在清单文件里面给 B 设置如下:
<activity
android:name=".BActivity"
android:launchMode="singleTask"
android:taskAffinity=".second"
</activity>
(1)A -> B -> B
由 A 到 B 时,由于 B 的实例还不存在,会创建一个新的任务栈和 B 的实例,然后再由 B 到 B,由于 B 已经存在了,这个时候会执行 B 的 onPause()
-> onNewIntent(Intent intent)
-> onResume()
方法。
(2)A -> B -> C -> A
这种情况下,我们也是只给 B 设置了启动模式, A1 C A2 都是未进行任何设置的。这个时候会发现,由 A 到 B 时创建了一个新的任务栈,之后的 C 和 A2 都和 B 在一个任务栈中,A2 和 A1 是不同栈中的两个活动实例。
(3)A -> B -> C -> B
和上面的设置相同,由 A 到 B 时创建了一个新的任务栈,但是当 C 再到 B 时会发生什么呢?C 会被销毁,然后 B1 会执行 onNewIntent
-> onRestart
-> onStart
-> onResume
方法。
2.下面的几种情况的设置方法和上面不同了,我们在清单文件里面只设置了 taskAffinity
而没有设置 launchMode
。
<activity
android:name=".SecondActivity"
android:taskAffinity=".second">
</activity>
(1)A -> B -> C -> B -> A
除了 taskAffinity
什么都没设置,这个时候没有任何效果,只会不停地创建实例然后入栈。
(2)A -> B -> C
在 A -> B 的时候,动态设置了
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
A -> B 创建一个新的任务栈,并且之后的 C 和 B 在一个栈中。和上面个的第(2)情况是一致的。
(3)A -> B -> B
在 A -> B 和 B -> B 的时候,都设置了
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
A -> B 创建一个新的任务栈,B -> B 的时候,不会创建新的任务栈,并且不会创建新的 B 的实例,也不会执行 B 的任何方法!这点需要注意了,和上面的第(1)种情况并不一样。
后记
在写这篇博客的时候,我发现启动模式原来有这么多的东西。我这里也只是写了一部分,更深层次的东西也没有研究到,但写了上面的这些感觉收获也蛮大的。如果你们有兴趣可以自己去研究研究。
欢迎关注我的微信公众号
网友评论