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

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

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

  • 第1篇:重识Activity—Activity的基本使用
  • 第2篇:重识Activity—Activity的生命周期
  • 第3篇:重识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再次位于栈顶状态。为了更形象的理解,下面我贴出官方给出的图片:

重识Activity-Activity任务栈和启动模式_第1张图片
图1.png

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

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

重识Activity-Activity任务栈和启动模式_第2张图片
图2.png

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

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

重识Activity-Activity任务栈和启动模式_第3张图片
图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文件的标签中,设置android:launchMode属性。

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

standard

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

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

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

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


    
        
            

            
        
    
    

在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

查看结果:

重识Activity-Activity任务栈和启动模式_第4张图片
图5.png

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

singleTop

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

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


在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实例的栈中地址是不同的。

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

重识Activity-Activity任务栈和启动模式_第5张图片
图9.png

可以发现有两个FirstActivity实例。

singleTask

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

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


启动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的启动模式属性修改一下:


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

图14.png

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

通过命令查看:

重识Activity-Activity任务栈和启动模式_第6张图片
图15.png

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

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

图16.png

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

通过命令查看:

重识Activity-Activity任务栈和启动模式_第7张图片
图17.png

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

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

图18.png

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

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

重识Activity-Activity任务栈和启动模式_第8张图片
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任务栈和启动模式)