Android狀態保存與恢復流程 完全解析

dwzc4096 7年前發布 | 8K 次閱讀 安卓開發 Android開發 移動開發

前言

很久沒寫過文章了,最近一段時間忙著各種各樣的事情,難得寒假有時間便把最近所學的整理及記錄下來與大家分享。本篇文章是關于Android的狀態保存與恢復的源碼分析。

對于一個Activity或者View來說,狀態的保存與恢復是必不可少的,最常見的一種情況是切換屏幕方向了,如果由豎屏切換為橫屏,那么必定會經歷Activity的摧毀與重建,那么它所對應的View視圖也會被摧毀和重建,如果此時沒有對View進行狀態的保存的話,那么待View重建后,其之前的狀態便不復存在。慶幸的是,對于系統自帶的View(比如CheckBox等),Android已經幫我們實現了狀態的自動保存與恢復,不必我們費心了。但是對于我們自己開發的自定義View,就需要我們去保存狀態和恢復狀態了,系統提供了兩個API方便我們去實現保存和恢復,分別是 onSaveInstanceStateonRestoreInstanceState 這兩個方法,我們只需要重寫這兩個方法,然后在里面寫需要的邏輯即可。那么接下來就分析整個保存和恢復的流程是怎樣的。

保存狀態流程分析

從Activity的onSaveInstanceState說起

Activity有這樣一個方法: onSaveInstanceState (Bundle outState) ,相信大家都不陌生,我們通過重寫這個方法來保存我們需要的數據。先來看看官方文檔對這個方法的注釋:

Called to retrieve per-instance state from an activity before being killed so that the state can be restored in onCreate(Bundle) or onRestoreInstanceState(Bundle) (the Bundle populated by this method will be passed to both).

This method is called before an activity may be killed so that when it comes back some time in the future it can restore its state

可以知道,當一個Activity變得容易被kill的時候會調用這個方法,比如說當旋轉屏幕的時候,當前Activity在onDestroy之前會調用onSaveInstanceState來保存狀態,又或者說當前Activity被置于后臺了,系統內存緊張的時候會有可能殺死這個Activity,那么此時Activity也會保存狀態。

那么我們來看看這個方法內部做了些什么, Activity#onSaveInstanceState :

protected void onSaveInstanceState(Bundle outState) {
    //1、保存View樹狀態,key是WINDOW_HIERARCHY_TAG
    outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
    //2、保存Fragment狀態,key是FRAGMENTS_TAG
    Parcelable p = mFragments.saveAllState();
    if (p != null) {
        outState.putParcelable(FRAGMENTS_TAG, p);
    }
    getApplication().dispatchActivitySaveInstanceState(this, outState);
}

顯然,數據是存放在一個Bundle里面的。首先,調用了mWindow的saveHierarchyState()方法,遍歷Activity的View樹來保存數據,接著調用mFragments.saveAllState()對與Activity所關聯的Fragment保存數據。下面,我們先來看看怎樣保存View樹的數據的:

1、保存View樹數據

從上面可以看出,調用了Window的saveHierarchyState方法,而Window是一個抽象類,它的實現類是PhoneWindow,那么我們來看 PhoneWindow#saveHierarchyState :

@Override
public Bundle saveHierarchyState() {
    Bundle outState = new Bundle();
    if (mContentParent == null) {
        return outState;
    }
    SparseArray<Parcelable> states = new SparseArray<Parcelable>();
    mContentParent.saveHierarchyState(states);
    outState.putSparseParcelableArray(VIEWS_TAG, states);
    //省略...

return outState;

}</code></pre>

這里先是實例化了一個SparseArray,它和HashMap類似,保存著鍵值對,接著調用了mContentParent的saveHierarchyState()方法,并把結果放進outState中并返回。這里的mContentParent是DecorView的子元素或者其自身,我之前的文章也有所提及,這里可以把mContentParent看做整個View樹的頂層視圖,由于mContentParent是一個ViewGroup,但是ViewGroup沒有重寫saveHierarchyState方法,那么這里調用的便是 View#saveHierarchyState :

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

