本篇文章是《重识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再次位于栈顶状态。为了更形象的理解,下面我贴出官方给出的图片:
如果用户一直按Back键,这样返回栈中的Activity会一个一个的被移除,知道最终返回到主屏幕。当返回栈中所有的Activity都被移除掉的时候,对用的任务也就不存在了。
刚才我们说到,启动一个App后,按Home键,那么其任务就会转移到后台,再启动另一个App,则该任务在前台。那么当任务处于后台的时候,返回栈中的所有Activity都会进入停止状态,但是这些Activity在栈中的顺序以及状态都会原封不动地保留,如下图:
注意:可以在后台同时保存多个任务。但是,如果用户同时运行许多后台任务,系统可能会开始销毁后台任务以恢复内存,从而导致Activity状态丢失。
由于返回栈中的Activity顺序永远不会发生变化,所以,如果你的应用允许有多个入口都可以启动同一个Activity,那么每次启动的时候就会创建该Activity的一个新的实例,而不是将栈中以后的该Activity的实例移到栈顶。这样就会容易导致一个问题产生:同一个Activity有可能会被实例化很多次,如下图所示:
通过上述介绍,在默认情况下,我们可以总结以下几点:
- 当Activity A启动Activity B时,A停止,但系统会保留其状态。如果用户在B中按下返回键,则A将恢复其状态;
- 当用户通过按Home键离开任务时,当前Activity停止,其任务将进入后台。系统保留任务中每个Activity的状态。如果用户稍后通过选择开始任务的启动器图标来恢复任务,则任务将到达前台并在堆栈顶部恢复Activity;
- 如果用户按下返回键,则会从堆栈中弹出当前Activity并将其销毁。堆栈中的前面一个Activity将处于栈顶位置并进入运行中状态。当一个Activity被销毁之后,系统不再为它保留任何的状态信息;
- 每个Activity都可以被实例化多次,即使是在不同的任务中。
Activity的启动模式
在上面我们说到,同一个Activity有可能会被实例化很多次,如果我们不希望这样,又该如何解决呢?现在我们来说一下Activity的启动模式。
Activity的启动模式负责管理Activity的实例化、加载Activity的方式,并且可以控制Activity与Task之间的加载关系。
Activity有四种启动模式,分别是:standard、singleTop、singleTask、singleInstance。
使用方式:
在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,结果如下:
可以发现,任务Id是一样的,但是当前Activity的实例的在栈中的地址是不一样的。
我们通过adb命令可以查看当前栈中的Activity。命令如下:
adb shell dumpsys activity
查看结果:
可以看到有三个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按钮,结果如下:
可以发现,FirstActivity位于栈顶的位置没有再次创建实例,而且调用了onNewIntent()
方法。
通过命令查看任务栈,结果如下:
此时,继续在FirstActivity中点击startMain按钮,然后在MainActivity中点击StartFirst按钮,结果如下:
发现这次的FirstActivity实例的栈中地址和刚才的FirstActivity实例的栈中地址是不同的。
通过命令查看任务栈,结果如下:
可以发现有两个FirstActivity实例。
singleTask
这种模式表示系统在同一个任务中,只创建一个Activity的实例,并将启动的Activity放入栈顶的位置,如果任务栈中已有该Activity的实例,那么系统将不会再创建一次它的实例,而是调用它的onNewIntent()
方法,并且如果不处于栈顶位置,那么在该Activity的上面的那些Activity将被移除销毁,从而让其位于栈顶。声明成这种模式的Activity在同一任务中只会存在一个实例。
我们将FirstActivity的启动模式属性修改一下:
启动App,然后在MainActivity中点击startFirst按钮,然后在在FirstActivity中点击startSelf按钮,结果如下:
通过命令查看任务栈:
在上面的前提下,继续操作,在FirstActivity中点击startMain按钮,然后在MainActivity中startFirst按钮,结果如下:
发现FirstActivity只调用了onNewIntent()
方法,然后MainActivity也调用了onDestroy()
方法,而这个MainActivity的实例就是刚才启动的MainActivity。
通过命令查看任务栈:
singleInstance
这种模式表示系统无论从哪个Task中启动Activity,只会创建一个Activity实例,并会使用一个全新的任务栈来加载该Activity实例,而且这个任务栈始终只会有一个Activity实例,通过这个Activity再打开其它的Activity也会被放入到别的任务中。
将FirstActivity的启动模式属性修改一下:
打开App,在MainActivity中点击StartFirstActivity按钮,然后在FirstActivity中点击startSelf按钮:
可以看到MainActivity和FirstActivity的taskId是不一样的,说明这两个Activity的实例处于两个任务中。
通过命令查看:
通过上图也可以看出两个Activity分别处于两个任务中。
在上面的操作的基础上,继续操作,在FirstActivity点击startMain按钮,然后在MainActivity中点击startFirst按钮:
可以看到FirstActivity的taskId和实例地址和刚才的FirstActivity是相同的,并且FirstActivity只是调用了onNewIntent()
方法。
通过命令查看:
发现MainActivity的那个任务栈中有两个MainActivity实例。
继续操作,在FirstActivity点击startMain按钮,然后一直按返回键,直到退出应用,回到主屏幕:
可以看出,退出的顺序并不是按照启动Activity的顺序执行的,而是先移除的位于栈顶的这个任务栈中的所有Activity,然后在移除另个任务栈中的所有Activity。
综上:
其实不管是Activity在一个新任务当中启动,还是在当前任务中启动,返回键永远都会把我们带回到之前的一个Activity中的。但是有一种情况是比较特殊的,就是如果Activity指定了启动模式是"singleTask",并且启动的是另外一个应用程序中的Activity,这个时候当发现该Activity正好处于一个后台任务当中的话,就会直接将这整个后台任务一起切换到前台。此时按下返回键会优先将目前最前台的任务(刚刚从后台切换到最前台)进行回退,下图比较形象地展示了这种情况:
扩展
- 在上面设置启动模式(除了默认模式)的情况下:
- 如果Activity位于栈顶,再次启动该Activity,生命周期的执行顺序是:
onNewIntent()
----->onResume()
; - 如果不是位于栈顶,假设该Activity的启动模式是
singleTop
,生命周期的执行顺序是创建该Activity的生命周期,即onCreate()
---->onStart()
---->onResume()
;假设启动模式是singleTask
或者singleInstance
,生命周期的执行顺序是:onNewIntent()
---->onRestart()
----->onStart()
----->onResume()
。
- 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()
方法就发现获取的参数值是最新的了。
好了,上述就是我今天要分享的内容,欢迎大家积极留言,一起探讨学习!也请大家继续关注我的文章!