安卓重溫基礎(chǔ)--四大組件之Activity的狀態(tài)保存與還原

Activity 的狀態(tài)保存與還原


Activity 的狀態(tài)保存

一般來說,當(dāng) Activity 暫?;蛲V梗╫nPause() 或 onStop() Activity 不再處于前臺(tái))時(shí),Activity 的狀態(tài)會(huì)得到保留。因?yàn)楫?dāng)Activity 暫停或停止時(shí),對(duì)象仍保留在內(nèi)存中 — 有關(guān)其成員和當(dāng)前狀態(tài)的所有信息仍處于活動(dòng)狀態(tài)。 因此,用戶在 Activity 內(nèi)所做的任何更改都會(huì)得到保留,這樣一來,當(dāng) Activity 返回前臺(tái)(當(dāng)它“繼續(xù)”)時(shí),這些更改仍然存在。但是,當(dāng)系統(tǒng)內(nèi)存不足,為了恢復(fù)內(nèi)存而銷毀某個(gè) Activity 時(shí),Activity 的對(duì)象也會(huì)被銷毀。因此系統(tǒng)在繼續(xù) Activity 時(shí)根本無法讓其狀態(tài)保持完好,而是必須在用戶返回 Activity 時(shí)重建 Activity 對(duì)象(重新 onCreate() )。但用戶并不知道系統(tǒng)銷毀 Activity 后又對(duì)其進(jìn)行了重建,因此他們很可能認(rèn)為 Activity 狀態(tài)毫無變化,這時(shí)可能會(huì)把用戶希望保存的信息也一起銷毀掉,導(dǎo)致不好的用戶體驗(yàn)。為了處理這種情況,Android 為我們提供了一個(gè) onSaveInstanceState() 的方法,我們可以回調(diào)這個(gè)方法來保存一些用戶希望保存的信息。在 Activity 變得易于銷毀,所謂的易于銷毀,在Android 的官方文檔中提到,onSaveInstanceState() 這個(gè)方法是在 onPause() 之前或者之后但肯定是在 onStop() 之前執(zhí)行的,就是還未銷毀之前,系統(tǒng)會(huì)先回調(diào)onSaveInstanceState() 。

使用情景
(1)當(dāng)用戶按下HOME鍵時(shí)。
(2)選擇運(yùn)行其他的程序時(shí)。
(3)按下電源按鍵(關(guān)閉屏幕顯示)時(shí)。
(4)從 Activity A 中啟動(dòng)另一個(gè) Activity 時(shí)。
(5)屏幕方向切換時(shí),例如從豎屏切換到橫屏?xí)r。

來看看 onSaveInstanceState() 的使用

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        /**
         * 可以在次使用 Bundle 的一系列方法保存信息
         * 比如:
         *     outState.putInt();
         */
    }

從上面的代碼可以發(fā)現(xiàn)這里傳入的是一個(gè) Bundle 對(duì)象,可以在里面使用putInt()、putString()等一系列方法保存信息。

再來看下 onSaveInstanceState() 的源碼

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

由上面代碼可知,outState 通過 put 一個(gè) TAG 給mWindow.saveHierarchyState()。

繼續(xù)跟蹤,由于 Window 是抽象類,查看其子類PhoneWindow的saveHierarchyState()方法。

     /** {@inheritDoc} */
    @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);
        // 代碼到此即可知道 Bundle 是怎么保存的
        /**
        * 此處省略一系列代碼
        */
        return outState;
    }

在上面代碼中:

  1. Bundle outState = new Bundle(); 初始化了一個(gè) Bundle 對(duì)象。
  2. mContentParent 是一個(gè) ViewGroup 對(duì)象的實(shí)例,當(dāng)它為空時(shí),就直接返回一個(gè)空的 Bundle。
  3. SparseArray<Parcelable> states = new SparseArray<Parcelable>(); states最終存到outState中。(Bundle 對(duì)象實(shí)現(xiàn)了 Parcelable 接口)
  4. 繼續(xù)跟蹤 mContentParent.saveHierarchyState(states);
    public void saveHierarchyState(SparseArray<Parcelable> container) {
        dispatchSaveInstanceState(container);
    }

上面代碼是 View.java 里面的,看不出什么,繼續(xù)跟蹤,看看它的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 ((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);
            }
        }
    }

