日常的开发过程中难免会自定义一些view,view的状态数据保存及恢复也是需要考虑进内的,就会用到onSaveInstanceState和onRestoreInstanceState这两个方法。
onSaveInstanceState
顾名思义,保存实例状态。以CompoundButton为例:
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.checked = isChecked();
return ss;
}
该方法返回的是一个序列化对象,即SavedState,checked状态也是保存在该对象里面,看一下该类的实现:
static class SavedState extends BaseSavedState {
boolean checked;
/**
* Constructor called from {@link CompoundButton#onSaveInstanceState()}
*/
SavedState(Parcelable superState) {
super(superState);
}
/**
* Constructor called from {@link #CREATOR}
*/
private SavedState(Parcel in) {
super(in);
checked = (Boolean)in.readValue(null);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeValue(checked);
}
@Override
public String toString() {
return "CompoundButton.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " checked=" + checked + "}";
}
@SuppressWarnings("hiding")
public static final Parcelable.Creator CREATOR =
new Parcelable.Creator() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
代码简单,整个类即是序列化的实现,该类继承BaseSavedState,BaseSavedState在View里面定义,用于保存View的状态。BaseSavedState继承AbsSavedState,AbsSavedState则实现了Parcelable。代码就不贴了,在我们的自定义view里面,比葫芦画瓢,也实现一个继承BaseSavedState的类,把需要保存的状态利用该类保存即可。
onRestoreInstanceState
顾名思义,恢复实例状态。还是看CompoundButton的代码:
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setChecked(ss.checked);
requestLayout();
}
onSaveInstanceState返回的是SavedState对象,该方法会将SavedState对象传递过来,强制类型转换之后,先将父View的状态恢复过去,再从该对象里面拿到之前保存的状态进行恢复即可。
以上即是自定义view的状态保存的基本用法,到这里还不够,我们需要阅读一下源码,看一下如上两个方法是如何调用的,才能知其然,遇到问题时知道如何分析。
源码分析
所有的view的根view均是android.view.View,可以从这里下手看看上面两个方法是怎么调用的。调用onSaveInstanceState的代码如下:
/**
* Store this view hierarchy's frozen state into the given container.
*
* @param container The SparseArray in which to save the view's state.
*
* @see #restoreHierarchyState(android.util.SparseArray)
* @see #dispatchSaveInstanceState(android.util.SparseArray)
* @see #onSaveInstanceState()
*/
public void saveHierarchyState(SparseArray container) {
dispatchSaveInstanceState(container);
}
/**
* Called by {@link #saveHierarchyState(android.util.SparseArray)} to store the state for
* this view and its children. May be overridden to modify how freezing happens to a
* view's children; for example, some views may want to not store state for their children.
*
* @param container The SparseArray in which to save the view's state.
*
* @see #dispatchRestoreInstanceState(android.util.SparseArray)
* @see #saveHierarchyState(android.util.SparseArray)
* @see #onSaveInstanceState()
*/
protected void dispatchSaveInstanceState(SparseArray container) {
if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
Parcelable state = onSaveInstanceState();
if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
"Derived class did not call super.onSaveInstanceState()");
}
if (state != null) {
// Log.i("View", "Freezing #" + Integer.toHexString(mID)
// + ": " + state);
container.put(mID, state);
}
}
}
onSaveInstanceState是由dispatchSaveInstanceState调用的,dispatchSaveInstanceState是由saveHierarchyState调用的。onSaveInstanceState被调用的时候有两个必要的条件,只有这两个条件同时成立,onSaveInstanceState才会真正被执行,所以在实际的开发过程中并不是单纯的实现onSaveInstanceState即可,具体情况如下:
- mId即view的id如果为空要如何处理(如代码动态添加view或xml里面生明view时没有赋值id)
- (mViewFlags & SAVE_DISABLED_MASK) != 0
解决第一种情况可以代码或者xml文件里面主动给view赋值id,第二种情况默认情况下是等于0的,不主动设置save_disabled的状态即可(setSaveEnabled(false)或者android:saveEnabled="false")。
调用onRestoreInstanceState的代码如下:
/**
* Restore this view hierarchy's frozen state from the given container.
*
* @param container The SparseArray which holds previously frozen states.
*
* @see #saveHierarchyState(android.util.SparseArray)
* @see #dispatchRestoreInstanceState(android.util.SparseArray)
* @see #onRestoreInstanceState(android.os.Parcelable)
*/
public void restoreHierarchyState(SparseArray container) {
dispatchRestoreInstanceState(container);
}
/**
* Called by {@link #restoreHierarchyState(android.util.SparseArray)} to retrieve the
* state for this view and its children. May be overridden to modify how restoring
* happens to a view's children; for example, some views may want to not store state
* for their children.
*
* @param container The SparseArray which holds previously saved state.
*
* @see #dispatchSaveInstanceState(android.util.SparseArray)
* @see #restoreHierarchyState(android.util.SparseArray)
* @see #onRestoreInstanceState(android.os.Parcelable)
*/
protected void dispatchRestoreInstanceState(SparseArray container) {
if (mID != NO_ID) {
Parcelable state = container.get(mID);
if (state != null) {
// Log.i("View", "Restoreing #" + Integer.toHexString(mID)
// + ": " + state);
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
onRestoreInstanceState(state);
if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
"Derived class did not call super.onRestoreInstanceState()");
}
}
}
}
从上述的调用过程可以发现,每个view的state都是存储在SparseArray
View的saveHierarchyState和restoreHierarchyState又是谁调用的呢?我们先从View的父布局ViewGroup里面找一下源码吧。
ViewGroup是继承自View的,从源码里看ViewGroup并没有重写saveHierarchyState和restoreHierarchyState的,但是重写了dispatchSaveInstanceState和dispatchRestoreInstanceState,代码如下:
@Override
protected void dispatchSaveInstanceState(SparseArray 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);
}
}
}
@Override
protected void dispatchRestoreInstanceState(SparseArray container) {
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);
}
}
}
由此可见所有的子View的dispatch方法均是由父布局ViewGroup来分发的,并且这些View是共享同一个SparseArray的,每个View的state都是存储在这个SparseArray里面的,key值为mID(即View的id)。如果View的id重复的话,就会导致SparseArray里面的state值被覆盖,state恢复的时候就会出现问题了。解决这种问题一种方案是使每个View的id保持唯一,但做起来会很难控制,另一种方案也是这里要介绍的方案,即使state的存储颗粒化,每个ViewGroup单独管理它的子View的state。具体实现如下:
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState savedState = new SavedState(superState);
savedState.childrenStates = new SparseArray();
//保存子view的state
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).saveHierarchyState(savedState.childrenStates);
}
return savedState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
//恢复子view的state
for (int i = 0; i < getChildCount(); i++) {
getChildAt(i).restoreHierarchyState(savedState.childrenStates);
}
}
@Override
protected void dispatchSaveInstanceState(SparseArray container) {
//只保存自身的state到共有的SparseArray
dispatchFreezeSelfOnly(container);
}
@Override
protected void dispatchRestoreInstanceState(SparseArray container) {
//从共有的SparseArray拿到state并只恢复自身
dispatchThawSelfOnly(container);
}
public static class SavedState extends BaseSavedState {
//用于保存子view的state
SparseArray childrenStates;
public SavedState(Parcel source) {
super(source);
}
private SavedState(Parcel source, ClassLoader loader) {
super(source);
childrenStates = source.readSparseArray(loader);
}
public SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeSparseArray(childrenStates);
}
public static final ClassLoaderCreator CREATOR = new ClassLoaderCreator() {
@Override
public SavedState createFromParcel(Parcel source, ClassLoader loader) {
return new SavedState(source, loader);
}
@Override
public SavedState createFromParcel(Parcel source) {
return new SavedState(source);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
这里给出从Activity处开始调用onSaveInstanceState和onRestoreInstanceStatede 代码执行流程。
Activity. onSaveInstanceState
/**
* The hook for {@link ActivityThread} to save the state of this activity.
*
* Calls {@link #onSaveInstanceState(android.os.Bundle)}
* and {@link #saveManagedDialogs(android.os.Bundle)}.
*
* @param outState The bundle to save the state to.
*/
final void performSaveInstanceState(Bundle outState) {
//调用入口
onSaveInstanceState(outState);
//调用activity所管理的dialog的onSaveInstanceState逻辑,从window开始流程基本一致
saveManagedDialogs(outState);
。。。
}
protected void onSaveInstanceState(Bundle outState) {
//调用window的onSaveInstanceState
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
//保存所有fragment的状态
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
if (mAutoFillResetNeeded) {
outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
getAutofillManager().onSaveInstanceState(outState);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
Window. saveHierarchyState
Activity的mWindow是PhoneWindow的实例,所以需要看PhoneWindow的saveHierarchyState。
@Override
public Bundle saveHierarchyState() {
Bundle outState = new Bundle();
if (mContentParent == null) {
return outState;
}
SparseArray states = new SparseArray();
//mContentParent即为id为content的那层FrameLayout,setContentView的布局文件即是添加到这个ViewGroup里面的,接下来就回到了上面讲述的流程里面了
mContentParent.saveHierarchyState(states);
outState.putSparseParcelableArray(VIEWS_TAG, states);
// Save the focused view ID.
final View focusedView = mContentParent.findFocus();
if (focusedView != null && focusedView.getId() != View.NO_ID) {
outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
}
// save the panels
SparseArray panelStates = new SparseArray();
savePanelState(panelStates);
if (panelStates.size() > 0) {
outState.putSparseParcelableArray(PANELS_TAG, panelStates);
}
//保存actionbar的状态
if (mDecorContentParent != null) {
SparseArray actionBarStates = new SparseArray();
mDecorContentParent.saveToolbarHierarchyState(actionBarStates);
outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
}
return outState;
}
再来看一下onRestoreInstanceState的流程。
Activity. onRestoreInstanceState
/**
* The hook for {@link ActivityThread} to restore the state of this activity.
*
* Calls {@link #onSaveInstanceState(android.os.Bundle)} and
* {@link #restoreManagedDialogs(android.os.Bundle)}.
*
* @param savedInstanceState contains the saved state
*/
final void performRestoreInstanceState(Bundle savedInstanceState) {
//调用入口
onRestoreInstanceState(savedInstanceState);
//恢复dialog的状态
restoreManagedDialogs(savedInstanceState);
}
protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (mWindow != null) {
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
mWindow.restoreHierarchyState(windowState);
}
}
}
Window.restoreHierarchyState
@Override
public void restoreHierarchyState(Bundle savedInstanceState) {
if (mContentParent == null) {
return;
}
//恢复所有view的状态
SparseArray savedStates
= savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
if (savedStates != null) {
mContentParent.restoreHierarchyState(savedStates);
}
// restore the focused view
int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID);
if (focusedViewId != View.NO_ID) {
View needsFocus = mContentParent.findViewById(focusedViewId);
if (needsFocus != null) {
needsFocus.requestFocus();
} else {
Log.w(TAG,
"Previously focused view reported id " + focusedViewId
+ " during save, but can't be found during restore.");
}
}
// Restore the panels.
SparseArray panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG);
if (panelStates != null) {
restorePanelState(panelStates);
}
//恢复actionBar的状态
if (mDecorContentParent != null) {
SparseArray actionBarStates =
savedInstanceState.getSparseParcelableArray(ACTION_BAR_TAG);
if (actionBarStates != null) {
doPendingInvalidatePanelMenu();
mDecorContentParent.restoreToolbarHierarchyState(actionBarStates);
} else {
Log.w(TAG, "Missing saved instance states for action bar views! " +
"State will not be restored.");
}
}
}