细谈Activity四种启动模式

    嗨大家好,又和大家见面了,上一次我们一起搞清楚了Handler的源码机制(现在回想起来是不是感觉也就那么回事,当时看的头皮发麻-。+!!),今天我们谈一谈我们在Android开发中必不可缺少的一个组件——Activity:

    Activity作为四大组件之一,也可以说是四大组件中最重要的一个组件,它负责App的视图,还负责用户交互,而且有时候还经常其他组件绑定使用,可以说非常的重要。

    虽然说我们天天都在使用Activity,但是你真的对Activity的生命机制烂熟于心,完全了解了吗?的确,Activity的生命周期方法只有七个(自己数-。+),但是其实那只是最平常的情况,或者说是默认的情况。也就是说在其他情况下,Activity的生命周期可能不会是按照我们以前所知道的流程,这就要说到我们今天的重点了——Activity的启动模式:我们的Activity会根据自身不同的启动模式,自身的生命周期方法会进行不同的调用。

一、在将启动模式之前必须了解的一些知识:

    在正式的介绍Activity的启动模式之前,我们首先要了解一些旁边的知识,这些知识如果说模糊不清,那么在讨论启动模式的时候会一头雾水(笔者亲身感悟)。

1.一个应用程序通常会有多个Activity,这些Activity都有一个对应的action(如MainActivity的action),我们可以通过action来启动对应Activity(隐式启动)。

<action android:name="android.intent.action.MAIN" />

2.一个应用程序可以说由一系列组件组成,这些组件以进程为载体,相互协作实现App功能。

3.任务栈(Task Stack)或者叫退回栈(Back Stack)介绍:

3.1.任务栈用来存放用户开启的Activity。

3.2.在应用程序创建之初,系统会默认分配给其一个任务栈(默认一个),并存储根Activity。

3.3.同一个Task Stack,只要不在栈顶,就是onStop状态:


3.4.任务栈的id自增长型,是Integer类型。

3.5.新创建Activity会被压入栈顶。点击back会将栈顶Activity弹出,并产生新的栈顶元素作为显示界面(onResume状态)。

3.6.当Task最后一个Activity被销毁时,对应的应用程序被关闭,清除Task栈,但是还会保留应用程序进程(狂点Back退出到Home界面后点击Menu会发现还有这个App的框框。个人理解应该是这个意思),再次点击进入应用会创建新的Task栈。

4.Activity的affinity:

4.1.affinity是Activity内的一个属性(在ManiFest中对应属性为taskAffinity)。默认情况下,拥有相同affinity的Activity属于同一个Task中。

4.2.Task也有affinity属性,它的affinity属性由根Activity(创建Task时第一个被压入栈的Activity)决定。

4.3.在默认情况下(我们什么都不设置),所有的Activity的affinity都从Application继承。也就是说Application同样有taskAffinity属性。

<application
        android:taskAffinity="gf.zy"

4.4.Application默认的affinity属性为Manifest的包名。


暂时就是这么多了,如果还有不妥的地方我会补充的。接下来我们来正式看Activity的启动模式:

二、Activity启动模式:

1.默认启动模式standard:

    该模式可以被设定,不在manifest设定时候,Activity的默认模式就是standard。在该模式下,启动的Activity会依照启动顺序被依次压入Task中:


上面这张图讲的已经很清楚了,我想应该不用做什么实验来论证了吧,这个是最简单的一个,我们过。

2.栈顶复用模式singleTop:

    在该模式下,如果栈顶Activity为我们要新建的Activity(目标Activity),那么就不会重复创建新的Activity。



这次我来用代码举例:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="zy.pers.activitytext">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:taskAffinity="gf.zy"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".TwoActivity"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="ONETEXT_TWOACTIVITY" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <activity android:name=".ThreeActivity">
            <intent-filter>
                <action android:name="ONETEXT_THREEACTIVITY" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>

</manifest>

这是我的第一个应用OneText的Mainfest结构,里面创建了三个Activity,我们把第二个Activity的模式设置为singleTop。

每个Activity界面都只有一个显示当前界面名称的TextView和一个用来组跳转的Button,所以应用OneText的功能就是从活动1跳转到活动2,活动2继续跳转活动2,代码就不给大家展示了,都能写出来。


我们发现在我们跳转到TwoActivity之后,点击跳转新的TwoActivity时候,他没有响应。

为了作对比,我们再把TwoActivity设置为standard,看一看效果:


我们发现创建了很多的TwoActivity。

同时我们打印上task的Id(我没有把所有周期方法都打印log):


发现他们全部都是来自一个Task。这个可以过。

应用场景:

开启渠道多,适合多应用开启调用的Activity:通过这种设置可以避免已经创建过的Activity被重复创建(多数通过动态设置使用,关于动态设置下面会详细介绍)