從名字可以猜測,接下來就是分發保存事件了,遍歷所有子View,讓所有的子View去保存數據,我們來看看 ViewGroup#dispatchSaveInstanceState 是否如我們所想的那樣:

@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    //調用View的dispatchSaveInstanceState方法,目的是保存ViewGroup自身的狀態
    super.dispatchSaveInstanceState(container);
    final int count = mChildrenCount;
    final View[] children = mChildren;
    //遍歷所有的子View,保存狀態,所有狀態都放在SparseArray內
    for (int i = 0; i < count; i++) {
        View c = children[i];
        if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
            c.dispatchSaveInstanceState(container);
        }
    }
}

從上面源碼可以看出,ViewGroup除了把保存事件分發給子View,還會讓自身先保存好狀態,接著我們看 View#dispatchSaveInstanceState 源碼:

protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
        mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
        Parcelable state = onSaveInstanceState();

    if (state != null) {
        container.put(mID, state);
    }
}

}</code></pre>

這里首先判斷當前View是不是有一個ID以及當前View的標志位的狀態等,接著下面便調用到了 View#onSaveInstanceState() 方法,也即我們的自定義View需要重寫的方法,這個方法返回Parcelable對象,即可序列化對象,最后把該Parcelable對象放進了SparseArray內,key是該View的id。

由此可知,如果一個View需要保存狀態,那么至少需要以下兩個條件:

①該View有著唯一的一個id。可通過setId或xml設置id,如果id不唯一,由于SparseArray是以id為key保存狀態的,那么相同的id的View的數據會覆蓋。

②該View的標志位不是SAVE_DISABLED_MASK。可通過setSaveEnabled(boolean)方法來改變,默認是true,即可保存狀態。

那么,到此Activity的View樹保存狀態流程分析完畢,如果Activity關聯著Fragment,那么也會對Fragment進行狀態保存,即上面提到的2號代碼。

2、保存Fragment數據

上面2號代碼調用了mFragment的saveAllState()方法,而這里的mFragment是一個FragmentController,最后調用到了 FragmentManagerImpl#saveAllState

Parcelable saveAllState() {

// First collect all active fragments.
int N = mActive.size();
FragmentState[] active = new FragmentState[N];
boolean haveFragments = false;
for (int i=0; i<N; i++) {
    Fragment f = mActive.get(i);
    if (f != null) {

        haveFragments = true;

        FragmentState fs = new FragmentState(f);
        active[i] = fs;

        if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
            fs.mSavedFragmentState = saveFragmentBasicState(f);  // 3
        //省略..
        }        
    }
}

//省略...

FragmentManagerState fms = new FragmentManagerState();
fms.mActive = active;
fms.mAdded = added;
fms.mBackStack = backStack;
return fms;

}</code></pre>

以上是精簡后的源碼,首先對該Activity內的活躍的Fragment進行遍歷(所謂活躍的Fragment,筆者這里理解為未被摧毀的,還有著視圖結構的Fragment,如有錯誤請指正),接著對每一個活躍的Fragment都新建一個FragmentState,顧名思義,這個FragmentState對應著Fragment的狀態,我們來簡單看看這個 FragmentState :

final class FragmentState implements Parcelable {
    final String mClassName;
    final int mIndex;
    //若干個屬性...

Bundle mSavedFragmentState;

Fragment mInstance;

public FragmentState(Fragment frag) {
    mClassName = frag.getClass().getName();
    mIndex = frag.mIndex;
    //若干個屬性...
}

}</code></pre>

在新建一個FragmentState的時候,就已經把Fragment的幾個重要屬性賦值給FragmentState的屬性了,而FragmentState還有一個屬性,那就是mSavedFragmentState,這個屬性是在上面的③號代碼處得到賦值的,這里調用了 saveFragmentBasicState(Fragment) 方法,我們來看看這個方法:

