在Android开发旅途中,常常会遇到系统控件没法知足咱们的视觉,交互效果,这个时候咱们经常须要本身自定义控件来知足咱们的需求。在这个开发探索过程当中,咱们不可避省得遇到View要保存状态信息这样的问题。刚开始接触控件自定义开发的时候,我本身也搞不懂要怎样保存当前数据,若是没有对当前状态数据进行保存,那么若是一不当心旋转一下手机屏幕或者按下back,那么控件又回到初始化状态,以前全部的输入都已经不存在。好比TextView文本显示,EditText输入内容,Switch选中状态等等。固然也不要担忧,安卓系统一般会自动保存这些View的状态(通常是系统控件),可是若是是咱们自定义的控件,那么就不起做用了,这就须要咱们本身去保存咱们本身自定义的控件的状态。这些是后话,咱们先来分析Android系统是怎么保存系统控件的状态的。html

咱们先来分析保存状态的过程:
一、saveHierarchyState(SparseArray Container)android
当状态须要保存的时候被安卓framework调用,一般会调用dispatchSaveInstanceState() 。app
?ide
1函数 2this 3spa 4.net 53d 6rest 7 8 9 10 11 12 |
/** * 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); }</parcelable> |
源码上已经注释很清楚了,saveHierarchyState(SparseArrayContainer)这个方法主要是将视图层次结构冻结状态储存到给定的容器中。接着咱们继续一步步进入它的方法调用栈中看看具体的保存过程。
二、dispatchSaveInstanceState(SparseArray container)
被saveHierarchyState()调用。 在其内部调用onSaveInstanceState(),而且返回一个表明当前状态的Parcelable。这个Parcelable被保存在container参数中,container参数是一个键值对的map集合。View的ID是加键,Parcelable是值。若是这是一个ViewGroup,还须要遍历其子view,保存子View的状态。
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/** * 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); } } }</parcelable> |
从上面的源码上咱们能够看到dispatchSaveInstanceState(SparseArraycontainer)主要是调用onSaveInstanceState()方法返回当前状态的Parcelable,利用Map集合器,把当前View的ID看成键,把Parcelable看成值保存到container这个Map容器中。
三、Parcelable onSaveInstanceState()
被 dispatchSaveInstanceState()调用。这个方法应该在View的实现中被重写以返回实际的View状态。
restoreHierarchyState(SparseArray container)
在须要恢复View状态的时候被Android调用,做为传入的SparseArray参数,包含了在保存过程当中的全部view状态。
?
1 2 3 4 5 6 7 8 |
/** * 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. * |
* 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究竟是个什么鬼。
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/** * 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]; } }; }</basesavedstate></basesavedstate> |
构造函数调用派生类建立对象时传入他们的savedstate,其实就是一个序列化数据的写入,恢复数据无非就是从这个序列里面读取出刚刚写入的数据。好了,咱们再来分析数据恢复的过程。
从上面那张图中,咱们不难看出数据的恢复首先会调用restoreHierarchyState(SparseArray container)这个方法,而后再调dispatchRestoreInstanceState(SparseArray container),最后调onRestoreInstanceState(Parcelable state)。因此咱们接下一步步往下看。
四、restoreHierarchyState(SparseArray container)
在须要恢复View状态的时候被Android调用,做为传入的SparseArray参数,包含了在保存过程当中的全部view状态。
?
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * 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); }</parcelable> |
即从给定容器中恢复此视图层次结构的冻结状态。跟刚刚保存数据是一个相反的过程。
五、dispatchRestoreInstanceState(SparseArray container)
被restoreHierarchyState()调用。根据View的ID找出相应的Parcelable,同时传递给onRestoreInstanceState()。若是这是一个ViewGroup,还要恢复其子View的数据。
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/** * 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()" ); } } } }</parcelable> |
还记得保存数据的时候安卓是怎么干的吗?嘿嘿,把当前view的ID看成键,把Parcelable看成值,保存到给定的container Map容器里面。那么如今是恢复咱们以前保存的数据,那固然是要从Map容器里面把数据读取出来。即根据当前view的ID找出相应的Parcelable值,而后一次同时,把这个Parcelable值传给onRestoreInstanceState()。那么咱们顺着往下看onRestoreInstanceState()到底干了啥。
六、onRestoreInstanceState(Parcelable state)
被dispatchRestoreInstanceState()调用。若是container中有某个view,ViewID所对应的状态被传递在这个方法中。
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
/** * 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键,再进入就不是离开时候的那个页面了。来看看我是怎么保存的。
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
@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]; } }; }</savedstate></savedstate> |
看到了吧,无非是按照Android系统控件保存的那几个步骤,先把数据保存起来,须要的时候再把它取出来。
再来看看最多见的CheckBox是怎么保存状态。
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
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(); }</savedstate></savedstate> |
总结:在安卓中有一个类(View.BaseSavedState)专门作数据保存这件事情。 (1)经过继承它来实现保存上一级的状态同时容许你保存自定义的属性。在onRestoreInstanceState()期间咱们则须要作相反的事情 (2)从指定的Parcelable中获取上一级的状态,同时让你的父类经过调用super.onRestoreInstanceState(ss.getSuperState())来恢复它的状态。以后咱们才能恢复咱们本身的状态