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

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


Activity 的狀態(tài)保存

一般來(lái)說(shuō)再来,當(dāng) Activity 暫臀钫叮或停止(onPause() 或 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ì)得到保留狈涮,這樣一來(lái)狐胎,當(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í)根本無(wú)法讓其狀態(tài)保持完好,而是必須在用戶返回 Activity 時(shí)重建 Activity 對(duì)象(重新 onCreate() )晓锻。但用戶并不知道系統(tǒng)銷毀 Activity 后又對(duì)其進(jìn)行了重建歌焦,因此他們很可能認(rèn)為 Activity 狀態(tài)毫無(wú)變化,這時(shí)可能會(huì)把用戶希望保存的信息也一起銷毀掉砚哆,導(dǎo)致不好的用戶體驗(yàn)独撇。為了處理這種情況,Android 為我們提供了一個(gè) onSaveInstanceState() 的方法躁锁,我們可以回調(diào)這個(gè)方法來(lái)保存一些用戶希望保存的信息纷铣。在 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蒜哀。

來(lái)看看 onSaveInstanceState() 的使用

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

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

再來(lái)看下 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 通過(guò) 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;
由注釋可知它是用來(lái)標(biāo)志一個(gè)沒(méi)有 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)志,通過(guò)下面的代碼打印的異澄诚颍可以推斷
   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) 中通過(guò) 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)鹦筹。如果沒(méi)有狀態(tài)信息需要恢復(fù)铝阐,則傳遞給您的 Bundle是為null(如果是首次創(chuàng)建該 Activity,就會(huì)出現(xiàn)這種情況)盛龄。

來(lái)看它的使用吧

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
         /**
         * 可以在次使用 Bundle 的一系列方法來(lái)獲取信息
         * 比如:
         *     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,在這里自然也使用它來(lái)獲取數(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);
        }
        // 省略部分代碼
            }
        }
    }

由上面源碼中可知
先通過(guò) VIEWS_TAG 獲取 View 的信息赠制。再通過(guò) 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í)的感覺(jué)钟些,從上面可知,先是通過(guò) 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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市宪赶,隨后出現(xiàn)的幾起案子宗弯,更是在濱河造成了極大的恐慌,老刑警劉巖搂妻,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒙保,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡欲主,警方通過(guò)查閱死者的電腦和手機(jī)邓厕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門逝嚎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人邑狸,你說(shuō)我怎么就攤上這事懈糯。” “怎么了单雾?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵赚哗,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我硅堆,道長(zhǎng)屿储,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任渐逃,我火速辦了婚禮够掠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘茄菊。我一直安慰自己疯潭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布面殖。 她就那樣靜靜地躺著竖哩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪脊僚。 梳的紋絲不亂的頭發(fā)上相叁,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音辽幌,去河邊找鬼增淹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛乌企,可吹牛的內(nèi)容都是我干的虑润。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼加酵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼端辱!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起虽画,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荣病,沒(méi)想到半個(gè)月后码撰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡个盆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年脖岛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了朵栖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡柴梆,死狀恐怖陨溅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绍在,我是刑警寧澤门扇,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站偿渡,受9級(jí)特大地震影響臼寄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜溜宽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一吉拳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧适揉,春花似錦留攒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至吃沪,卻和暖如春汤善,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背票彪。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工红淡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人降铸。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓在旱,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親推掸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子桶蝎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

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