在Android开发旅途中,经常会遇到系统控件无法满足我们的视觉,交互效果,这个时候我们常常需要自己自定义控件来满足我们的需求。在这个开发探索过程中,我们不可避免得遇到View要保存状态信息这样的问题。刚开始接触控件自定义开发的时候,我自己也搞不懂要怎样保存当前数据,如果没有对当前状态数据进行保存,那么如果一不小心旋转一下手机屏幕或者按下back,那么控件又回到初始化状态,之前所有的输入都已经不存在。比如TextView文本显示,EditText输入内容,Switch选中状态等等。当然也不要担心,安卓系统通常会自动保存这些View的状态(一般是系统控件),但是如果是我们自定义的控件,那么就不起作用了,这就需要我们自己去保存我们自己自定义的控件的状态。这些是后话,我们先来分析Android系统是怎么保存系统控件的状态的。
我们先来分析保存状态的过程:
1、saveHierarchyState(SparseArray Container)
- 当状态需要保存的时候被安卓framework调用,通常会调用dispatchSaveInstanceState() 。
/**
* 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<Parcelable> container) {
dispatchSaveInstanceState(container);
}
源码上已经注释很清楚了,saveHierarchyState(SparseArrayContainer)这个方法主要是将视图层次结构冻结状态储存到给定的容器中。接着我们继续一步步进入它的方法调用栈中看看具体的保存过程。
2、dispatchSaveInstanceState(SparseArray container)
- 被saveHierarchyState()调用。 在其内部调用onSaveInstanceState(),并且返回一个代表当前状态的Parcelable。这个Parcelable被保存在container参数中,container参数是一个键值对的map集合。View的ID是加键,Parcelable是值。如果这是一个ViewGroup,还需要遍历其子view,保存子View的状态。
/**
* 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<Parcelable> 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);
}
}
}
从上面的源码上我们可以看到dispatchSaveInstanceState(SparseArraycontainer)主要是调用onSaveInstanceState()方法返回当前状态的Parcelable,利用Map集合器,把当前View的ID当作键,把Parcelable当作值保存到container这个Map容器中。
3、Parcelable onSaveInstanceState()
- 被 dispatchSaveInstanceState()调用。这个方法应该在View的实现中被重写以返回实际的View状态。
restoreHierarchyState(SparseArray container) - 在需要恢复View状态的时候被Android调用,作为传入的SparseArray参数,包含了在保存过程中的所有view状态。
/**
* Hook allowing a view to generate a representation of its internal state
* that can later be used to create a new instance with that same state.
* This state should only contain information that is not persistent or can
* not be reconstructed later. For example, you will never store your
* current position on screen because that will be computed again when a
* new instance of the view is placed in its view hierarchy.
* <p>
* Some examples of things you may store here: the current cursor position
* in a text view (but usually not the text itself since that is stored in a
* content provider or other persistent storage), the currently selected
* item in a list view.
*
* @return Returns a Parcelable object containing the view's current dynamic
* state, or null if there is nothing interesting to save. The
* default implementation returns null.
* @see #onRestoreInstanceState(android.os.Parcelable)
* @see #saveHierarchyState(android.util.SparseArray)
* @see #dispatchSaveInstanceState(android.util.SparseArray)
* @see #setSaveEnabled(boolean)
*/
@CallSuper
protected Parcelable onSaveInstanceState() {
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
if (mStartActivityRequestWho != null) {
BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
return state;
}
return BaseSavedState.EMPTY_STATE;
}
允许一个视图来生成它的内部状态的表示的钩子,可以用来创建一个相同的状态的新实例。此状态只包含不持久的或无法重建的信息。例如,您将永远不会在屏幕上存储当前的位置,因为当视图层次结构中的一个新实例放置在视图中时,将再次计算您的当前位置。看到没有,在onSaveInstanceState()中,创建了一个BaseSavedState的对象,看到这个对象的出现我想应该知道View的数据保存跟恢复是怎么回事了吧。如果你还不是很清楚,没关系,我们继续看看BaseSavedState到底是个什么鬼。
/**
* Constructor called by derived classes when creating their SavedState objects
*
* @param superState The state of the superclass of this view
*/
public BaseSavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeString(mStartActivityRequestWhoSaved);
}
public static final Parcelable.Creator<BaseSavedState> CREATOR =
new Parcelable.Creator<BaseSavedState>() {
public BaseSavedState createFromParcel(Parcel in) {
return new BaseSavedState(in);
}
public BaseSavedState[] newArray(int size) {
return new BaseSavedState[size];
}
};
}
构造函数调用派生类创建对象时传入他们的savedstate,其实就是一个序列化数据的写入,恢复数据无非就是从这个序列里面读取出刚刚写入的数据。好了,我们再来分析数据恢复的过程。
从上面那张图中,我们不难看出数据的恢复首先会调用restoreHierarchyState(SparseArray container)这个方法,然后再调dispatchRestoreInstanceState(SparseArray container),最后调onRestoreInstanceState(Parcelable state)。所以我们接下一步步往下看。
4、restoreHierarchyState(SparseArray container)
- 在需要恢复View状态的时候被Android调用,作为传入的SparseArray参数,包含了在保存过程中的所有view状态。
/**
* 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<Parcelable> container) {
dispatchRestoreInstanceState(container);
}
即从给定容器中恢复此视图层次结构的冻结状态。跟刚刚保存数据是一个相反的过程。
5、dispatchRestoreInstanceState(SparseArray container)
- 被restoreHierarchyState()调用。根据View的ID找出相应的Parcelable,同时传递给onRestoreInstanceState()。如果这是一个ViewGroup,还要恢复其子View的数据。
/**
* 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<Parcelable> 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的ID当作键,把Parcelable当作值,保存到给定的container Map容器里面。那么现在是恢复我们之前保存的数据,那当然是要从Map容器里面把数据读取出来。即根据当前view的ID找出相应的Parcelable值,然后一次同时,把这个Parcelable值传给onRestoreInstanceState()。那么我们顺着往下看onRestoreInstanceState()到底干了啥。
6、onRestoreInstanceState(Parcelable state)
- 被dispatchRestoreInstanceState()调用。如果container中有某个view,ViewID所对应的状态被传递在这个方法中。
/**
* Hook allowing a view to re-apply a representation of its internal state that had previously
* been generated by {@link #onSaveInstanceState}. This function will never be called with a
* null state.
*
* @param state The frozen state that had previously been returned by
* {@link #onSaveInstanceState}.
*
* @see #onSaveInstanceState()
* @see #restoreHierarchyState(android.util.SparseArray)
* @see #dispatchRestoreInstanceState(android.util.SparseArray)
*/
@CallSuper
protected void onRestoreInstanceState(Parcelable state) {
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
if (state != null && !(state instanceof AbsSavedState)) {
throw new IllegalArgumentException("Wrong state class, expecting View State but "
+ "received " + state.getClass().toString() + " instead. This usually happens "
+ "when two views of different type have the same id in the same hierarchy. "
+ "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure "
+ "other views do not use the same id.");
}
if (state != null && state instanceof BaseSavedState) {
mStartActivityRequestWho = ((BaseSavedState) state).mStartActivityRequestWhoSaved;
}
}
看到没,这个过程就是从BaseSavedState里把之前写进去的带有数据属性的变量给读取出来。好了,Android系统系统控件的状态整个保存以及恢复的过程到此分析完成。接下来我们来看看如果是我们自定义的控件,我们应该如何来保存我们的状态数据。既然安卓系统控件的状态保存我们都掌握了,那么毫无悬念我们就按照安卓系统的方案走呗。这里我举例看看我的自定义控件的数据保存是怎么干的,我这里需要自定义一个轮播效果的引导页,那肯定得把当前页保存起来,不然不小心旋转屏幕或者按下back键,再进入就不是离开时候的那个页面了。来看看我是怎么保存的。
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
mCurrentPage = savedState.currentPage;
mSnapPage = savedState.currentPage;
requestLayout();
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState savedState = new SavedState(superState);
savedState.currentPage = mCurrentPage;
return savedState;
}
static class SavedState extends BaseSavedState {
int currentPage;
public SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
currentPage = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(currentPage);
}
@SuppressWarnings("UnusedDeclaration")
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
看到了吧,无非是按照Android系统控件保存的那几个步骤,先把数据保存起来,需要的时候再把它取出来。
再来看看最常见的CheckBox是怎么保存状态。
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 + "}";
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.checked = isChecked();
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setChecked(ss.checked);
requestLayout();
}
总结:在安卓中有一个类(View.BaseSavedState)专门做数据保存这件事情。
(1)通过继承它来实现保存上一级的状态同时允许你保存自定义的属性。在onRestoreInstanceState()期间我们则需要做相反的事情
(2)从指定的Parcelable中获取上一级的状态,同时让你的父类通过调用super.onRestoreInstanceState(ss.getSuperState())来恢复它的状态。之后我们才能恢复我们自己的状态