#### 1.1.2 异常情况下的生命周期分析
上一节我们分析了典型情况下Activity的生命周期,本节我们接着分析Activity在异常情况下的生命周期。我们知道,Activity除了受用户操作所导致的正常的生命周期方法调度,还有一些异常情况,比如当资源相关的系统配置发生改变以及系统内存不足时,Activity就可能被杀死。下面我们具体分析这两种情况。
**1.情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建**
理解这个问题,我们首先要对系统的资源加载机制有一定了解,这里不详细分析**系统的资源加载机制**,只是简单说明一下。**拿最简单的图片来说,当我们把一张图片放在drawable目录后,就可以通过Resources去获取这张图片。同时为了兼容不同的设备,我们可能还需要在其他一些目录放置不同的图片,比如drawable-mdpi、drawable-hdpi、drawable-land等。这样,当应用程序启动时,系统就会根据当前设备的情况去加载合适的Resources资源,比如说横屏手机和竖屏手机会拿到两张不同的图片(设定了landscape或者portrait状态下的图片)。比如说当前Activity处于竖屏状态,如果突然旋转屏幕,由于系统配置发生了改变,在默认情况下,Activity就会被销毁并且重新创建**,当然我们也可以阻止系统重新创建我们的Activity。
**在默认情况下,如果我们的Activity不做特殊处理,那么当系统配置发生改变后,Activity就会被销毁并重新创建**,其生命周期如图1-3所示。
:-: ![](https://img.kancloud.cn/45/40/4540116ed4f76b0bfa3ba6bc9cf646ff_1438x757.png)
图1-3 异常情况下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来说,我们分析一下它到底保存了哪些数据。
源码:TextView# onSaveInstanceState
```
@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 SpannableStringBuilder(mText);
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方法的源码,可以发现它的确恢复了这些数据,具体源码就不再贴出了,读者可以去看看源码。下面我们看一个实际的例子A,**来对比一下Activity正常终止和异常终止的不同,同时验证系统的数据恢复能力。为了方便,我们选择旋转屏幕来异常终止Activity**,如图1-4所示。
**示例A**
:-: ![](https://img.kancloud.cn/ab/b3/abb3db2821c808c7edcf3fad524c7e22_1436x508.png)
图1-4 Activity旋转屏幕后数据的保存和恢复
通过**图1-4可以看出,在我们选择屏幕以后,Activity被销毁后重新创建,我们输入的文本“这是测试文本”被正确地还原,这说明系统的确能够自动地做一些View层次结构方面的数据存储和恢复**。下面再用一个例子B,来验证我们自己做数据存储和恢复的情况,代码如下:
**示例B**
@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]restore extra_test:" + test);
}
上面的代码很简单,首先我们**在onSaveInstanceState中存储一个字符串,然后当Activity被销毁并重新创建后,我们再去获取之前存储的字符串。接收的位置可以选择onRestoreInstanceState或者onCreate**,二者的区别是:**onRestoreInstanceState一旦被调用,其参数Bundle savedInstanceState一定是有值的,我们不用额外地判断是否为空;但是onCreate不行,onCreate如果是正常启动的话,其参数Bundle savedInstanceState为null,所以必须要额外判断。这两个方法我们选择任意一个都可以进行数据恢复,但是官方文档的建议是采用onRestoreInstanceState去恢复数据**。下面我们看一下运行的日志,如图1-5所示。
:-: ![](https://img.kancloud.cn/5f/87/5f87fd555a2c8d9d6b8a92a8a5e77396_1439x336.png)
图1-5 系统日志
如图1-5所示,**Activity被销毁了以后调用了onSaveInstanceState来保存数据,重新创建以后在onCreate和onRestoreInstanceState中都能够正确地恢复我们之前存储的字符串**。这个例子很好地证明了上面我们的分析结论。**针对onSaveInstanceState方法**还有一点需要说明,那就是**系统只会在Activity即将被销毁并且有机会重新显示的情况下才会去调用它**。考虑这么一种情况,**当Activity正常销毁的时候,系统不会调用onSaveInstanceState,因为被销毁的Activity不可能再次被显示**。这句话不好理解,但是我们可以对比一下**旋转屏幕所造成的Activity异常销毁,这个过程和正常停止Activity是不一样的,因为旋转屏幕后,Activity被销毁的同时会立刻创建新的Activity实例,这个时候Activity有机会再次立刻展示,所以系统要进行数据存储。这里可以简单地这么理解,系统只在Activity异常终止的时候才会调用onSaveInstanceState和onRestoreInstanceState来存储和恢复数据,其他情况不会触发这个过程**。
>[info]注意:但是按Home键后者启动新的Activity仍然会单独触发onSaveInstanceState的调用,当然,这种情况和上面所说的异常终止Activity也不一样。
**2.情况2:资源内存不足导致低优先级的Activity被杀死**
这种情况我们不好模拟,但是其数据存储和恢复过程和情况1完全一致。这里我们描述一下Activity的优先级情况Activity按照优先级从高到低,可以分为如下三种:
* (1)**前台Activity**——正在和用户交互的Activity,**优先级最高**。
* (2)**可见但非前台Activity**——比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户直接交互。
* (3)**后台Activity**——已经被暂停的Activity,比如执行了onStop,**优先级最低**。
**当系统内存不足时,系统就会按照上述优先级去杀死目标Activity所在的进程,并在后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数数据。如果一个进程中没有四大组件在执行,那么这个进程将很快被系统杀死,因此,一些后台工作不适合脱离四大组件而独自运行在后台中,这样进程很容易被杀死。比较好的方法是将后台工作放入Service中从而保证进程有一定的优先级,这样就不会轻易地被系统杀死**。
上面分析了系统的数据存储和恢复机制,我们知道,**当系统配置发生改变后,Activity会被重新创建,那么有没有办法不重新创建呢?答案是有的**,接下来我们就来分析这个问题。**系统配置中有很多内容,如果当某项内容发生改变后,我们不想系统重新创建Activity,可以给Activity指定configChanges属性。比如不想让Activity在屏幕旋转的时候重新创建,就可以给configChanges属性添加orientation这个值**,如下所示。
```
android:configChanges="orientation"
```
**如果我们想指定多个值,可以用“|”连接起来**,比如android:configChanges="orientation|keyboardHidden"。系统配置中所含的项目是非常多的,下面介绍每个项目的含义,如表1-1所示。
:-: 表1-1 configChanges的项目和含义
![](https://img.kancloud.cn/93/c0/93c05be329eb581e8bd1778b996ad507_1012x742.png)
从表1-1可以知道,**如果我们没有在Activity的configChanges属性中指定该选项的话,当配置发生改变后就会导致Activity重新创建。上面表格中的项目很多,但是我们常用的只有locale、orientation和keyboardHidden这三个选项,其他很少使用**。
>[info]需要注意的是screenSize和smallestScreenSize,它们两个比较特殊,它们的行为和编译选项有关,但和运行环境无关。
下面我们再看一个demo,看看当我们指定了configChanges属性后,Activity是否真的不会重新创建了。我们所要修改的代码很简单,只需要在AndroidMenifest.xml中加入Activity的声明即可,代码如下:
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="19" />
<activity
android:name="com.ryg.chapter_1.MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.d(TAG, "onConfigurationChanged, newOrientation:" + newConfig.
orientation);
}
需要说明的是,由于编译时笔者指定的minSdkVersion和targetSdkVersion有一个大于13,所以为了防止旋转屏幕时Activity重启,除了orientation,我们还要加上screenSize,原因在上面的表格里已经说明了。其他代码还是不变,运行后看看log,如图1-6所示。
:-: ![](https://img.kancloud.cn/d6/da/d6dac9e19234330de6b9b246121fa3f1_1435x211.png)
图1-6 系统日志
由上面的日志可见,**Activity的确没有重新创建,并且也没有调用onSaveInstanceState和onRestoreInstanceState来存储和恢复数据,取而代之的是系统调用了Activity的onConfigurationChanged方法,这个时候我们就可以做一些自己的特殊处理了**。
- 前言
- 第1章 Activity的生命周期和启动模式
- 1.1 Activity的生命周期全面分析
- 1.1.1 典型情况下的生命周期分析
- 1.1.2 异常情况下的生命周期分析
- 1.2 Activity的启动模式
- 1.2.1 Activity的LaunchMode
- 1.2.2 Activity的Flags
- 1.3 IntentFilter的匹配规则
- 第2章 IPC机制
- 2.1 Android IPC简介
- 2.2 Android中的多进程模式
- 2.2.1 开启多进程模式
- 2.2.2 多进程模式的运行机制
- 2.3 IPC基础概念介绍
- 2.3.1 Serializable接口
- 2.3.2 Parcelable接口
- 2.3.3 Binder
- 2.4 Android中的IPC方式
- 2.4.1 使用Bundle
- 2.4.2 使用文件共享
- 2.4.3 使用Messenger
- 2.4.4 使用AIDL
- 2.4.5 使用ContentProvider
- 2.4.6 使用Socket
- 2.5 Binder连接池
- 2.6 选用合适的IPC方式
- 第3章 View的事件体系
- 3.1 View基础知识
- 3.1.1 什么是View
- 3.1.2 View的位置参数
- 3.1.3 MotionEvent和TouchSlop
- 3.1.4 VelocityTracker、GestureDetector和Scroller
- 3.2 View的滑动
- 3.2.1 使用scrollTo/scrollBy
- 3.2.2 使用动画
- 3.2.3 改变布局参数
- 3.2.4 各种滑动方式的对比
- 3.3 弹性滑动
- 3.3.1 使用Scroller7
- 3.3.2 通过动画
- 3.3.3 使用延时策略
- 3.4 View的事件分发机制
- 3.4.1 点击事件的传递规则
- 3.4.2 事件分发的源码解析
- 3.5 View的滑动冲突
- 3.5.1 常见的滑动冲突场景
- 3.5.2 滑动冲突的处理规则
- 3.5.3 滑动冲突的解决方式
- 第4章 View的工作原理
- 4.1 初识ViewRoot和DecorView
- 4.2 理解MeasureSpec
- 4.2.1 MeasureSpec
- 4.2.2 MeasureSpec和LayoutParams的对应关系
- 4.3 View的工作流程
- 4.3.1 measure过程
- 4.3.2 layout过程
- 4.3.3 draw过程
- 4.4 自定义View
- 4.4.1 自定义View的分类
- 4.4.2 自定义View须知
- 4.4.3 自定义View示例
- 4.4.4 自定义View的思想
- 第5章 理解RemoteViews
- 5.1 RemoteViews的应用
- 5.1.1 RemoteViews在通知栏上的应用
- 5.1.2 RemoteViews在桌面小部件上的应用
- 5.1.3 PendingIntent概述
- 5.2 RemoteViews的内部机制
- 5.3 RemoteViews的意义
- 第6章 Android的Drawable
- 6.1 Drawable简介
- 6.2 Drawable的分类
- 6.2.1 BitmapDrawable2
- 6.2.2 ShapeDrawable
- 6.2.3 LayerDrawable
- 6.2.4 StateListDrawable
- 6.2.5 LevelListDrawable
- 6.2.6 TransitionDrawable
- 6.2.7 InsetDrawable
- 6.2.8 ScaleDrawable
- 6.2.9 ClipDrawable
- 6.3 自定义Drawable
- 第7章 Android动画深入分析
- 7.1 View动画
- 7.1.1 View动画的种类
- 7.1.2 自定义View动画
- 7.1.3 帧动画
- 7.2 View动画的特殊使用场景
- 7.2.1 LayoutAnimation
- 7.2.2 Activity的切换效果
- 7.3 属性动画
- 7.3.1 使用属性动画
- 7.3.2 理解插值器和估值器 /
- 7.3.3 属性动画的监听器
- 7.3.4 对任意属性做动画
- 7.3.5 属性动画的工作原理
- 7.4 使用动画的注意事项
- 第8章 理解Window和WindowManager
- 8.1 Window和WindowManager
- 8.2 Window的内部机制
- 8.2.1 Window的添加过程
- 8.2.2 Window的删除过程
- 8.2.3 Window的更新过程
- 8.3 Window的创建过程
- 8.3.1 Activity的Window创建过程
- 8.3.2 Dialog的Window创建过程
- 8.3.3 Toast的Window创建过程
- 第9章 四大组件的工作过程
- 9.1 四大组件的运行状态
- 9.2 Activity的工作过程
- 9.3 Service的工作过程
- 9.3.1 Service的启动过程
- 9.3.2 Service的绑定过程
- 9.4 BroadcastReceiver的工作过程
- 9.4.1 广播的注册过程
- 9.4.2 广播的发送和接收过程
- 9.5 ContentProvider的工作过程
- 第10章 Android的消息机制
- 10.1 Android的消息机制概述
- 10.2 Android的消息机制分析
- 10.2.1 ThreadLocal的工作原理
- 10.2.2 消息队列的工作原理
- 10.2.3 Looper的工作原理
- 10.2.4 Handler的工作原理
- 10.3 主线程的消息循环
- 第11章 Android的线程和线程池
- 11.1 主线程和子线程
- 11.2 Android中的线程形态
- 11.2.1 AsyncTask
- 11.2.2 AsyncTask的工作原理
- 11.2.3 HandlerThread
- 11.2.4 IntentService
- 11.3 Android中的线程池
- 11.3.1 ThreadPoolExecutor
- 11.3.2 线程池的分类
- 第12章 Bitmap的加载和Cache
- 12.1 Bitmap的高效加载
- 12.2 Android中的缓存策略
- 12.2.1 LruCache
- 12.2.2 DiskLruCache
- 12.2.3 ImageLoader的实现446
- 12.3 ImageLoader的使用
- 12.3.1 照片墙效果
- 12.3.2 优化列表的卡顿现象
- 第13章 综合技术
- 13.1 使用CrashHandler来获取应用的crash信息
- 13.2 使用multidex来解决方法数越界
- 13.3 Android的动态加载技术
- 13.4 反编译初步
- 13.4.1 使用dex2jar和jd-gui反编译apk
- 13.4.2 使用apktool对apk进行二次打包
- 第14章 JNI和NDK编程
- 14.1 JNI的开发流程
- 14.2 NDK的开发流程
- 14.3 JNI的数据类型和类型签名
- 14.4 JNI调用Java方法的流程
- 第15章 Android性能优化
- 15.1 Android的性能优化方法
- 15.1.1 布局优化
- 15.1.2 绘制优化
- 15.1.3 内存泄露优化
- 15.1.4 响应速度优化和ANR日志分析
- 15.1.5 ListView和Bitmap优化
- 15.1.6 线程优化
- 15.1.7 一些性能优化建议
- 15.2 内存泄露分析之MAT工具
- 15.3 提高程序的可维护性