从源码上分析Android View保存数据状态

在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())来恢复它的状态。之后我们才能恢复我们自己的状态

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值