Android中SaveState原理分析

在Activity被回收之前,系統(tǒng)會調(diào)用Activity#onSaveInstanceState(Bundle outState)來保存View的狀態(tài)到傳入的outState對象中。
在Activity被重新創(chuàng)建時,Activity#onCreate(Bundle savedInstanceState)Activity#onRestoreInstanceState(Bundle savedInstanceState)會傳入保存的狀態(tài)信息并恢復(fù)View的狀態(tài)。
用戶也可以重載Activity#onSaveInstanceState()方法來保存額外的Activity狀態(tài),并在Activity.onCreate()或者Activity#onRestoreInstanceState()獲取這些狀態(tài)。
這里主要看View的狀態(tài)是怎么保存和重新獲取的。

先說一下Bundle類,在Android代碼里的注釋是:
A mapping from String values to various Parcelable types.
可以簡單把Bundle看成一個Map<String, Object>,其中Object都實現(xiàn)了Parcelable接口。Bundle類有一系列的set和get方法用來操作Map中的值。

protected void onSaveInstanceState(Bundle outState) {
    outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
    Parcelable p = mFragments.saveAllState();
    if (p != null) {
        outState.putParcelable(FRAGMENTS_TAG, p);
    }
    getApplication().dispatchActivitySaveInstanceState(this, outState);
}

這個方法做了三件事,1.保存Activity對應(yīng)的Window的State信息,并存放在outState中,2.保存所有Fragements的信息,如果不為零,也存放在outState中,3.調(diào)用外部注冊的一些回調(diào)方法。保存Fragments的信息是通過保存Fragment的根View的狀態(tài)實現(xiàn)的,這和保存Window的State信息類似。所以看mWindow.saveHierarchyState()方法,這里mWindow是一個PhoneWindow對象,找到PhoneWindow#saveHierarchyState()方法:

public Bundle saveHierarchyState() {
    //step 1:
    Bundle outState = new Bundle();
    if (mContentParent == null) {
        return outState;
    }
    // step 2: save View states
    SparseArray<Parcelable> states = new SparseArray<Parcelable>();
    mContentParent.saveHierarchyState(states);
    outState.putSparseParcelableArray(VIEWS_TAG, states);

    // step 3: save the focused view id
    View focusedView = mContentParent.findFocus();
    if (focusedView != null) {
        if (focusedView.getId() != View.NO_ID) {
            outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
        } else {
            if (false) {
                Log.d(TAG, "couldn't save which view has focus because the focused view "
                        + focusedView + " has no id.");
            }
        }
    }

    // step 4: save the panels state
    SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();
    savePanelState(panelStates);
    if (panelStates.size() > 0) {
        outState.putSparseParcelableArray(PANELS_TAG, panelStates);
    }
    // step 5: save actionBar state
    if (mDecorContentParent != null) {
        SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
        mDecorContentParent.saveToolbarHierarchyState(actionBarStates);
        outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
    }

    return outState;
}

這個方法依次做了這些事情:
1.創(chuàng)建Bundle對象用來返回,判斷Window是否有對應(yīng)的mContentParent這個View對象,如果沒有直接返回
2.保存View的信息
3.保存當(dāng)前View焦點的信息
4.保存抽屜信息
5.保存ActionBar信息。
我們關(guān)注第二步保存View信息: mContentParent.saveHierarchyState(states);.這里states是一個SparseArray<Parcelable>對象,所有View信息都會保存在states中。SparseArray可以理解為Map<Integer,Object>,是Android系統(tǒng)為了優(yōu)化內(nèi)存創(chuàng)建的類。
看一下View#saveHierarchyState(SparseArray<Parcelable> container):

public void saveHierarchyState(SparseArray<Parcelable> container) {
    dispatchSaveInstanceState(container);
}

直接調(diào)用了View#dispatchSaveInstanceState(SparseArray<Parcelable> container):

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);
        }
    }
}

