典型情况下的生命周期分析
Activity生命周期中的四个重要状态
活动状态:在屏幕的最前面(也就是在栈顶),获得焦点并且可见
暂停状态:失去焦点,不过还是可见,例如屏幕上面弹出来的Dialog
停止状态:失去焦点,不可见
销毁状态:被系统销毁
完整生命周期:Activity的完整生命周期自第一次调用onCreate()开始,直至调用onDestroy()为止。Activity在onCreate()中设置并且进行初始化,而在onDestroy()中释放所有系统资源。
可见生命周期:Activity的可见生命周期自onStart()调用开始直到相应的onStop()调用结束。在此期间,用户可以在屏幕上看到Activity,尽管它也许并不是位于前台或者也不与用户进行交互。在这两个方法之间,我们可以保留用来向用户显示这个Activity所需的资源。
前台生命周期:Activity的前台生命周期自onResume()调用起,至相应的onPause()调用为止。在此期间,Activity位于前台最上面并与用户进行交互。Activity会经常在暂停和恢复之间进行状态转换。
正常情况下的生命周期
在正常的情况下,Activity会经历如下生命周期:
onCreate:表示Activity正在被创建,这是生命周期的第一个方法,在这个方法中,我们可以做一些初始化工作,比如调用setContentView去加载界面布局资源、初始化Activity所需数据等。
onRestart:表示Activity正在重新启动。一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart就会被调用。这种情形一般是用户行为所导致的,比如用户按Home键切换到桌面或者用户打开了一个新的Activity,这时当前的Activity就会暂停,也就是onPause和onStop方法被执行了,接着用户又回到了这个Activity,就会出现这种情况。
onStart:表示Activity正在被启动,即将开始,这时Activity已经可见了,但是还没有出现在前台,还无法和用户交互。这个时候可以理解为Activity已经显示出来了,但是我们还看不到。
onResume:表示Activity已经可见了,并且出现在前台并开始活动。要注意这个和onStart的对比,这两个都表示Activity已经可见,但是onStart的时候Activity还处于后台,无法和用户进行交互,onResume的时候Activity才显示到前台,可以与用户进行交互。
onPause:表示Activity正在停止,正常情况下,紧接着onStop就会被调用,在特殊情况下,如果这个时候再快速回到当前Activity,那么onResume就会被调用。这种情况比较极端,用户操作很难重现这个场景。此时可以做一些数据存储,停止动画等工作,但是注意不能太耗时,因为这会影响到新Activity的显示,onPause必须先执行完,新Activity的onResume才会执行。
onStop:表示Activity即将停止,可以做一些稍微重量级的回收工作,同样不能太耗时。
onDestroy:表示Activity即将被销毁,这是Activity生命周期中的最后一个回调,在这里,我们可以做一些回收工作和最终的资源释放。
正常情况下,Activity的常用生命周期就只有上面的7个,下图详细地描述了Activity各种生命周期的切换过程。
这里附加几个说明:
一个Activity
- 针对一个特定的Activity,第一次启动,回调如下:onCreate ——> onStart ——> onResume;
- 当用户打开新的Activity或者切换到桌面的时候,回调如下:onPause ——> onStop ——> 这里有一种特殊的情况,如果新的Activity采用了透明主题,那么当前Activity不会回调onStop;
- 当用户再次回到原Activity,回调如下:onRestart ——> onStart ——> onResume;
- 当用户按back键回退时,回调如下:onPause ———> onStop ——> onDestroy;
- 当Activity被系统回收后再次打开,生命周期方法回调过程和1一样,注意只是生命周期方法一样,不代表所有过程都一样,这个问题在后面会详细说明;
- 从整个生命周期来说,onCreate和onDestroy是配对的,分别标识着Activity的创建和销毁,并且只可能有一次调用。从Activity是否可见来说,onStart和onStop是配对的,随着用户的操作和设备屏幕的点亮和熄灭,这两个方法可能被调用多次;从Activity是否在前台来说,onResume和onPause是配对的,随着用户操作或者设备的点亮和熄灭,这两个方法可能被多次调用。
两个Activity之间
- 启动一个Activity
onCreate()—>onStart()—>onResume() - 打开一个新Activity
旧Activity的onPause()—>新Activity的onCreate()—>onStart()—>onResume()—>旧Activity的onStop(); - 返回到旧Activity
新Activity的onPause()—>旧Activity的onRestart()—>onStart()—>onResume()—>新Activity的onStop() —>onDestory(); - Activity1上弹出对话框Activity2
Activity1的onPause()—>Activity2的onCreate()—>onStart()—>onResume() - 关闭屏幕/按Home键
Activity2的onPause()—>onStop()—>Activity1的onStop() - 点亮屏幕/回到前台
Activity2的onRestart()—>onStart()—>Activity1的onRestart()—>onStart()—>Activity2的onResume() - 关闭对话框Activity2
Activity2的onPause()—>Activity1的onResume()—>Activity2的onStop()—>onDestroy() - 销毁Activity1
onPause()—>onStop()—>onDestroy()
异常情况下的生命周期分析
资源相关的系统配置发生改变导致Activity被杀死并重新创建
拿最简单的图片来说,当我们把一张图片放在drawable中的时候,就可以通过Resources去获取这张图片。同时为了兼容不同的设备,我们可能还需要在其它一些目录下放置不同的图片,比如drawable-xhdpi之类,当应用程序启动时,系统就会根据当前设备的情况去加载合适的Resources资源,比如横屏手机和竖屏手机会拿到两张不同的图片(设定了landscape或者portrait状态下的图片)。比如之前Activity处于竖屏状态,如果突然旋转屏幕,由于系统配置发生了改变,在默认情况下,Activity会被销毁并且重新创建,当然我们也可以阻止系统重新创建我们的Activity。
默认情况下,如果Activity不做特殊处理,那么当系统配置发生改变之后,Activity就会销毁并且重新创建,生命周期如下图:
当系统配置发生改变后,Activity会被销毁,其onPause,onStop,onDestroy均会被调用,同时由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity的状态。这个方法的调用时机是在onStop之前,它和onPause没有既定的时序关系,它既可能在onPause之前调用,也有可能在onPause之后调用。需要强调的是,这个方法只会出现在Activity被异常终止的情况下,正常情况下系统不会回调这个方法。当Activity被重新创建后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法。因此,我们可以通过onRestoreInstanceState和onCreate方法来判断Activity是否被重建了,如果被重建了,我们就取出之前的数据恢复,从时序上来说,onRestoreInstanceState的调用时机在onStart之后。
在onSaveInstanceState和onRestoreInstanceState方法中,系统自动为我们做了一定的恢复工作。当Activity在异常情况下需要重新创建时,系统会默认为我们保存当前的Activity视图结构,并且在Activity重新启动后为我们恢复这些数据,比如文本框中用户输入的数据、ListView滚动的位置等,这些View相关的状态系统都会默认恢复。具体针对某一个特定的View系统能为我们恢复哪些数据,我们可以查看View的源码。和Activity一样,每个View都有onSaveInstanceState和onRestoreInstanceState这两个方法,看一下它们的具体实现,就能知道系统能够为每一个View恢复哪些数据。
关于保存和恢复View层次结构,系统的工作流程是这样的:首先Activity被意外终止时,Activity会调用onSaveInstanceState去保存数据,然后Activity会委托Window去保存数据,接着Window再委托上面的顶级容器去保存数据,顶层容器是一个ViewGroup,一般来说它很可能是DecorView。最后顶层容器再去一一通知它的子元素来保存数据,这样整个数据保存过程就完成了。可以发现,这是一种典型的委托思想,上层委托下层,父容器委托子容器去处理一件事件,这种思想在Android 中有很多的应用,比如View的绘制过程、事件分发等。接下来举个例子,拿TextView来说,我们分析一下它到底保存了哪些数据。
1 |
|
从上述源码可以看到,TextView保存了自己的文本选中状态和文本内容。下面我们看下实际的例子,对比一下Activity正常终止和异常终止的不同,同时验证系统的数据恢复能力。为了方便,我们采用旋转屏幕来异常终止Activity。代码很简单,只有一个EditText。需要注意的是:
1 | Note: In order for the android system to restore the state of the views in your activity, each view must have a unique ID, supplied by the android:id attribute. |
Android系统存储和还原View的状态,该View必须有一个唯一的ID。也就是说,如果我们的EditText并没有设置唯一的ID,在输入内容然后旋转屏幕,使Activity异常终止后,数据并不会恢复。给控件加了ID,然后再测试,就可以恢复数据了。另外,在AndroidManifest中给Activity配置android:screenOrientation=“sensor”,只是给Activity添加了屏幕自动旋转切换,并不会打开手机的屏幕旋转功能。不过Activity中设置的screenOrientation将优先于系统的自动转屏设置,也就是说如果系统禁止了屏幕旋转,设置过screenOrientation属性的Activity仍然可以旋转。
在我们旋转屏幕后,Activity被销毁重建,我们输入的文本被正确还原了,说明系统能够自动地做一些View层次结构方面的数据存储和恢复。下面再用一个例子来验证我们自己做数据存储和恢复的情况。代码如下:
1 |
|
上面的代码很简单,首先我们在onSaveInstanceState中存储一个字符串,然后当Activity被销毁并重新创建后,我们再去获取之前存储的字符串。接收的位置可以选择onRestoreInstanceState或者onCreate,两者的区别是:onRestoreInstanceState一旦被调用,其参数Bundle savedInstanceState一定有值,我们不用额外判断是否为空;但是onCreate不行,onCreate如果是正常启动的话,其参数Bundle onSaveInstanceState为null,所以需要额外判断。这两个方法我们选择任意一个都可以进行数据恢复,但是官方文档建议我们使用onRestoreInstanceState去恢复数据。
Activity被销毁后调用了onSaveInstanceState来保存数据,重新创建以后在onCreate和onRestoreInstanceState中都能够正确恢复之前存储的数据。针对onSaveInstanceState方法还有一点需要说明,那就是系统只会在Activity即将被销毁并且有机会重新显示的情况下才会去调用它。考虑这么一种情况,当Activity正常销毁的时候,系统不会调用onSaveInstanceState,因为被销毁的Activity不可能再次被显示。我们可以对比一下旋转屏幕所造成的Activity异常销毁,这个过程和正常停止Activity是不一样的,因为旋转屏幕后,Activity被销毁的同时会立刻创建新的Activity实例,这个时候Activcity有机会再次立刻展示,所以系统要进行数据存储。这里可以简单地这么理解,系统只在Activity异常终止的时候才会调用onSaveInstanceState和onRestoreInstanceState来存储和恢复数据,其它情况不会触发这个过程,但是按Home键或者启动新Activity仍然会单独触发onSaveInstanceState的调用。
资源内存不足导致低优先级的Activity被杀死
这个情况我们不好模拟,但是其数据存储和恢复过程和情况一是一致的,这里我们描述一下Activity的优先级情况。Activity按照优先级的从高往低,可以分为如下三种:
- 前台Activity:正在和用户交互的Activity,优先级最高。
- 可见但非前台Activity:比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户直接交互。
- 后台Activity:已经被暂停的Activity,比如执行了onStop,优先级最低。
当系统内存不足的时候,系统就会按照上述优先级去杀死目标Activity所在的进程,并且在后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数据。如果一个进程中没有四大组件在执行,那么这个进程将很快被系统杀死,因此,一些后台工作不适合脱离四大组建而独自运行在后台中,这样进程很容易就被杀死了。比较好的方法是将后台工作放在Service中从而保证进程有一定的优先级,这样就不会轻易地被系统杀死。
上面分析了系统的数据存储和恢复机制,当系统配置发生改变后,Activity会被重新创建,不过也有办法不重新创建。系统配置中有很多内容,如果当某项内容发生改变后,我们不想系统重新创建Activity,可以给Activity指定configChangs属性。比如不想让Activity在屏幕旋转时重新创建,就可以给configChangs属性加上orientation这个值:
1 | android:configChanges="orientation" |
如果想指定多个值的话可以用“|”连接起来。系统配置中所含的项目是非常多的,下面介绍每个项目的含义,如下表所示:
configChanges的项目和含义
项目 | 含义 |
---|---|
mcc | SIM卡唯一标识IMSI(国际移动用户识别码)中的国家代码,由三位数字组成,中国为460。此项标识mcc代码发生了改变。 |
mnc | SIM卡唯一标识IMSI(国际移动用户识别码)中的运营商代码,由两位数字组成,中国移动TD系统为00,中国连通为01,中国电信为03。此项标识mnc发生了改变。 |
locale | 设备的本地位置发生了改变,一般指切换了系统语言。 |
touchscreen | 触摸屏发生了改变,正常情况下无法发生,可以忽略。 |
keyboard | 键盘类型发生了改变,比如用户使用了外插键盘。 |
keyboardHidden | 键盘的可访问性发生了改变,比如用户调出了键盘。 |
navigation | 系统导航方式发生了改变,比如采用了轨迹球导航。(这通常不应该发生,举例:连接蓝牙键盘,连接后确实导致了navigation的类型发生变化。因为连接蓝牙键盘后,我可以使用方向键来navigate了。) |
screenLayout | 屏幕的布局发生改变,很可能是用户激活了另外一个显示设备。 |
fontScale | 系统字体缩放比例发生了改变,比如用户选择了一个新字号。 |
uiMode | 用户界面模式发生了改变,比如是否开启了夜间模式。(API8新添加) |
orientation | 屏幕方向发生了改变,这个是最常用的,比如旋转了手机屏幕。 |
screenSize | 当屏幕的尺寸信息发生了改变,当旋转设备屏幕时,屏幕尺寸会发生变化,这个选项比较特殊,它与编译选项有关,当编译选项中的minSdkVersion 和 targetSdkVersion 均低于13时,此选项不会导致Activity重启,否则会导致Activity重启。(API 13新添加) |
smallestScreenSize | 设备的物理屏幕尺寸发生改变,这个选项与屏幕的方向没有关系,仅仅表示在实际的物理屏幕的尺寸改变的时候发生,例如用户切换到了外部的显示设备,这个选项和screenSize一样,与编译选项有关,当编译选项中的minSdkVersion 和 targetSdkVersion 均低于13时,此选项不会导致Activity重启,否则会导致Activity重启。(API 13新添加) |
layoutDirection | 当布局方向发生变化,这个属性用的比较少,正常情况下无须修改布局的layoutDirection属性。(API 17 新添加) |
从上面的属性中我们可以知道,如果没有在Activity的configChanges属性中指定该选项的话,当系统配置发生改变后就会导致Activity重新创建。上面表格中的项目很多,但是我们常用的只有locale、orientation、keyboardHidden这三个选项,其它很少使用。需要注意的是screenSize和smallestScreenSize,它们的行为和编译选项有关,但和运行环境无关。所以当minSdkVersion 和 targetSdkVersion 有一个大于13时,为了防止旋转屏幕时Activity重启,除了orientation,还要加上screenSize,如下代码:
1 | android:configChanges="orientation|screenSize" |
这样Activity便不会重新创建了,并且也没有调用 onSaveInstanceState 和 onRestoreInstanceState 来存储和恢复数据,取而代之的是系统调用了Activity的onConfigurationChanged方法,这个时候我们就可以做一些自己的特殊处理了。
参考资料:
《Android 开发艺术探索》任玉刚 第1章 1.1 Activity的生命周期全面分析
djun100 Android Activity生命周期大全详解
厘米姑娘 要点提炼|开发艺术之Activity