Bundle saveFragmentBasicState(Fragment f) {
    Bundle result = null;

if (mStateBundle == null) {
    mStateBundle = new Bundle();
}
//這里會調用到Fragment的onSaveInstanceState方法
f.performSaveInstanceState(mStateBundle);
if (!mStateBundle.isEmpty()) {
    result = mStateBundle;
    mStateBundle = null;
}

if (f.mView != null) {
    //保存Fragment的視圖結構數據
    saveFragmentViewState(f);
}
if (f.mSavedViewState != null) {
    if (result == null) {
        result = new Bundle();
    }
    //將上面保存的視圖結構數據放進Bundle內
    result.putSparseParcelableArray(
            FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState);
}

return result;

}</code></pre>

上面調用了Fragment#performSaveInstanceState方法,里面進而調用了 Fragment#onSaveInstanceState 方法,這個是個空實現,我們可以在我們的Fragment重寫該方法,保存我們需要的數據,此時數據都會保存在Bundle內。接著,調用了 saveFragmentViewState方法 ,我們來看看這個方法:

void saveFragmentViewState(Fragment f) {
    if (f.mView == null) {
        return;
    }
    if (mStateArray == null) {
        mStateArray = new SparseArray<Parcelable>();
    } else {
        mStateArray.clear();
    }
    f.mView.saveHierarchyState(mStateArray);
    if (mStateArray.size() > 0) {
        f.mSavedViewState = mStateArray;
        mStateArray = null;
    }
}

可以看出,里面調用了f.mView.saveHierarchyState(mStateArray),那么這就跟上面所說的View樹保存的套路一樣了。可以看出,當調用了saveFragmentBasicState(Fragment)這個方法時,其返回的 Bundle對象 已經攜帶了兩種數據:其一,我們重寫的onSaveInstanceState(Bundle)方法所保存的數據;其二,Fragment的View樹的視圖數據。而這個Bundle對象則是存放在FragmenState內的mSavedFragmentState屬性中。 也即是說,在saveAllState()方法內,對于每一個Fragment所生成的FragmentState,不但保存著Fragemnt的基礎數據,也保存著Bundle對象。 所以說,FragmentState有著舉足輕重的作用。

至此,保存狀態的源碼分析完畢。各位看官可以站起來放松活動一下,接著就到我們的恢復狀態的流程分析了。

恢復狀態流程分析

從Activity的onRestoreInstanceState說起

通過保存狀態的流程分析,我們知道所有的狀態都保存在了一個Bundle對象內,那么要恢復狀態就必須要拿到這個Bundle對象才行,在Activity的生命周期內,onCreate(Bundle)攜帶了一個Bundle參數,這正是我們之前保存狀態的Bundle,因此我們可以在onCreate(Bundle)方法內進行恢復數據的操作。此外,還有一個方法,與onSaveInstanceState(Bundle)相對的,onRestoreInstanceState(Bundle)方法用于恢復狀態。我們看看官方文檔對 onRestoreInstanceState(Bundle) 方法的解釋:

This method is called after onStart() when the activity is being re-initialized from a previously saved state, given here in savedInstanceState. Most implementations will simply use onCreate(Bundle) to restore their state, but it is sometimes convenient to do it here after all of the initialization has been done...

從官方文檔的注釋,我們知道,onRestoreInstanceState(Bundle)會在onStart()方法后面執行,用于重建視圖的時候恢復數據,其實調用onCreate(Bundle)方法的時候已經進行了一部分的恢復,此時主要是恢復Fragment的狀態,由于onCreate方法內部一般是用于控件的初始化,如果此時就進行View樹的數據恢復,就會出錯(未初始化完畢)。

1、恢復View樹狀態

我們來看看 Activity#onRestoreInstanceState 方法:

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

先是根據WINDOW_HIERARCHY_TAG這個key獲取Bundle對應的數據,即View樹數據,接著調用mWindow.restoreHierarchyState方法,我們繼續看 PhoneWindow#restoreHierarchyState

@Override
public void restoreHierarchyState(Bundle savedInstanceState) {

SparseArray<Parcelable> savedStates
        = savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
if (savedStates != null) {
    mContentParent.restoreHierarchyState(savedStates);
}
//省略...

}</code></pre>