從上面的代碼:
首先要搞清楚里面的一些常量是什么
1)NO_ID

    /**
     * Used to mark a View that has no ID.
     */
    public static final int NO_ID = -1;
由注釋可知它是用來標(biāo)志一個(gè)沒有 id 的 View 。

2)mID

    @IdRes
    @ViewDebug.ExportedProperty(resolveId = true)
    int mID = NO_ID;

    public void setId(@IdRes int id) {
        mID = id;
        if (mID == View.NO_ID && mLabelForId != View.NO_ID) {
            mID = generateViewId();
        }
    }
每個(gè) View 都有它的 ID ,要么在 xml 中指定,要么在代碼中setID(),要么是默認(rèn) ID。告誡大家盡量別用同樣的 ID 命名 View ,否則會(huì)有意想不到的 Bug。

3)PFLAG_SAVE_STATE_CALLED

private static final int PFLAG_SAVE_STATE_CALLED   = 0x00020000;
這個(gè)應(yīng)該是是否在子類實(shí)現(xiàn) onSaveInstanceState() 的標(biāo)志,通過下面的代碼打印的異??梢酝茢?
   if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
               throw new IllegalStateException(
                       "Derived class did not call super.onSaveInstanceState()");
           }

繼續(xù)看 Parcelable state = onSaveInstanceState();

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

由上面代碼可知,默認(rèn)設(shè)置的標(biāo)志位為空,若不為空,則返回相應(yīng)的state
最后由 container 將相應(yīng)的 View 的 ID 和 state 進(jìn)行保存。代碼如下

    if (state != null) {
                // Log.i("View", "Freezing #" + Integer.toHexString(mID)
                // + ": " + state);
                container.put(mID, state);
            }

至此,我們可以知道關(guān)于 onSaveInstanceState(),保存了 View 有用的數(shù)據(jù),包括 ID 和 View 的各種狀態(tài)到一個(gè) Parcelable 對(duì)象并返回。在Activity 的 onSaveInstanceState(Bundle outState) 中通過 Window 的
saveHierarchyState() 方法,最終調(diào)用 View 的 onSaveInstanceState (),返回Parcelable對(duì)象,接著用 Bundle 的 putParcelable 方法保存在 Bundle 的實(shí)例 outState 中。key 值為WINDOW_HIERARCHY_TAG。


Activity 的狀態(tài)還原

如果系統(tǒng)終止您的應(yīng)用進(jìn)程之后,用戶返回您的 Activity,則系統(tǒng)會(huì)重建該 Activity,并將 Bundle 同時(shí)傳給 onCreate() 和 onRestoreInstanceState()。可以使用上述任一方法從 Bundle 提取您保存的狀態(tài)并恢復(fù)該 Activity 狀態(tài)。如果沒有狀態(tài)信息需要恢復(fù),則傳遞給您的 Bundle是為null(如果是首次創(chuàng)建該 Activity,就會(huì)出現(xiàn)這種情況)。

來看它的使用吧

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
         /**
         * 可以在次使用 Bundle 的一系列方法來獲取信息
         * 比如:
         *     savedInstanceState.getInt();
         */
    }

繼續(xù)看

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

在 onSaveInstanceState() 中,Bundle 的 key 值為 WINDOW_HIERARCHY_TAG,在這里自然也使用它來獲取數(shù)據(jù)。接著,如果數(shù)據(jù)不為空,則使用 restoreHierarchyState() 。繼續(xù)跟蹤

    /** {@inheritDoc} */
    @Override
    public void restoreHierarchyState(Bundle savedInstanceState) {
        if (mContentParent == null) {
            return;
        }
        SparseArray<Parcelable> savedStates
                = savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
        if (savedStates != null) {
            mContentParent.restoreHierarchyState(savedStates);
        }
        // 省略部分代碼
            }
        }
    }

由上面源碼中可知
先通過 VIEWS_TAG 獲取 View 的信息。再通過 View 的 restoreHierarchyState() 方法還原。繼續(xù)跟蹤

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

繼續(xù)

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

哎,有種似曾相識(shí)的感覺,從上面可知,先是通過 mID 獲取返回一個(gè) Parcelable 對(duì)象實(shí)例 state,接著再使用 View 的 onRestoreInstanceState() 方法,繼續(xù)看

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

最終是在 View 的 onRestoreInstanceState() 方法中找到是在哪里保存的狀態(tài)。至此,分析結(jié)束。
最后看一個(gè)整體圖


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容