Activity
作者:桂志宏
Activity是Android四大组件之首,也是使用最为频繁的组件,主要负责界面的显示,因此Activity的重要性不言而喻。关于Activity的基本用法就不多说,下面主要讨论两个重要的过程:Activity的生命周期 和 Activity的启动模式
Activity的生命周期
下面给出Activity生命周期的一张经典的流程图
下面一一说明
onCreate: 表示Activity正在被创建。onCreate调用后,紧接着onStart就会被调用。
onStart: 表示Activity正在被启动,并已经可见,但是还没出现在前台,因此用户还看不到,但紧接着onResume就会被调用。
onResume: 表示Activity可见,并且已经出现在前台,可交互。和onStart不同仅在于,调用onResume使得Activity出现在前台,onStart时,Activity在后台。
onRestart:这个方法一般是在当前Activity由不可见重新变为可见,该方法就会被调用,这种情形很常见,比如用户启动一个新的Activity后,然后由重新返回当前Activity,或者用户按Home键回到桌面,然后点击app图标又回到当前Activity,这两种情形都会调用onRestart。当调用了onRestart后,进阶着会调用onStart方法。
onPause: 表明Activity正在停止,这时Activity已不可见,紧接着onStop方法会立刻被调用。在上面的生命周期途中由onPause到onResume这条虚线对应的场景是,用户退出当前Activity后又迅速返回Activity,实际上,这点很难重新,因为速度太快。
onStop: 表明Activity即将停止。注意,onPause和onStop方法中都不能做太多耗时的工作
onDestroy: 表明Activity即将被销毁。这时Activity生命周期的最后一个回调方法,一般这就意味着当前Activity实例已经出栈。
这里就有一个问题,当从一个Activity启动另外一个Activity时,是先停掉原Activity再创建新Activity还是先创建新Activity再停掉原Activity?
上面这个问题等价于问原Activity的onPause方法和新Activity的onCreate方法的执行时间顺序。我们直接测试一下就知道了。
考虑两个Activity,分别为A和B,先启动A,然后在A中启动B,最后按返回键回退到B。我们看一下输出的日志信息,如下图所示
由输出的日志,当启动A时,按顺序依次回调onCreate,onStart,onResume方法,这时A在前台且可见,可交互。在这时启动B,由日志信息可知,这时会先回调A的onPause方法,然后会依次调用B的onCreate,onStart,onResume方法,然后再调用A的onStop,这时B位于前台,可交互,A被停止,但是A的实例还在任务栈中,因此没有回调A的onDestroy方法。 此时,按一下返回键,又重新回到A,由前面的结果,当然是先调用B的onPuase方法,然后调用A的onRestart,onStart等方法,当A回到前台后,接着回调了B的onStop,紧接着回调了B的onDestroy方法,因为B已经出栈了。
综上所述: 当启动新Activity时,先停掉原Activity,然后创建新Activity。
上面是正常情形下Activity的生命周期,当出现异常时,Activity的生命周期也需要讨论一下,这里存在两种情形,一种是系统配置发生改变导致Activity被销毁又重建,比如由竖屏变为横屏;一种是资源内存不足导致优先级低的Activity被销毁。下面分别讨论
(1)系统配置发生改变
这种情况一般是指屏幕旋转,即由竖屏变为横屏或相反,这时由于横屏和竖屏的参数发生变化,因此需要重新去加载合适的资源,比如我们在drawable目录中放一些程序可能会用到的图片,但是为了兼容不同的设备,所以会在drawable-mdpi,drawable-hdpi等目录中放入一些图片。这里涉及到系统的资源加载机制,先暂且不深入研究,但后续我们会对其做一个详细的探讨。
当横屏时,此时会在销毁当前Activity之前先调用onSaveInstanceState方法来存储Activity的状态,这个方法是在onStop方法调用之前调用,但和onPause方法调用时机没有绝对的先后的关系。然后在销毁当前Activity后再重新创建Activity时会把onSaveInstanceState保存的Bundle对象同时传递给onRestoreInstanceState和onCreate方法来恢复之前保存的Activity状态。其中onRestoreInstanceState方法调用是在onStart之后。我们可以看一下在横屏前后输出的日志信息,如下图所示
(2)内存资源不足
内存不足使得低优先级的Activity被销毁,这里叙述一下Activity的优先级,可分为三种:
a.前台Activity. 可和用户交互,其优先级最高
b.可见但位于后台Activity. 例如当前Activity中弹出一个对话框,使得Activity不可和用户交互,但是可见,其优先级次之。
c.不可见位于后台Activity. 这种一般是指onStop方法被调用,其优先级最小。
当系统内存不足时就会去杀死上述优先级低的Activity所在进程。如果一个进程中没有四大组件运行,则该进程将很快被系统杀死,所以一些耗时的后台工作应该放在Service中运行,不应该直接开启一个子线程运行,这样避免当前进程被杀死。
如果我们想系统配置发生改变时,Activity不销毁重新创建,就可以给Activity指定如下属性
android:configChanges="orientation"
上述属性指定屏幕旋转时,Activity不重新创建,当然还可以有其它属性值,例如fontScale(系统字体缩放比例发生改变),
Activity的启动模式
当创建一个Activity时,系统会创建其实例,并将它放入任务栈中(压栈),我们知道,栈(Stack)是一种后进先出的结构,每当用户按后退键,当前Activity被弹出栈,直到这个栈为空,系统就会回收这个任务栈。
每当创建一个Activity,不同的启动模式决定了系统如何创建相应的Activity实例以及该实例在任务栈中位置。启动模式有四种,分别为:standard,singleTop,singleTas**k和**singleInstance。下面详细解说
stadard:标准启动模式。这也是系统默认的启动模式,每次启动一个Activity时,都会创建其对应的实例,并压入任务栈中,因此Activity生命周期中的onCreate,onStart,onResume等方法会逐一被调用。
singleTop:栈顶复用模式。也就是说如果启动一个Activity,而任务栈的栈顶恰巧存在该Activity的一个实例,这时系统不会再去创建其实例,这时该Activity的onCreate,onStart等方法不会被系统调用。 如果该Activity的实例不是位于栈顶,系统还是会重新创建一个该Activity的实例,病将其压入栈内。
singleTask:栈内复用模式。从名字可以知道,只要任务栈内存在某个Activity实例时,当启动该Activity时,系统不会去创建新的实例,和栈顶复用模式不同的是,被启动的Activity实例不用位于栈顶。这里需要注意的是,当启动该Activity时,该Activity的实例上面的Activity实例就会被弹出栈。
singleInstance:单实例模式。这种模式具有singleTask模式的所有特性,除此之外,单实例模式的Activity实例都会运行在一个单独的任务栈中。
这样说完比较枯燥和抽象,我们可以用实际例子来演示。
考虑5个Activity,分别为MainActivity,A,B,C和D。其中MainActivity是app的启动Activity,其启动模式为标准模式,我们主要用后面四个Activity来演示。
执行如下操作: 按顺序从MainActivity逐一启动这四个Activity(就是MainActivity启动A,然后A启动B,如此进行,直到D被启动,最后D启动A,然后按返回键),我们改变启动模式,看看会有怎样的变化
1)A到D的启动模式均为 标准模式
我们可以先看一下按照上面操作出现的现象,如下:
我们可以看到,标准模式下,A,B,C,D依次被启动,最后D启动A,然后返回,按照相反的顺序,其任务栈的实例动态入栈和弹栈如下图
图中push的方向表示依次被压栈的实例对象,pop的方向表示按返回键时,依次被弹栈的实例对象。其中,当D启动A时,系统依然会重新创建A的一个实例。我们可以从输出的日志信息可知,如下
由日志信息易知,D启动A时,A的onCreate方法又被调用,表明系统重新创建了A的实例。
2)B到D的启动模式均为 标准模式,但是A的启动模式为singleTask模式
我们先看一下实际的过程,如下
由运行的结果可知,当D启动A后,再按返回键,直接回退到MainActivity,再按返回键,程序直接回退至桌面。这和上面的标准模式差别很大,其原因在于A的启动模式为栈内复用模式,当D启动A时,由于当前任务栈内存在一个A的实例,因此系统不会再去创建A的实例,而是直接利用已存在的A的实例,由于A的实例不在栈顶,因此需要将A上面的实例弹栈,这时栈内的实例就只有A和MainActivity的实例,因此再按一次返回键,A被弹栈,MainActivity的实例位于栈顶,因此就回到MainActivity的界面,再按一次返回键,MainActivity实例也被弹栈,此时任务栈为空,系统回收任务栈退出程序,因此就退回至桌面。下图是D启动A时的弹栈过程示意图,如下
同样,我们看一下上述过程的日志信息,如下:
前面的A,B,C,D依次创建的过程不用多说,其中红色矩形区域的日志信息很重要,矩形区域的第一条信息表明D被创建,紧接着,D启动了A,可以看到这时并没有调用A的OnCreate方法,而是依次调用了B,C,D的onDestroy方法,这表明B,C,D被出栈并被销毁,后面因为按返回键,A和MainActivity也依次出栈。
3)B到D的启动模式均为 标准模式,但是A的启动模式为singleInstance模式
我们还是看一下按上述操作的运行结果,如下
从上述结果可以看到,当启动A时会有不一样的过渡效果,其原因在于启动A时,系统创建其实例,并且创建一个新的任务栈,并将A的实例压入该栈。注意是当从D再一次启动A并回退时,直接回退到D,当进一步回退时,在B时,进一步回退时,此时直接回退到MainActivity,然后退出app。我们可以用下面一张图图解说,如下
非singleInstance启动模式的Activity都运行在系统默认的任务栈中,在上图中就是栈1,当从MainActivity启动A,由于A是singleInstance模式,因此会重新创建一个任务栈2,并将A的实例压入栈2中,当再从A中启动B时,这时又会切换到栈1,并将B,C,D的实例压入栈1,当从D中启动A时,又会切换到栈2,由于栈2中存在A的实例,因此会直接复用栈2中的A的实例,此时按回退键,由于此时A在前台,因此先从A所在的栈2中进行弹栈,所以A被弹出栈2,这时栈2为空并被系统回收,此时切换到栈1,并进一步在栈1中弹栈,所以当从B回退时就直接回退到MainActivity。
我们可以看一下日志信息,如下
从上图中红色矩形信息可知,D启动A时,并没有创建A的实例。
4)A到C的启动模式均为 标准模式,但是D的启动模式为singleTop模式
先把前面的操作做一点更改,即在D中启动D,然后再返回,按该操作实际的运行结果如下
从运行结果可知,当从D中再启动D时,并没有又一次启动D。这个时候,由于D在栈顶,所以系统不会去创建D的实例,直接复用位于栈顶的D。我们可以从日志信息中验证,如下图
从日志信息,A,B,C,D依次启动,其onCreate方法依次被调用,在D再次启动D时,又红色矩形的信息可知,此时系统没有再一次调用D的onCreate方法,接着由于逐步返回,从而依次调用onDestroy方法。