3.栈内复用模式singleTask:

    与singleTop模式相似,只不过singleTop模式是只是针对栈顶的元素,而singleTask模式下,如果task栈内存在目标Activity实例,则:

  1. 将task内的对应Activity实例之上的所有Activity弹出栈。
  2. 将对应Activity置于栈顶,获得焦点。

同样我们也用代码来实现一下这个过程:

还是刚才的那一坨代码,只是我们修改一下Activity1的模式为singleTask,然后让Activity2跳转到Activity3,让Activity3跳转到Activity1:


在跳回MainActivity之后点击back键发现直接退出引用了,这说明此时的MainActivity为task内的最后一个Activity。所以这个模式过。

应用场景:

程序主界面,我们肯定不希望主界面被多创建,而且在主界面退出的时候退出整个App是最好的设想。

耗费系统资源的Activity:对于那些及其耗费系统资源的Activity,我们可以考虑将其设为singleTask模式,减少资源耗费(在创建阶段耗费资源的情况,个人理解-。+)。

4.全局唯一模式singleInstance:

    这是我们最后的一种启动模式,也是我们最恶心的一种模式:在该模式下,我们会为目标Activity分配一个新的affinity,并创建一个新的Task栈,将目标Activity放入新的Task,并让目标Activity获得焦点。新的Task有且只有这一个Activity实例。       如果已经创建过目标Activity实例,则不会创建新的Task,而是将以前创建过的Activity唤醒(对应Task设为Foreground状态)


我们为了看的更明确,这次不按照上图的步骤设计程序了(没错,这几张图都不是我画的-。+!)。

我们先指定一下这次的程序:还是这三个Activity,这次Activity3设置为singleInstance,1和2默认(standard)。

然后我们看一下这个效果:


说一下我们做了什么操作:

首先由1创建2,2创建3,然后又由3创建2,2创建3,3创建2,然后一直back,图如下:


还请各位别嫌弃我-。+,图虽然不好看,但是很生动形象。。。。具体说一下:这张图对应着我们上面的程序流程,黄色的代表Background的Task,蓝色的代表Foreground的Task。

我们发现back的时候会先把Foreground的Task中的Activity弹出,直到Task销毁,然后才将Background的Task唤到前台,所以最后将Activity3销毁之后,会直接退出应用。

但是有没有想过这样会出现一个问题,什么问题我们直接看图就好:


我简单说一下这个案例:1,2,3三个Activity,2是singleInstance模式,然后1->2,2->3,之后狂点back,在回到Home界面后点击菜单键,发现首先启动的是2Activity。

简单解释一下:1和3是一个task,2是单独的一个task,在我们2->3后,前台的task又从2的回到了1和3的。所以最后退出的task是2的线程,而如果不是重新启动App。上一次最后关闭的Task是2的,所以。。

所以说singleInstance设置的Activity最好不要设置成中间界面。


以上表示我们关于四种模式的最基本理解,其实有了前面的知识了解之后,我们发现这些其实也不是很难对吧。。。真正比较绕的在后面-。+,注意前方高能:

三、动态设置启动模式

    在上述所有情况,都是我们在Manifest中设置的(通过launchMode属性设置),这个被称为静态设置(我们写程序写多了会发现有静态就有动态,而且静态多数在xml设置,动态在java代码设置-。+),接下来我们来看一下如何动态的设置Activity启动方式。

注):如果同时有动态和静态设置,那么动态的优先级更高。

1.关于动态设置与静态设置的理解:

    关于这个理解我是看过一篇文章,比较认同里面的思想,所以在这里也总结一下:

    静态设置,可以理解为通知别人:就是当我被创建的时候,我告诉你我是通过这种模式启动的。

    动态设置,可以理解为别人的要求:别人给我设一个Flag,我就以这种Flag的方式启动。

    可能这个没什么用哈,但是仔细想一下这种对程序的思想理解应该是正确的。

2.几种常见的Flag:

    我们说的动态设置,其实是通过Intent。对与Intent这个类大家应该不陌生吧,我们刚才在启动Activity的时候就用到这个类了。

如果我们要设置要启动的Activity的启动模式的话,只需要这样:

intent.setFlags(、、、、、);

然后在里面添加对应的Flag就好,那么接下来我们介绍几个常见的Flag(他的Flag太多了,头皮发麻。):

2.1._NEW_TASK

他对应的Flag如下:

Intent.FLAG_ACTIVITY_NEW_TASK

这个Flag跟我们的singleInstance很相似:在给目标Activity设立此Flag后,会根据目标Activity的affinity进行匹配:如果已经存在与其affinity相同的task,则将目标Activity压入此Task。    反之没有的话,则新建一个task,新建的task的affinity值与目标Activity相同。然后将目标Activity压入此栈。

其实简单来说,就是先看看需不需要创建一个新的Task,根据就是有没有相同的affinity。然后把Activity放进去。

但是此情况和singleInstance有不同,有两点注意的地方:

  1. 新的Task没有说只能存放一个目标Activity。只是说决定是否新建一个Task。而singleInstance模式下新的Task只能放置一个目标Activity。
  2. 在同一应用下,如果Activity都是默认的affinity,那么此Flag无效。而singleInstance默认情况也会创建新的Task。

