Activity异常生命周期中会涉及到onSaveInstanceState() 和 onRestoreInstanceState()
常见的异常情况有两种: 资源相关的系统配置发生改变 系统内存不足
资源相关的系统配置发生改变有:屏幕横竖屏切换,改变系统语言等
Activity异常情况下被杀死并重新创建的过程中就涉及到View的 保存和恢复,下面详细分析整个保存和恢复过程
1 举例子
假设我们这里有一个带有ImageView ,TextView 和 Switch控件的简单布局
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="@dimen/activity_horizontal_margin">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher"/>
<TextView
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="My Text"/>
<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dip"/>
</LinearLayout>
但是当我们滑动一下switch开关然后旋转屏幕方向,switch又回到了原来的状态。通常,安卓会自动保存这些View(一般是系统控件)的状态,但是为什么在我们的案例中不起作用了呢?
从源码中我们可以得到答案
2 从宏观上看看保存和恢复的源码
一、onSaveInstanceState
(1) Activity
首先从Activity中的onSaveInstanceState看起,毕竟,我们从这里开始
private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState";
protected void onSaveInstanceState(Bundle outState) {
// 1、对Window里面的View树进行状态保存
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
// 2、对Fragmet进行状态保存
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
可以看到上面分为两个部分:1、对View树进行状态保存
2、对Fragment进行状态保存
因为我们的Activity的视图是由View层次树或者Fragment构成。
这里调用了Window对象的saveHierarchyState,这里的mWindow对应于PhoneWindow,所以实际上调用了PhoneWindow.saveHierarchyState()
(2)PhoneWindow
@Override
public Bundle saveHierarchyState() {
Bundle outState = new Bundle();
if (mContentParent == null) {
return outState;
}
//数据保存的委托模式,从mContentParent(ViewGroup)开始,一层一层往下通知他的子元素
SparseArray<Parcelable> states = new SparseArray<Parcelable>();
mContentParent.saveHierarchyState(states);
outState.putSparseParcelableArray(VIEWS_TAG, states);
......
return outState;
}
我们主要来看看mContentParent.saveHierarchyState(states);mContentParent是整个Activity中View视图的顶层视图。它是一个ViewGroup类型。ViewGroup里面没有实现saveHierarchyState方法,它继承自View
(3) View
public void saveHierarchyState(SparseArray<Parcelable> container) {
dispatchSaveInstanceState(container);
}
(4)ViewGroup
在ViewGroup中复写了dispatchSaveInstanceState
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
super.dispatchSaveInstanceState(container);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
View c = children[i];
if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
c.dispatchSaveInstanceState(container);
}
}
}
首先调用了View的dispatchSaveInstanceState方法,目的是保存ViewGroup自身的当前状态然后遍历ViewGroup的子View,调用每个子View的dispatchSaveInstanceState方法
再来看看View中的dispatchSaveInstanceState
(5)View
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
;
//1、获取当前View需要保存的Parcelable
Parcelable state = onSaveInstanceState();
......
if (state != null) {
......
container.put(mID, state);
}
}
}
从这里我们知道,如果一个view需要保存它的当前状态,就必须给这个view一个id,因为它将作为key来存放该view的状态。例子中没有给switch一个id,所以switch的状态没能保存下来。另外,如果我们需要保存一个View的当前状态,我们可以重写view它自己的onSaveInstanceState方法,把需要保存的内容进行保存进可以了
(6)Textview
这里我们可以看看Textview是怎么重写onSaveInstance()方法的
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
boolean save = mFreezesText;
int start = 0;
int end = 0;
if (mText != null) {
start = getSelectionStart();
end = getSelectionEnd();
......
}
//在这里保存了选中位置的起点和终点
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);
......
ss.text = sp; }
else { ss.text = mText.toString(); }
......
if (mEditor != null)
{ ss.editorState = mEditor.saveInstanceState(); }
return ss;
}
return superState; }
从上面源码可以看出,Textview保存了自己的文本选中状态和文本内容二、onRestoreInstanceState
(1) Activity
还是从Activity开始,先看看Activity的onRestoreInstanceState
protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (mWindow != null) {
// 1、得到保存的状态
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
// 2、状态恢复
mWindow.restoreHierarchyState(windowState);
}
}
}
(2)PhoneWindow
轻车熟路,这里mWindow是指PhoneWindow上面已经说过了。
@Override
public void restoreHierarchyState(Bundle savedInstanceState) {
if (mContentParent == null) {
return;
}
// 得到所有View保存的状态
SparseArray<Parcelable> savedStates
= savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
if (savedStates != null) {
mContentParent.restoreHierarchyState(savedStates);
}
......
}
mContentParent是ViewGroup,ViewGroup没有重写restoreHierarchyState方法,它继承子View,下面来看看View的restoreHierarchyState代码,
(3)View
public void restoreHierarchyState(SparseArray<Parcelable> container) {
dispatchRestoreInstanceState(container);
}
(4)ViewGroup
ViewGroup复写了dispatchRestoreInstanceState
@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
// 1、执行父类的dispatchRestoreInstanceState方法,也就是View的dispatchRestoreInstanceState方法
// 目的是恢复当前ViewGroup的状态
// 2、递归遍历所有的子View来恢复所有子View的状态
super.dispatchRestoreInstanceState(container);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
View c = children[i];
if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
c.dispatchRestoreInstanceState(container);
}
}
}
(5)View
再来看看view中的dispatchRestoreInstanceState
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
if (mID != NO_ID) {
// 1、得到这个View保存的状态
Parcelable state = container.get(mID);
if (state != null) {
......
onRestoreInstanceState(state);
......
}
}
}
所以如果我们的一个View重写了onSaveInstanceState()方法进行了状态的保存,相应的就应该重写onRestoreInstanceState方法进行相应的状态恢复。
(6)Textview
我们看看Textview怎么恢复数据的
@Override
public void onRestoreInstanceState(Parcelable state) {
......
SavedState ss = (SavedState)state;
super.onRestoreInstanceState(ss.getSuperState());
// 恢复文本的显示
if (ss.text != null) {
setText(ss.text);
}
if (ss.selStart >= 0 && ss.selEnd >= 0) {
if (mText instanceof Spannable) {
......
Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
......
}
}
}
......
if (ss.editorState != null) {
createEditorIfNeeded();
mEditor.restoreInstanceState(ss.editorState);
}
}
从onRestoreInstanceState可以看出,的确对文本选中状态和文本内容进行了恢复3 总结
1 要保存View的状态,View必须要有id, SparseArray<Parcelable> container用来保存view的状态,这个容器的key就是每个view的id,value就是每个view的状态
整个saveInstanceState过程和restoreInstanceState过程都是一个委派的过程,从Activity到PhoneWindow,此过程创建sparseArray集合(稀疏数组),用来保存每个控件的状态对象(序列化对象Parcelable),然后把spareArray保存在Bundle中,接着调用viewGroup的dispatchSaveInstanceState方法,该方法会遍历viewGroup中的每个子元素,将子元素中onSaveInstanceState返回的结果保存作为sparseArray的值,控件的id作为sparseArray的键。
onSaveInstanceState则是从Bundle中获取到相应的sparseArray集合,接着调用ViewGroup的dispatchRestoreInstanceState方法,该方法会遍历viewGroup的每个子元素,然后从sparseArray中获取相应的状态对象(通过控件的id)并传入onRestoreInstanceState中
Activity中saveInstanceState(Bundle)和restoreInstanceState(Bundle)的bundle从何而来?运行机制?为什么Activity销毁后还可以从Bundle中恢复?
Bundle是序列化对象,Activity的Bundle保存了ViewGroup中创建的Bundle,ViewGroup的Bundle保存了sparseArray,sparseArray保存了控件的状态。
那么Activity中的Bundle来自哪里?怎么管理的?
猜测:Activity在容易销毁时会创建一个Bundle对象,然后传入saveInstanceState(Bundle)中,当Activity销毁时,序列化对象Bundle与相应的Activity会建立相应映射关系,并保存在AMS的集合中,当Activity重建时会去看是否有相应的Bundle存在,存在则恢复状态,此过程涉及到binder通信。
2 整个保存view层次的流程图如下:
(1)Activity方法onSaveInstanceState中 outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
(2)PhoneWindow方法saveHierarchyState中,
Bundle outState = new Bundle();
SparseArray<Parcelable> states = new SparseArray<Parcelable>();
mContentParent.saveHierarchyState(states);
outState.putSparseParcelableArray(VIEWS_TAG, states);
(3)ViewGroup中
for (int i = 0; i < count; i++) {
c.dispatchSaveInstanceState(container);
}
(4)View
Parcelable state = onSaveInstanceState();
container.put(mID, state);