平常在移动开发过程中,说起Activity的生命周期,开发人员不会陌生,但是此处概念切勿混淆,其生命周期分为典型情况下的生命周期与异常情况下的生命周期。第一种代表在用户交互情况下,Activity所经历的生命周期变化;第二种是指Activity被系统回收或者当前设备的Configration发生改变从而导致Activity被销毁重建。我们通常较熟悉Activity的典型生命周期,从而稍有忽略异常情况下的生命周期,仅以此篇来总结剖析这两种情况下的Activity生命周期,及存在的重点和易忽略的问题。
一.典型情况下的生命周期分析
1. 七大基本生命周期介绍
在正常情况下,Activity会经历如下生命周期:
(1)onCreate
含义:表示Activity正在被创建,这是生命周期的第一个方法。
处理事件:在此方法中,开发者可以做一些初始化工作,例如最常见的调用setContentView
方法去加载界面布局资源,初始化Activity所需的数据。
(2)onRestart
含义:表示Activity正在重新启动。
注意:一般情况下,若当前Activity从不可见状态重新变成可见状态时,onRestart
方法会被调用。这种情形一般是用户行为所致,例如用户按Home键切换到桌面 或者 用户打开了一个新的 Activity,此时当期Activity会暂停,也就是调用生命周期中的onPause
和onStop
方法,接着用户又回到此Activity,onRestart
方法会被回调。
(3)onStart
含义:表示Activity正在被启动。
注意:此时Activity已经是可见状态了,但是还没有出现在前台,无法与用户交互。这种状态可以理解成:Activity已经显示出来了,可我们还看不到。
(4)onResume
含义:表示Activity已经可见了,并且出现在前台开始活动。
注意:要注意此方法与 onStart
的对比,两者都表示Activity已经可见,但是onStart
时的Activity还在后台,而onResume
时的Activity已经显示到前台。
(5)onPause
含义:表示Activity正在停止。
注意:在正常情况下,当onPause
被调用时,紧接着onStop
方法会被调用。在特殊情况下,如果这个时候快速地再回到当前Activity,那么onStop
方法不会被调用,调用onResume
,但是这属于极端情况,用户操作很难实现这一场景。
处理事件:此时可以做一些存储数据、停止动画等工作,但是注意不能做耗时操作,因为这会影响到新Activity的显示,onPause
必须先执行完,新的ActivityonResume
方法才会被调用。
(6)onStop
含义:表示Activity正在停止。
处理事件:此时可以做一些稍微重量级的回收工作,但同样不能太耗时。
(7)onDestroy
含义:表示Activity即将被销毁。
处理事件:这是Activity生命周期的最后一个回调,在这里,我们可以做一些回收工作和最终的资源释放。
2. 常用场景方法回调
(1)针对一个特定的 Activity,第一次启动,回调方法如下:
onCreate –> onStart –> onResume
(2)当用户打开新的Activity或者切换桌面时,回调如下:
onPause –> onStop
这里有一种特殊情况,如果新Activity 采用了透明主题,那么当前Activity不会回调 onStop
(3)当用户再次回到原Activity时,回调如下:
onRestart –> onStart –> onResume
(4)当用户按 back 键回退时,回调如下:
onPause –> onStop –> onDestroy
(5)当Activity 被系统回收后再次打开,生命周期方法回调过程和(1)相同。注意是生命周期方法一样,不代表所有的过程一样。
(6)从整个生命周期来说,onCreate和onDestroy是配对的,分别标识Activity的创建和销毁,并且只可能有一次调用。从Activity是否可见来说,onStart和onStop是配对的,随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可以被调用多次;从Activity是否在前台来说,onResume和onPause是配对的,随着用户的操作或者设备屏幕的点亮和熄灭,这两个方法可以被调用多次。
3. 典型生命周期下的问题剖析
(1)onStart 和 onResume,onPause 和 onStop 从描述上来看差不多,有什么实质上的不同吗?
从实际使用过程来说,onStart 和 onResume、onPause 和 onStop看似差不多,但是Android系统不会提供具有重复作用的接口。根据以上使用场景的分析,这两个配对的回调分别代表不同的意义,onStart和onStop是从Activity是否可见这个角度来回调的,而onResume和onPause是从Activity是否在前台这个角度来回调的,除了这个区别,在实际应用中没有其他明显区别。
(2)假设当前Activity为A,如果这时用户打开一个新的Activity B,那么B的onResume方法和A的onPause方法哪个先执行呢?
分析方法一:
此问题追根溯源关系到Activity的启动过程,此过程相当复杂,涉及到 Instrumentation、ActivityThread 和 ActivityManagerService(简称AMS),这里并不详细分析此过程,只从此问题角度出发简单分析,详细过程可看此篇blog,也是由本人总结而来:http://blog.csdn.net/itermeng/article/details/71250158
首先简单描述Activity启动过程:启动Activity的请求会交给 Instrumentation 处理,它通过 Binder 向 AMS 发送请求,AMS 内部维护这一个 ActivityStack 并负责栈内的 Activity 的状态同步,AMS 通过 ActivityThread 来同步 Activity的状态而完成声明周期方法的调用。
在ActivityStack 中的 resumeTopActivityInnerLocked
方法中,有如下代码:
//We need to start pausing the current activity so the top one
//can be resumed ...
boolean dontWaitForPause = (next.info.flags& ActivityInfo.FLAG_RESUME_WHILE_PAUSING) != 0;
boolean pausing = mStackSupervisor.pausebackStacks(userLeaving, true, dontWaitForPause);
if(mResumedActivity != null){
pausing != startPausingLocked(userLeaving, false, true, dontWaitForPause);
if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Pausing" + mResumedActivity);
}
上述代码可以看出,在新Activity启动之前,栈顶的Activity需要先 onPause
后,新Activity才能启动。最终,在 ActivityStackSupervisor 中的 realStartActivityLocked
方法会调用如下代码:
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info,
new Configuration(mService.mConfiguration), r.compat,
app.repProcState, r.icicle, results, newIntents, !andResume,
mService.isNextTransitionForward(), profileFile, profileFd,
profileAutoStop);
该 app.thread 的类型是 IApplicationThread,而IApplicationThread的具体实现是 ActivityThread 中的 ApplicationThread。 所以,这段代码实际调用了ActivityThread中的 ApplicationThread类中的scheduleLaunchActivity
方法,而此方法最终会完成新Activity的onCreate
、onStart
、onResume
的调用过程。
因此,可得出结论:系统会先调用原先Activity的onPause
方法,再启动新Activity,即调用它的onCreate
、onStart
、onResume
方法。
分析方法二:
以上分析方法是从源码底层的角度去分析问题,未了解过Activity启动相关知识的初学者可能理解起来有些费劲,此方法比较简单明了,直接编写两个Activity,从日志打印的结果来证实分析方法一的结论,代码如下:
public class OneActivity extends AppCompatActivity {
private static final String Tag = "OneActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_one);
}
@Override
protected void onPause() {
super.onPause();
Log.d(Tag, "onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.d(Tag, "onStop");
}
}
public class TwoActivity extends AppCompatActivity {
private static final String Tag = "TwoActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_two);
}
@Override
protected void onStart() {
super.onStart();
Log.d(Tag, "onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(Tag, "onResume");
}
}
日志打印结果:
通过日志打印,再次证实了以上结论。注意:以上的源码分析版本来自于Android 5.0,也许你会怀疑版本不同其逻辑可能有改变,但是作为Android运行过程的基本机制,随着版本的更新并不会有太大影响,因为Android系统也需要兼容性,只能说Android运行的基本机制在不同版本上具有延续性。
最后,在Android官方文档中对onPause
方法的解释有提到:不能在此方法中做重量级的操作,因为此方法执行完后新Activity才能处于前台(即新Activity被调用onResume
方法)。此话也间接证明了以上结论。通过对此问题的一系列分析,可知onPause
、onStop
都不能执行耗时的操作,尤其是onPause
方法,所以尽量在onStop
方法中做操作,从而使得新Activity尽快显示出来并切换到前台!
二. 异常情况下的生命周期分析
我们知道,Activity除了受用户操作所导致的正常的生命周期方法调度,还有一些异常的情况,比如当资源相关的系统配置发生改变以及系统内存不足时,Activity就可能被杀死。下面具体分析这两种情况:
1. 资源相关的系统配置发生改变
在理解此问题之前,简单说明一下系统的资源加载机制:拿最简单的图片来说,当我们把一张图片放到 drawable目录后,就可以通过Resources去获取这张图片。同时为了兼容不同的设备,还需要在放置到不同要求的目录中,比如 drawable-mdpi 、drawable-hdpi。当应用程序启动时,系统会根据当前设备的情况去加载合适的Resources资源,比如横屏和竖屏情况下手机会展现两张不同的图片(设定了landscape 或 portrait状态下的图片)。若当前Activity处于竖屏状态,突然旋转屏幕,由于系统配置发送改变,在默认情况下Activity会被销毁并且重新创建(当然也可以阻止系统重新创建)。
了解了简单的资源加载机制后,我们知道在默认情况下,若Activity不做特殊处理,那么当系统配置发生改变后,Activity就会被销毁并重新创建,其生命周期如下所示:
(1) onSaveInstanceState 介绍
当系统配置发生改变后,Activity会被销毁,其 onPause、onStop、onDestroy均会被调用,而且由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState 来保存当前Activity的状态。这个方法调用时机是在onStop之前,它和onPause没有规定的时序关系。需要强调的是,此方法只会出现在Activity被异常终止的情况下,正常情况下不会回调这个方法。
(2) onRestoreInstanceState 介绍
当Activity被重新创建后,系统会调用 onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState 方法所保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法,因此可以通过这两个方法来判断Activity是否被重建了,若被重建可以取出之前保存的数据并恢复。注意:从时序上来说,onRestoreInstanceState的调用时机在onStart之后。
(3) onSaveInstanceState 搭配 onRestoreInstanceState
同时需要注意,在onSaveInstanceState 和 onRestoreInstanceState方法中,系统自动为我们做了一定的恢复工作,当Activity在异常情况下需要重新创建时,系统会默认保存当前Activity的试图结构,并且在Activity重启后为我们恢复这些数据,比如文本框中用户输入的数据、ListView滚动的位置等,这些View相关的状态系统都能够自动恢复。具体针对某一特定的View,系统可以恢复对应的数据。在View的源码中,和Activity一样,每个View都有onSaveInstanceState 和 onRestoreInstanceState方法。
(4) 以TextView为例
关于保存和恢复View层次结构,系统工作流程如下:
- 首先Activity被意外终止时会调用onSaveInstanceState 去保存数据
- 然后Activity会委托 Window去保存数据
- 接着Window再委托它上面的顶级容器去保存数据。
- 最后顶层容器(顶层容器是一个ViewGroup,一般来说是DectorView)再一一通知它的子元素来保存数据,这样整个数据保存过程就完成了。
以上过程时一种典型的委托思想,上层委托下层、父容器委托子容器去处理一件事,这种思想其实在Android中应用广泛,比如View的绘制过程、时间分发等。数据恢复过程便是如此,接下来以TextView为例,分析它保存了哪些数据。
【TextView类源码】
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
// Save state if we are forced to
boolean save = mFreezesText;
int start = 0;
int end = 0;
if (mText != null) {
start = getSelectionStart();
end = getSelectionEnd();
if (start >= 0 || end >= 0) {
// Or save state if there is a selection
save = true;
}
}
if (save) {
SavedState ss = new SavedState(superState);
// XXX Should also save the current scroll position!
ss.selStart = start;
ss.selEnd = end;
if (mText instanceof Spanned) {
Spannable sp = new SpannableString(mText);
for (ChangeWatcher cw : sp.getSpans(0, sp.length(), ChangeWatcher.class)) {
sp.removeSpan(cw);
}
if (mEditor != null) {
removeMisspelledSpans(sp);
sp.removeSpan(mEditor.mSuggestionRangeSpan);
}
ss.text = sp;
} else {
ss.text = mText.toString();
}
if (isFocused() && start >= 0 && end >= 0) {
ss.frozenWithFocus = true;
}
ss.error = getError();
return ss;
}
return superState;
}
从上述源码可以看出,TextView保存了自己的文本选中状态和文本内容,而且通过查看其 onRestoreInstanceState 方法的源码,可以证实它确实恢复了这些数据。例如在一个文本框中输入了“恢复的文本”后旋转屏幕,Activity被销毁又被重新创建,此时文本框的内容会被正确还原。这说明系统的确能够自动地做一些View层次结构方面的数据存储和恢复。
(5)Activity实例验证数据存储和恢复
下面通过个简单的Activity实例来验证数据存储和恢复,代码如下:
public class MainActivity extends AppCompatActivity {
private String Tag = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(savedInstanceState != null){
String test = savedInstanceState.getString("extra_test");
Log.d(Tag, "[onCreate]restore extra_test:" + test);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(Tag, "onSaveInstanceState");
outState.putString("extra_test","test");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
String test = savedInstanceState.getString("extra_test");
Log.d(Tag, "[onRestoreInstanceState] extra_test:" + test);
}
@Override
protected void onPause() {
super.onPause();
Log.d(Tag, "onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.d(Tag, "onStop");
}
@Override
protected void onDestroy() {
super.onStop();
Log.d(Tag, "onDestroy");
}
}
如上所示,代码并不难,主要逻辑为:首先在onSaveInstanceState()
方法中存储一个字符串,然后当Activity被销毁并重新创建后,再去获取之前存储的字符串。注意,此处获取可以在onRestoreInstanceState(Bundle savedInstanceState)
或 onCreate(Bundle savedInstanceState)
方法,二者的区别是:onRestoreInstanceState 方法一旦被调用,其参数 savedInstanceState 一定是有值的,即非空;而 onCreate 方法若是正常启动的,其参数为空,所以需要额外的判断(官方文档建议通过 onRestoreInstanceState 方法来恢复数据)。经过前几小点的分析,可以通过此例来证实运行顺序,结果如下图:
如上结果,Activity被销毁后声明周期依次调用 onPause –> onSaveInstanceState –> onStop –> onDestroy,在onSaveInstanceState
方法中保存数据,Activity重新创建后可在onCreate
和 onResaveInstanceState
中正确恢复之前存储的字符串,此例也证实以上的结论。
onSaveInstanceState 注意点
关于onSaveInstanceState
方法需注意一点,系统只会在Activity即将被销毁并且有机会重新显示的情况下才会调用此方法。试考虑一种情况,当Activity正常销毁时,系统不会调用onSaveInstanceState
方法,因为被销毁的Activity何时再被显示不得而知,换句话说,它可能立刻被用户重新打开显示,可能过段时间再被打开显示。相较于旋转屏幕而造成Activity异常销毁的情况,两者过程区别就非常明显了,此时旋转后的Activity销毁后的结果只有一个,即立刻重新创建新的Activity实例,所以它是百分百会再次被显示,所以系统会进行数据存储。
结论
系统只在Activity异常终止的情况下才会调用onSaveInstanceState
、onResaveInstanceState
方法来存储和恢复数据,其它情况下不会触发此过程。
2. 资源不足导致低优先级的Activity被杀死
此种情况不好模拟,但是其数据存储和恢复过程与上述情况完全一致。首先描述Activity优先级情况,Activity可按照优先级从高到低分为如下三种:
(1)Activity优先级
- 前台Activity :正在和用户进行交互的Activity,优先级最高。
- 可见但非前台的Activity:例如Activity中弹出的对话框,导致Activity可见但是位于后台无法与用户直接交互。
- 后台Activity:已经被暂停的Activity,例如已经执行了生命周期的
onStop
方法,优先级最低。
(2)内存不足时,系统处理Activity重点
当系统内存不足时,系统会按照上述优先级去杀死目标Activity所在的进程,并在后续通过onSaveInstanceState
、onResaveInstanceState
方法来存储和恢复数据。如果一个进程中没有四大组件在执行,此进程很快会被系统杀死。因此,一些重要的后台工作不适合脱离四大组件而独自运行在后台,这样进程很容易被杀死。比较好的方法是将后台工作放入Service中而保证进程有一定的优先级,这样进程不会轻易被系统杀死。
(3)系统配置改变,防止Activity重新创建 —— configChanges
以上分析了系统的数据存储和恢复机制,即当系统发生改变后,Activity会被重新创建,接下来分析Activity不被重新创建相关内容。
系统配置中有很多内容,当某项内容发生改变后,若不想重新创建Activity,可以给Activity指定 configChanges 属性。例如不想让 Activity在屏幕旋转时重新创建,就可以在AndroidManifest.xml配置文件中给对应Activity的属性设置 orientation值,代码如下:
android:configChanges="orientation"
若想同时指定多个值,可以用“|”连接起来,例如:
android:configChanges="orientation|keyboardHidden"
configChanges 属性重点选项
项目 | 含义 |
---|---|
mcc | SIM卡唯一标识IMSI(国际移动用户识别码)中的国家代码,由三位数字组成,中国为460。此项标识mcc代码发生了改变 |
mnc | SIM卡唯一标识IMSI(国际移动用户识别码)中的运营商代码,由两位数字组成,中国移动TD系统为00,中国联通为01,中国电信为03。此项标识mnc发生改变 |
locale | 设备的本地位置发生了改变,一般指切换了系统语言 |
keyboard | 键盘类型发生了改变,例如用户使用了外插键盘 |
keyboardHidden | 键盘的可访问性发生了变化,例如用户调出了键盘 |
screenLayout | 屏幕布局发生了改变,很可能用户激活了一个显示设备 |
fontScale | 系统字体缩放比例发生了改变,例如用户选择了一个新字号 |
uiMode | 用户界面模式发生了变化,例如开启夜间模式(API8新添加) |
orientation | 屏幕方向发生了改变,例如旋转手机屏幕 |
screenSize | 当屏幕的尺寸信息发生了改变,当旋转设备屏幕时,屏幕尺寸会发送变化。此选项较特殊,与编译选项有关,当编译选项的 minSdkVersion 和 targetSdkVersion 均低于13时,此选项不会导致Activity重启,反之会导致重启(API)重启 |
smallestScreenSize | 设备的物理屏幕尺寸发生改变,这个项目和屏幕的方向没关系,仅仅表示在实际的物理屏幕的尺寸改变的时候改变,例如用户切换到了外部的显示设备。此选项与screenSize 一样,当编译选项的 minSdkVersion 和 targetSdkVersion 均低于13时,此选项不会导致Activity重启,反之会导致重启(API)重启 |
从上表可得,若不在AndroidManifest.xml配置文件中给对应Activity的属性设置 orientation选项,当配置发生改变后会导致Activity重新创建。以上最常用的只有 locale、keyboard、orientation这三个选项。
(4)Acticity实例验证防止重新创建
【AndroidManifest.xml配置文件】
<activity
android:name=".activity.MainActivity"
android:configChanges="orientation|screenSize"/>
注意:本项目的minSdkVersion 和 targetSdkVersion 均高于13时,所以在 configChanges 属性中还需额外指定screenSize选项。
public class MainActivityextends AppCompatActivity {
private String Tag = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test2);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(Tag, "onSaveInstanceStateresponse");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.d(Tag, "onRestoreInstanceState response");
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.d(Tag, "onConfigurationChanged, newOrientation:" + newConfig.orientation);
}
}
注意:检测configChanges 属性中的值变化,是在Activity的 onConfigurationChanged(Configuration newConfig)
方法内,相关属性选项信息存储于方法参数newConfig中。
日志打印结果:
由上图结果可知,转向横屏时打印Log,参数newConfig.orientation的值为2,代表横屏,重新转向竖屏时,打印1,代表竖屏,只有onConfigurationChanged
方法中的日志打印记录。如此可见,Activity确实没有被重建,也没有调用onSaveInstanceState
、 onRestoreInstanceState
方法来存储和恢复数据,取而代之的是调用了Activity的onConfigurationChanged
方法,此时若有特殊需求也可在此进行处理。
声明:以上内容总结于任玉刚老师的《Android开发艺术探索》,综合了个人理解,将知识点分成不同的小节,以博客的形式更好的总结出来。这本书真的是温故而知新,感恩~
若有错误,虚心指教~
希望对你们有帮助 :)