这个东西理解起来可能有一些抽象,我们通过一个实例来证明他:

在之前的一些例子中,我们都是在同一应用之间进行跳转,而现在我们进行不同App的Activity相互跳转(其实就是创造一个不同taskAffinity的情况。。。忘了的话见一、4)。

首先,我们需要创建一个新的App——TwoApp,这个App目前只需要一个MainActivity就够了,我们在MainActivity放置一个button,让他跳转到OneApp的TwoActivity。

public void onClick(View v) {
    Intent intent = new Intent("ONETEXT_TWOACTIVITY");
    
    startActivity(intent);
}

这是跳转的代码。

现在我们先概述一下我们的流程:我们先打开TwoApp,然后在TwoApp的MainActivity界面跳转到OneApp的TwoActivity。

对于OneApp的设定,我们已经将三个Activity都设置成了standard模式。还是1->2,2->3,3->2。

代码就不上了,这么简单,大家自己也能写出来。效果如下:

为了看的清清楚楚,最开始清空了所有的进程。

现在我们点开TwoApp,

现在只有TwoApp一个进程,

现在我们点开了 OneApp的TwoActivity,但是我们发现他还是只有一个进程,


现在我们在TwoApp的MainActivity跳转到OneApp的TwoActivity,添加_NEW_TASK的Flag。

public void onClick(View v) {
    Intent intent = new Intent("ONETEXT_TWOACTIVITY");
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
}

我们再看一下效果,还是跟刚才一样先清空所有的进程,这次效果直接连起来看了:


与上面不同的地方在于,我们新的界面创建在了新的进程中——其实就是OneApp被唤醒了,我们来分析一下为什么会这样:

首先我们会想一下我们上面所学过的一个东西,affinity:我们说这个东西在默认情况下就是App的包名packageName,而OneApp中的TwoActivity默认的affinity就是OneApp的包名。这里能够理解吧。

然后我们说_NEW_TASK情况下,会先查找是否有对应的affinity的task,如果有就不在创建,直接将其放入,反之新建task,所以新建的task就是我们的OneApp的task,我们可以再做一个测试,我们先唤醒OneApp,然后再让TwoApp跳转到OneApp的TwoActivity(有点绕啊。。。),我们看是什么情况:

首先启动OneApp,并跳转到ThreeActivity。

然后启动TwoApp,并跳转到OneApp的TwoActivity。

然后一直点击Back,

我们发现在Two中唤醒One的TwoActivity,同样是被放入了OneApp的默认Task中。

关于_NEW_TASK我们就说这么多吧。

2.2._SINGLE_TOP

该模式比较简单,对应Flag如下:

Intent.FLAG_ACTIVITY_SINGLE_TOP

次Flag与静态设置中的singleTop效果相同,所以请见二、2.

2.3._CLEAR_TOP

这个模式对应的Flag如下:

Intent.FLAG_ACTIVITY_CLEAR_TOP

当设置此Flag时,目标Activity会检查Task中是否存在此实例,如果没有则添加压入栈,

如果有,就将位于Task中的对应Activity其上的所有Activity弹出栈,此时有以下两种情况:

  • 如果同时设置Flag_ACTIVITY_SINGLE_TOP,则直接使用栈内的对应Activity,
  • 没有设置。。。。。。。,则将栈内的对应Activity销毁重新创建。

关于这个Flag,我们发现他和singleTask很像,准确的说,是在_CLEAR_TOP和_SINGLE_TOP同时设置的情况下,就是singleTask模式。

而唯一不同的一点就在于:他会销毁已存在的目标实例,再重新创建。这个我们通过打印一下生命周期就好。

这次我们只用OneApp就好了,还是1->2,2->3,3->2,这次我们将2的Flag设置为_CLEAR_TOP,看一下TwoActivity的生命周期。


我们的流程如下:1->2       2->3    3->2     2->3    3->2    back    back   然后就退出了,这说明在Task内2上面的3的确被弹出栈了。

然后我们再看一下2的日志:


我想在日志图片上面标注的很清楚了,我只截取了一部分日志,我们质疑3->2时候先销毁,后创建。


好,现在我们同时加上_SINGLE_TOP的Flag。

效果相同,我们只看log:


很明显,在3->2的时候,TwoActivity调用了onRestart方法,也就是栈顶复用了。

这个Flag过。


其实还有一点点东西想提一下的,但是感觉 篇幅已经很恶心了。。。。再写下去可能更没人看了吧/笑着哭,所以决定找机会穿插到下一篇文章中:Activity标签中task相关属性

喜欢的话可以关注一波,有建议和不同观点的欢迎下方留言!感谢大家的支持!

发布了85 篇原创文章 · 获赞 98 · 访问量 6万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 岁月 设计师: pinMode

分享到微信朋友圈

×

扫一扫,手机浏览