檢查一下View是否設(shè)置了ID,如果沒有id直接返回。這里我們可以看到,如果View沒有設(shè)置id,是不會保存它的狀態(tài)的。設(shè)置了ID之后,獲取狀態(tài),并將狀態(tài)以ID為key存儲在SparseArray中。這里可以看出,如果View樹中兩個View的id相同,那么后一個View的SavedState會覆蓋前一個SavedState。當(dāng)然這種情況下findViewById()也會出問題。
保存View狀態(tài)是在onSaveInstanceState()中實現(xiàn)的:

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;
}

這個方法檢查了一下mStartActivityRequestWho這個String對象是否為null,mStartActivityRequestWho這個對象只有在調(diào)用了View#startActivityForResult時才會設(shè)置,這時標(biāo)記一下state中的狀態(tài),為null時,直接返回一個空狀態(tài)。
在View類中保存狀態(tài)里用了三個方法,其中saveHierachyState()方法直接傳遞給了dispatchSaveInstanceState()方法。dispatchSaveInstanceState()用來分發(fā)保存狀態(tài)的行為,這個方法的默認(rèn)行為是在有ID的情況下保存自身的state,沒有id的情況下什么都不做。所以ViewGroup可以選擇重寫這個方法,將保存狀態(tài)的行為分發(fā)到子類中。onSaveInstanceState()方法用來具體地保存當(dāng)前View的狀態(tài),自定義View可以選擇重寫這個方法。

看一下ViewGroup#dispatchSaveInstanceState()方法

protected void dispatchSaveInstanceState(SparseArray<Parcelable> 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);
        }
    }
}

首先調(diào)用View#dispatchSaveInstanceState()保存自身的狀態(tài),然后遍歷子View,調(diào)用對應(yīng)的dispatchSaveInstanceState()方法,這里實現(xiàn)的View樹的遍歷。
看一下TextView#onSaveInstanceState()是怎么保存狀態(tài)的:

public Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();

    // Save state if we are forced to
    boolean save = mFreezesText;
    int start = 0;
    int end = 0;

    if (mText != null) {
        start = getSelectionStart();
        end = getSelectionEnd();
        if (start >= 0 || end >= 0) {
            // Or save state if there is a selection
            save = true;
        }
    }

    if (save) {
        SavedState ss = new SavedState(superState);
        // XXX Should also save the current scroll position!
        ss.selStart = start;
        ss.selEnd = end;

        if (mText instanceof Spanned) {
            Spannable sp = new SpannableStringBuilder(mText);

            if (mEditor != null) {
                removeMisspelledSpans(sp);
                sp.removeSpan(mEditor.mSuggestionRangeSpan);
            }

            ss.text = sp;
        } else {
            ss.text = mText.toString();
        }

        if (isFocused() && start >= 0 && end >= 0) {
            ss.frozenWithFocus = true;
        }

        ss.error = getError();

        if (mEditor != null) {
            ss.editorState = mEditor.saveInstanceState();
        }
        return ss;
    }

    return superState;
}

首先判斷TextView是否需要保存狀態(tài),如果mFreezesText設(shè)置為true,或者TextView處于被選中的狀態(tài),那么需要保存狀態(tài)。不許要保存保存狀態(tài)的話直接返回super.onSaveInstanceState().
TextView中有個內(nèi)部類SavedState用來表示狀態(tài),將對應(yīng)的狀態(tài)設(shè)置好之后直接返回即可。

至此,整個保存狀態(tài)的過程已經(jīng)走完,總結(jié)一下:
1.在Activity被回收時,會觸發(fā)一個SaveState的事件。
2.跟其他的事件一樣,SaveState事件從Activity->Window->View傳遞到最大的View,然后遍歷View樹保存狀態(tài)
3.狀態(tài)保存在一個SparseArray中,以View的ID作為key。
4.自定義View可以重載onSaveInstanceState()來保存自己的狀態(tài),參考TextView的實現(xiàn)方法。