從Bundle里面根據VIEWS_TAG來獲取SparseArray,這個之前我們也說過了,這就是View樹數據所對應的SparseArray,接著調用mContentParent.restoreHierarchyState,到這里我們也知道接下來應該是調用 View#restoreHierarchyState 方法,而就如保存狀態一樣,恢復狀態也需要把事件分發給ViewGroup的所有子View,所以在restoreHierarchyState方法里面又調用到了 ViewGroup#dispatchRestoreInstanceState :

@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
    //調用View的dispatchRestoreInstanceState,目的是恢復ViewGroup自身的狀態
    super.dispatchRestoreInstanceState(container);
    final int count = mChildrenCount;
    final View[] children = mChildren;
    //遍歷所有子View,逐個恢復它們的狀態
    for (int i = 0; i < count; i++) {
        View c = children[i];
        if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
            c.dispatchRestoreInstanceState(container);
        }
    }
}

可以看出,代碼和dispatchOnSaveInstanceState方法基本類似,接著我們看 View#dispatchRestoreInstanceState :

protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
    if (mID != NO_ID) {
        Parcelable state = container.get(mID);
        if (state != null) {
            mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
            onRestoreInstanceState(state);
        }
    }
}

這里根據View的Id在SparseArray中獲得對應的Parcelable對象,即視圖數據,接著調用了View#onRestoreInstanceState(Parcelable)方法,交給每一個View來自行恢復數據,一般對于自定義View來說,我們會重寫onRestoreInstanceState(Parcelable)方法,來處理我們需要恢復的數據。至此,View樹的數據恢復解析完畢,我們接著看Fragment的數據恢復。

2、恢復Fragment狀態

讓我們回到 Activity#onCreate(Bundle)方法 ,看看源碼:

protected void onCreate(@Nullable Bundle savedInstanceState) {
    //省略部分源碼..

if (savedInstanceState != null) {
    Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
    mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.fragments : null);
}
mFragments.dispatchCreate();
//...

}</code></pre>

這里先根據FRAGMENTS_TAG這個key獲取對應的Parcelable對象,接著調用mFragments.restoreAllState方法,即 FragmentManagerImpl#restoreAllState 方法:

void restoreAllState(Parcelable state, List<Fragment> nonConfig) {
    // If there is no saved state at all, then there can not be
    // any nonConfig fragments either, so that is that.
    if (state == null) return;
    FragmentManagerState fms = (FragmentManagerState)state;
    if (fms.mActive == null) return;

// Build the full list of active fragments, instantiating them from
// their saved state.
mActive = new ArrayList<Fragment>(fms.mActive.length);
if (mAvailIndices != null) {
    mAvailIndices.clear();
}
for (int i=0; i<fms.mActive.length; i++) {
    FragmentState fs = fms.mActive[i];
    if (fs != null) {
        Fragment f = fs.instantiate(mHost, mParent);  // 1
        if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
        mActive.add(f);
        // Now that the fragment is instantiated (or came from being
        // retained above), clear mInstance in case we end up re-restoring
        // from this FragmentState again.
        fs.mInstance = null;
    } 
    //...
    }
}

//省略部分代碼...
//...

}</code></pre>

關注上面的①號代碼,這里調用了FragmentState的instantiate方法,上面說有FragmentState保存著一個Fragment的狀態,那恢復狀態也應該是由FragmentState來恢復,我們看 FragmentState#instantiate :

public Fragment instantiate(FragmentHostCallback host, Fragment parent) {
    //...
    mInstance = Fragment.instantiate(context, mClassName, mArguments);

if (mSavedFragmentState != null) {
    mSavedFragmentState.setClassLoader(context.getClassLoader());
    mInstance.mSavedFragmentState = mSavedFragmentState;
}
mInstance.setIndex(mIndex, parent);
mInstance.mFromLayout = mFromLayout;
mInstance.mRestored = true;
mInstance.mFragmentId = mFragmentId;
mInstance.mContainerId = mContainerId;
mInstance.mTag = mTag;
mInstance.mRetainInstance = mRetainInstance;
mInstance.mDetached = mDetached;
mInstance.mFragmentManager = host.mFragmentManager;
if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
        "Instantiated fragment " + mInstance);

return mInstance;

}</code></pre>

