美文网首页Android开发Android开发
Activity 的启动模式、应用场景、Intent.FLAG_

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

作者: ayuhani | 来源:发表于2018-08-19 19:24 被阅读29次

    前段时间去面试的时候,有被问到 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>
    

    这是最常用的配置方式。

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

    后记

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


    欢迎关注我的微信公众号

    相关文章

      网友评论

        本文标题:Activity 的启动模式、应用场景、Intent.FLAG_

        本文链接:https://www.haomeiwen.com/subject/zcbmbftx.html