Restore的過程:Activity#onRestoreInstanceState()方法:

protected void onRestoreInstanceState(Bundle savedInstanceState) {
    if (mWindow != null) {
        Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
        if (windowState != null) {
            mWindow.restoreHierarchyState(windowState);
        }
    }
}

跟保存的過程一樣,傳遞到Window#restoreHierarchyState()方法中:

public void restoreHierarchyState(Bundle savedInstanceState) {
    // step 1.
    if (mContentParent == null) {
        return;
    }
    // step 2.
    SparseArray<Parcelable> savedStates
            = savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
    if (savedStates != null) {
        mContentParent.restoreHierarchyState(savedStates);
    }

    // step 3. 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.");
        }
    }

    // step 4. restore the panels
    SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG);
    if (panelStates != null) {
        restorePanelState(panelStates);
    }
    // step 5.
    if (mDecorContentParent != null) {
        SparseArray<Parcelable> 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.");
        }
    }
}

跟保存的過程完全一致,分五步。我們只看第三步:mContentParent.restoreHierarchyState(savedStates),這里調(diào)用View#restoreHierarchyState(savedStates)方法:

public void restoreHierarchyState(SparseArray<Parcelable> container) {
    dispatchRestoreInstanceState(container);
}

一樣直接傳遞到View#dispatchRestoreInstanceState()方法:

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的這個方法,從SparseArray中用mID獲取到state,然后調(diào)用View#onRestoreInstanceState()方法。看一下ViewGroup對應(yīng)的方法:

protected void dispatchRestoreInstanceState(SparseArray<Parcelable> 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);
        }
    }
}

先調(diào)用View版本的dispatchRestoreInstanceState()方法,然后遍歷childView,調(diào)用對應(yīng)的dispatchRestoreInstanceState()方法。
最后看一下View#onRestoreInstanceState()方法:

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 ");
    }
    if (state != null && state instanceof BaseSavedState) {
        mStartActivityRequestWho = ((BaseSavedState) state).mStartActivityRequestWhoSaved;
    }
}

這個方法很簡單,直接獲取mStartActivityRequestWho對象,對于自定義View,需要重寫這個方法,獲取自己的狀態(tài)。同樣看一下TextView#onRestoreInstanceState()方法:

public void onRestoreInstanceState(Parcelable state) {
    if (!(state instanceof SavedState)) {
        super.onRestoreInstanceState(state);
        return;
    }

    SavedState ss = (SavedState)state;
    super.onRestoreInstanceState(ss.getSuperState());

    // XXX restore buffer type too, as well as lots of other stuff
    if (ss.text != null) {
        setText(ss.text);
    }

    if (ss.selStart >= 0 && ss.selEnd >= 0) {
        if (mText instanceof Spannable) {
            int len = mText.length();

            if (ss.selStart > len || ss.selEnd > len) {
                String restored = "";

                if (ss.text != null) {
                    restored = "(restored) ";
                }

                Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
                      "/" + ss.selEnd + " out of range for " + restored +
                      "text " + mText);
            } else {
                Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);

                if (ss.frozenWithFocus) {
                    createEditorIfNeeded();
                    mEditor.mFrozenWithFocus = true;
                }
            }
        }
    }

    if (ss.error != null) {
        final CharSequence error = ss.error;
        // Display the error later, after the first layout pass
        post(new Runnable() {
            public void run() {
                if (mEditor == null || !mEditor.mErrorWasChanged) {
                    setError(error);
                }
            }
        });
    }

    if (ss.editorState != null) {
        createEditorIfNeeded();
        mEditor.restoreInstanceState(ss.editorState);
    }
}

首先判斷state是否為TextView保存的狀態(tài),如果不是,直接調(diào)用super,然后獲取對應(yīng)的狀態(tài)設(shè)置給TextView。

可以看出Restore過程和Save過程完全相同。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容