這里先新建了Fragment,接著把FragmentState保存的狀態賦值給Fragment,即新的Fragment“獲得”了之前的Fragment的狀態。但是這里的Fragment只是恢復的部分的基礎數據,另外的一些數據并不是在這里恢復的,這里只是創建了一個新的Fragment,是空的,View視圖還沒有創建,所以別的數據是在Fragment進入生命周期后再恢復的。讓我們先回到 Activity#onCreate 方法,在調用mFragments.restoreAllState方法完畢后,接著調用了mFragments.dispatchCreate()方法,這就是使Fragment開始了它的生命周期,我們看 FragmentManagerImpl#dispatchCreate :

public void dispatchCreate() {
     mStateSaved = false;
     moveToState(Fragment.CREATED, false);
}

這里調用了moveToState()方法,這個方法是貫穿了Fragment生命周期的所有方法,我們來看看moveToState()方法中有關狀態恢復部分的源碼:

void moveToState(Fragment f, int newState, int transit, int transitionStyle,
            boolean keepActive) {
    //省略...
    //...

if (f.mState < newState) {
    switch (f.mState) {
        case Fragment.INITIALIZING:
            //省略...

        case Fragment.CREATED:
            if (newState > Fragment.CREATED) {
                //省略...
                f.performActivityCreated(f.mSavedFragmentState);
                if (f.mView != null) {
                    f.restoreViewState(f.mSavedFragmentState);
                }
                f.mSavedFragmentState = null;
            }
        case Fragment.ACTIVITY_CREATED:
        case Fragment.STOPPED:
            //...
        case Fragment.STARTED:

    }   
}     
f.mState = newState;

}</code></pre>

看代碼中間,調用了performActivityCreated()方法,這里對應著Fragment生命周期中的onActivityCreated()方法,隨后調用了restoreViewState()方法,想必這里便是狀態恢復的部分了,同時我們得到一個信息,Fragment的onRestoreViewState()方法是在onActivityCreated()方法之后調用的,當然,由于onActivityCreated也攜帶Bundle對象,我們也可以在這個方法內恢復狀態。我們來看看 Fragment#restoreViewState :

final void restoreViewState(Bundle savedInstanceState) {
    if (mSavedViewState != null) {
        mView.restoreHierarchyState(mSavedViewState);
        mSavedViewState = null;
    }
    mCalled = false;
    onViewStateRestored(savedInstanceState);
    //...
}

看到這里,大家也應該很熟悉了,跟Activity的恢復狀態很類似,先是調用mView.restoreHierarchyState,進行Fragment的View樹狀態恢復,接著調用onViewStateRestored(Bundle)方法,恢復我們需要的數據,這個方法一般是由我們來重寫。至此,恢復狀態流程分析完畢。

總結

上面詳細地分析了狀態保存與恢復的流程,最后我們總結一下整個保存-恢復流程。首先,在Activity中調用onSaveInstanceState()方法,保存了Activity的View樹狀態、與Activity關聯的Fragment的狀態以及我們自己重寫的onSaveInstanceState()方法所保存的數據。而Fragment的狀態又包括了:Fragment的View樹狀態、Fragment的基礎屬性以及我們自己重寫的onSaveInstanceState()方法所保存的數據。所保存的所有狀態數據均放在Bundle內,這個Bundle可以在Activity的onCreate(Bundle)方法或者onRestoreInstanceState(Bundle)方法內獲取。在Activity#onCreate方法中,會對Fragment進行狀態恢復,接著Fragment就會進入正常的生命周期,進而對自身的View樹進行狀態恢復,或者調用我們重寫的onRestoreViewState方法來恢復我們需要的數據。而在Activity#onRestoreInstanceState方法內則是對Activity的View樹進行狀態恢復以及恢復我們需要的數據。好了,到此為止本文以及對Android的狀態保存-恢復進行了完整的梳理解析,最后感謝你的閱讀~如有錯誤,歡迎指正。

參考文章:

fragment的狀態保存&恢復過程分析

從源碼角度分析,為什么會發生Fragment重疊?

Android View狀態保存

 

來自:http://www.jianshu.com/p/58579627f70a

 

 本文由用戶 dwzc4096 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!