從源碼上分析Android View保存數據狀態

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())來恢復它的狀態。以後咱們才能恢復咱們本身的狀態

相關文章
相關標籤/搜索