美文网首页
重识Activity-Activity任务栈和启动模式

重识Activity-Activity任务栈和启动模式

作者: Android_程序媛 | 来源:发表于2019-05-31 17:57 被阅读0次

    本篇文章是《重识Activity》系列的第4篇。

    下面是《重识Activity》系列的其他文章:

    本篇文章主要记录了关于Activity的任务栈的理解以及和任务栈相关的Activity的启动模式的知识点。

    对Activity的任务栈的理解

    我们在开发的过程中,会发现有很多个Activity,每启动一个Activity都会覆盖上一次启动的Activity,然后按返回键最上面的Activity就会被销毁,下面的另一个Activity就会重新显示出来,那么Android是如何管理这些Activity的呢?

    下面介绍一下任务(Task) 的概念。

    任务(Task) 是指在执特定作业时与用户交互的Activity的集合。使用栈的方式管理其中的Activity,这个栈被称为Back Stack(返回栈),又称为任务栈,它的特点是:后进先出(LIFO),常用操作入栈(push),出栈(pop),处于最顶部的叫栈顶,最底部的叫栈低
    如何理解呢?
    当我们在主页面点击某个App的启动Logo时打开一个应用,在默认的情况下,Android系统就会创建一个Task,我们的理解就是启动的这个App就是一个Task,点击Home键,再启动另一个App,这时Android系统会再创建一个Task。启动App后会打开MainActivity,而MainActivity就会进入返回栈中,如果在启动另一个Activity,那么这个Activity也会进入返回栈,处于栈顶的状态,按返回键,该Activity被销毁出栈,MainActivity再次位于栈顶状态。为了更形象的理解,下面我贴出官方给出的图片:

    图1.png

    如果用户一直按Back键,这样返回栈中的Activity会一个一个的被移除,知道最终返回到主屏幕。当返回栈中所有的Activity都被移除掉的时候,对用的任务也就不存在了。

    刚才我们说到,启动一个App后,按Home键,那么其任务就会转移到后台,再启动另一个App,则该任务在前台。那么当任务处于后台的时候,返回栈中的所有Activity都会进入停止状态,但是这些Activity在栈中的顺序以及状态都会原封不动地保留,如下图:

    图2.png

    注意:可以在后台同时保存多个任务。但是,如果用户同时运行许多后台任务,系统可能会开始销毁后台任务以恢复内存,从而导致Activity状态丢失。

    由于返回栈中的Activity顺序永远不会发生变化,所以,如果你的应用允许有多个入口都可以启动同一个Activity,那么每次启动的时候就会创建该Activity的一个新的实例,而不是将栈中以后的该Activity的实例移到栈顶。这样就会容易导致一个问题产生:同一个Activity有可能会被实例化很多次,如下图所示:

    图3.png

    通过上述介绍,在默认情况下,我们可以总结以下几点:

    • 当Activity A启动Activity B时,A停止,但系统会保留其状态。如果用户在B中按下返回键,则A将恢复其状态;
    • 当用户通过按Home键离开任务时,当前Activity停止,其任务将进入后台。系统保留任务中每个Activity的状态。如果用户稍后通过选择开始任务的启动器图标来恢复任务,则任务将到达前台并在堆栈顶部恢复Activity;
    • 如果用户按下返回键,则会从堆栈中弹出当前Activity并将其销毁。堆栈中的前面一个Activity将处于栈顶位置并进入运行中状态。当一个Activity被销毁之后,系统不再为它保留任何的状态信息;
    • 每个Activity都可以被实例化多次,即使是在不同的任务中。

    Activity的启动模式

    在上面我们说到,同一个Activity有可能会被实例化很多次,如果我们不希望这样,又该如何解决呢?现在我们来说一下Activity的启动模式。

    Activity的启动模式负责管理Activity的实例化、加载Activity的方式,并且可以控制Activity与Task之间的加载关系。

    Activity有四种启动模式,分别是:standardsingleTopsingleTasksingleInstance

    使用方式:
    在AndroidManifest.xml文件的<activiy>标签中,设置android:launchMode属性。

    在介绍之前,我先说明一下我的Demo,有两个Activity:MainActivity和FirstActivity,他们分别有两个按钮,一个按钮用来启动自己的Activity,另一个按钮用来启动另一个Activity。

    standard

    标准模式,默认的启动模式,不管有没有设置属性android:launchMode="standard"。这种启动模式表示每次启动一个Activity都会创建一个新的实例,并且把它放到当前的任务中,声明成这种启动模式的Activity可以被实例化多次,一个任务当中也可以包含多个这种Activity的实例。

    下面,通过一个小Demo理解一下:

    将MainActivity和FirstActivity都设置为默认模式。

    在AndroidManifest.xml中的<Activity>标签中设置属性android:launchMode="standard",如下图:

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        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=".FirstActivity"
            android:launchMode="standard"></activity>
    </application>
    

    在MainActivity中的onCreate()方法中打印一下Log日志:

    Log.i("Alisa","MainActivity onCreate taskId:" + getTaskId() + "*****currentActivity:" + this.toString());
    

    其中,getTaskId()用户获取任务Id,this.toString()获取当前Activity的实例在栈中的地址。

    我们在MainActivity中点击两次startSelf,结果如下:

    图4.png

    可以发现,任务Id是一样的,但是当前Activity的实例的在栈中的地址是不一样的。

    我们通过adb命令可以查看当前栈中的Activity。命令如下:

    adb shell dumpsys activity

    查看结果:

    图5.png

    可以看到有三个MainActivity的实例,从上往下依次是栈顶到栈低。

    singleTop

    这种启动模式表示如果要启动的Activity在当前任务中已经存在,并且处于栈顶的位置,那么系统将不会在创建一个该Activity的实例,而是调用栈顶Activity的onNewIntent()方法;如果在任务栈中但不处于栈顶,那么系统还是会再次创建一个该Activity的实例。声明成这种启动模式的Activity可以被实例化多次,一个任务中也可以包含多个这种Activity的实例。

    下面将FirstActivity的启动模式设置成该属性值,其中MainActivity还是标准模式:

    <activity
        android:name=".FirstActivity"
        android:launchMode="singleTop"/>
    

    在FirstActivity的onCreate()方法中设置如下代码:

    Log.i("Alisa","FirstActivity onCreate taskId:" + getTaskId() + "*****currentActivity:" + this.toString());
    

    在FirstActivity中重写onNewIntent()方法:

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.i("Alisa","FirstActivity onNewIntent taskId:" + getTaskId() + "*****currentActivity:" + this.toString());
    }
    

    启动App后,在MainActivity中点击startFirst按钮,然后在FirstActivity中点击startSelf按钮,结果如下:

    图6.png

    可以发现,FirstActivity位于栈顶的位置没有再次创建实例,而且调用了onNewIntent()方法。

    通过命令查看任务栈,结果如下:

    图7.png

    此时,继续在FirstActivity中点击startMain按钮,然后在MainActivity中点击StartFirst按钮,结果如下:

    图8.png

    发现这次的FirstActivity实例的栈中地址和刚才的FirstActivity实例的栈中地址是不同的。

    通过命令查看任务栈,结果如下:

    图9.png

    可以发现有两个FirstActivity实例。

    singleTask

    这种模式表示系统在同一个任务中,只创建一个Activity的实例,并将启动的Activity放入栈顶的位置,如果任务栈中已有该Activity的实例,那么系统将不会再创建一次它的实例,而是调用它的onNewIntent()方法,并且如果不处于栈顶位置,那么在该Activity的上面的那些Activity将被移除销毁,从而让其位于栈顶。声明成这种模式的Activity在同一任务中只会存在一个实例。

    我们将FirstActivity的启动模式属性修改一下:

    <activity
        android:name=".FirstActivity"
        android:launchMode="singleTask"/>
    

    启动App,然后在MainActivity中点击startFirst按钮,然后在在FirstActivity中点击startSelf按钮,结果如下:

    图10.png

    通过命令查看任务栈:

    图11.png

    在上面的前提下,继续操作,在FirstActivity中点击startMain按钮,然后在MainActivity中startFirst按钮,结果如下:

    图12.png

    发现FirstActivity只调用了onNewIntent()方法,然后MainActivity也调用了onDestroy()方法,而这个MainActivity的实例就是刚才启动的MainActivity。

    通过命令查看任务栈:

    图13.png
    singleInstance

    这种模式表示系统无论从哪个Task中启动Activity,只会创建一个Activity实例,并会使用一个全新的任务栈来加载该Activity实例,而且这个任务栈始终只会有一个Activity实例,通过这个Activity再打开其它的Activity也会被放入到别的任务中。

    将FirstActivity的启动模式属性修改一下:

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

    打开App,在MainActivity中点击StartFirstActivity按钮,然后在FirstActivity中点击startSelf按钮:

    图14.png

    可以看到MainActivity和FirstActivity的taskId是不一样的,说明这两个Activity的实例处于两个任务中。

    通过命令查看:

    图15.png

    通过上图也可以看出两个Activity分别处于两个任务中。

    在上面的操作的基础上,继续操作,在FirstActivity点击startMain按钮,然后在MainActivity中点击startFirst按钮:

    图16.png

    可以看到FirstActivity的taskId和实例地址和刚才的FirstActivity是相同的,并且FirstActivity只是调用了onNewIntent()方法。

    通过命令查看:

    图17.png

    发现MainActivity的那个任务栈中有两个MainActivity实例。

    继续操作,在FirstActivity点击startMain按钮,然后一直按返回键,直到退出应用,回到主屏幕:

    图18.png

    可以看出,退出的顺序并不是按照启动Activity的顺序执行的,而是先移除的位于栈顶的这个任务栈中的所有Activity,然后在移除另个任务栈中的所有Activity。

    综上:
    其实不管是Activity在一个新任务当中启动,还是在当前任务中启动,返回键永远都会把我们带回到之前的一个Activity中的。但是有一种情况是比较特殊的,就是如果Activity指定了启动模式是"singleTask",并且启动的是另外一个应用程序中的Activity,这个时候当发现该Activity正好处于一个后台任务当中的话,就会直接将这整个后台任务一起切换到前台。此时按下返回键会优先将目前最前台的任务(刚刚从后台切换到最前台)进行回退,下图比较形象地展示了这种情况:

    image19.png

    扩展

    1. 在上面设置启动模式(除了默认模式)的情况下:
    • 如果Activity位于栈顶,再次启动该Activity,生命周期的执行顺序是:onNewIntent()----->onResume();
    • 如果不是位于栈顶,假设该Activity的启动模式是singleTop,生命周期的执行顺序是创建该Activity的生命周期,即onCreate()---->onStart()---->onResume();假设启动模式是singleTask或者singleInstance,生命周期的执行顺序是:onNewIntent()---->onRestart()----->onStart()----->onResume()
    1. onNewIntent(Intent intent)方法的使用

    在上面的设置启动模式中,有说到除了默认模式,其他模式会执行onNewIntent()方法,那么该方法又如何操作呢?

    我们知道通过Intent将参数可以从一个Activity传递到另一个Activity,然后通过getIntent()方法获取参数值,但是如果设置了启动模式,发现获取的参数值不是最新的参数值,而是一开始传递过来的参数值,那么该如何操作呢?

    
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        Log.i("Alisa","FirstActivity onNewIntent taskId:" + getTaskId() + "*****currentActivity:" + this.toString());
        
        setIntent(intent);
        }
    

    在上述代码中,可以看到调用了setIntent(intent)方法,然后再通过getIntent()方法就发现获取的参数值是最新的了。

    好了,上述就是我今天要分享的内容,欢迎大家积极留言,一起探讨学习!也请大家继续关注我的文章!

    相关文章

      网友评论

          本文标题:重识Activity-Activity任务栈和启动模式

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