Activity 的启动模式、应用场景、Intent.FLAG_ 与 taskAffinity

前段时间去面试的时候,有被问到 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 所在的栈中的元素出栈。

使用场景:一般很少用到,比如说闹钟的提示页、锁屏页等。

使用方式

1. 在 AndroidManifest.xml 文件中配置,比如:

<activity
        android:name=".ThirdActivity"
        android:launchMode="singleInstance">
</activity>

这是最常用的配置方式。

2. 使用 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 的作用下,会将第一个页面销毁,从而保证了只有一个页面显示给用户。所以,要同时在 onNewIntentonCreate 方法里面做处理。

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)种情况并不一样。

后记

在写这篇博客的时候,我发现启动模式原来有这么多的东西。我这里也只是写了一部分,更深层次的东西也没有研究到,但写了上面的这些感觉收获也蛮大的。如果你们有兴趣可以自己去研究研究。


欢迎关注我的微信公众号
欢迎关注我的微信公众号

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值