Android onSaveInstanceState/onRestoreInstanceState 原來要這么理解

前言

Activity/Fragment/View 系列文章:

Android Activity 與View 的互動思考
Android Activity 生命周期詳解及監聽
Android onSaveInstanceState/onRestoreInstanceState 原來要這么理解
Android Fragment 要你何用?
Android Activity/View/Window/Dialog/Fragment 深層次關聯(白話解析)

前些天,有位小伙伴興匆匆地跑過來給我展示一個現象:Activity 里有個EditText,點擊該EditText 輸入一些文字。此時,轉動手機方向,Activity 變成橫屏了,而EditText 上的文字依然保留。
問我:為啥EditText上文字能夠恢復?
我說:你Activity 配置了橫豎屏切換時不重建Activity。
他立馬給我展示了:Activity 重建的日志。
我說:系統會在重建Activity 的時候恢復整個ViewTree吧。
他又給我展示了:ImageView 橫豎屏時沒有恢復之前的圖像。
我:...
不服輸的我開始了默默地研究,于是有了這篇總結以解心中困惑。
通過本篇文章,你將了解到:

1、onSaveInstanceState/onRestoreInstanceState 作用。
2、onSaveInstanceState/onRestoreInstanceState 原理分析
3、onSaveInstanceState/onRestoreInstanceState 觸發場景。
4、onSaveInstanceState/onRestoreInstanceState 為啥不能存放大數據?
5、與Jetpack ViewModel 區別。

1、onSaveInstanceState/onRestoreInstanceState 作用

EditText/ImageView 橫豎屏地表現

tt0.top-423136.gif

可以看出,從豎屏到橫屏再恢復到豎屏,EditText 內容沒有變化。而從豎屏到橫屏時,ImageView 內容已經丟失了。
都是系統控件,咱們也沒有進行其它的額外區別處理,為啥表現不一致呢?
View.java 里有兩個方法:

#View.java
    protected Parcelable onSaveInstanceState() {...}

    protected void onRestoreInstanceState(Parcelable state){...}

官方注釋上寫的比較清楚了:

1、onSaveInstanceState 是個鉤子方法,View.java 的子類可以重寫該方法,在方法里面存儲一些子類的內部狀態,用以下次重建時恢復。
2、onRestoreInstanceState 也是個鉤子方法,用以恢復在onSaveInstanceState 里保存的狀態。

既然是View的方法,分別查看EditText 與ImageView 對它們的重寫情況:

#TextView.java
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        ...
        if (freezesText || hasSelection) {
            SavedState ss = new SavedState(superState);

            if (freezesText) {
                if (mText instanceof Spanned) {
                    final Spannable sp = new SpannableStringBuilder(mText);
                    ...
                    ss.text = sp;
                } else {
                    //將TextView 內容存儲在SavedState里
                    ss.text = mText.toString();
                }
            }
            ...
            return ss;
        }

        //返回存儲的對象
        return superState;
    }

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

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

        if (ss.text != null) {
            //取出TextView 內容,并設置
            setText(ss.text);
        }
        ...
    }

由此可見,TextView 重寫這倆方法,先是在onSaveInstanceState 里存儲文本內容,再在onRestoreInstanceState 里恢復文本內容。
而通過查看ImageView 發現它并沒有重寫這倆方法,當然就不能恢復了。其實這也比較容易理解,畢竟對于ImageView,Bitmap 是它的內容,暫存這個Bitmap 很耗內存。

需要注意的是:想要onSaveInstanceState 被調用,則需要給該控件設置id。因為系統是根據View id將狀態存儲在SparseArray 里

Activity 橫豎屏的處理

現在的問題是:誰調用了View 的onSaveInstanceState/onRestoreInstanceState ? 在前一篇分析過Activity 和View的關系:Android Activity 與View 的互動思考
可知,Activity 通過Window 控制View,我們子類繼承自EditText,并重寫onSaveInstanceState/onRestoreInstanceState,然后在橫豎屏切換時查看這倆方法的調用棧:

image.png

第一個紅色框表示EditText子類里的方法(onSaveInstanceState),而第二個紅框表示Activity 子類里重寫的方法(onSaveInstanceState)。
由此可知,當橫豎屏切換時調用了Activity.onSaveInstanceState(xx) 方法。

#Activity.java
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        //saveHierarchyState 調用整個ViewTree 的onSaveInstanceState 方法
        outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
        ...
        //告知生命周期回調方法狀態已保存
        dispatchActivitySaveInstanceState(outState);
    }

同樣的對于onRestoreInstanceState:

#Activity.java
    protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
        if (mWindow != null) {
            Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
            if (windowState != null) {
                //恢復整個ViewTree 狀態
                mWindow.restoreHierarchyState(windowState);
            }
        }
    }

當橫豎屏切換時,會調用到Activity onSaveInstanceState/onRestoreInstanceState 方法,進而會調用整個ViewTree onSaveInstanceState/onRestoreInstanceState 方法來保存與恢復必要的狀態。

Activity 數據保存與恢復

Activity 的onSaveInstanceState/onRestoreInstanceState 方法 除了觸發View 的狀態保存與恢復外,還可以將Activity 用到的一些重要的數據保存下來,待下次Activity 重建時恢復。
重寫兩者:

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("say", "hello world");
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        String restore = savedInstanceState.getString("say");
        Log.d("fish", restore);
    }

此時我們注意到onSaveInstanceState 的入參是Bundle 類型,往outState 寫入數據,在onRestoreInstanceState 將數據取出,outState/savedInstanceState 必然不為空。

總結 onSaveInstanceState/onRestoreInstanceState 作用

1、保存與恢復View 的狀態。
2、保存與恢復Activity 自定義數據。

2、onSaveInstanceState/onRestoreInstanceState 原理分析。

onSaveInstanceState 調用時機

之前 Android Activity 生命周期詳解及監聽 有詳細分析了Activity 各個階段的調用情況,現在結合生命周期來分析onSaveInstanceState(xx)在生命周期中的哪個階段被調用的。
調用棧如下:

image.png

看看上圖標黃色的方法,這方法很眼熟,在Activity 生命周期中分析過,它是Activity.onStop()方法的調用者:

#ActivityThread.java
    private void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason) {
        // Before P onSaveInstanceState was called before onStop, starting with P it's
        // called after. Before Honeycomb state was always saved before onPause.
        //這句話翻譯過來:
        //如果目標設備是Android 9之前,那么onSaveInstanceState 在onStop 之前調用
        //如果在Android 9 之后,那么onSaveInstanceState 在onStop 之后調用
        //Honeycomb 指的是Android 3.0 現在基本可以忽略了。
        //r.activity.mFinished 表示Activity 是否即將被銷毀
        final boolean shouldSaveState = saveState && !r.activity.mFinished && r.state == null
                && !r.isPreHoneycomb();
        final boolean isPreP = r.isPreP();
        //Android p 之前先于onStop 之前執行
        if (shouldSaveState && isPreP) {
            callActivityOnSaveInstanceState(r);
        }
        try {
            //最終執行到Activity.onStop()方法
            r.activity.performStop(r.mPreserveWindow, reason);
        } catch (SuperNotCalledException e) {
            ...
        }
        //標記Stop狀態
        r.setState(ON_STOP);

        if (shouldSaveState && !isPreP) {
            //調用onSave 保存
            callActivityOnSaveInstanceState(r);
        }
    }

以上注釋比較詳細了,小結一下:

1、在Android 9之前,onSaveInstanceState 在onStop 之前調用(至于在onPause 之前還是之后調用,這個時機不確定);在Android 9(包含)之后,onSaveInstanceState 在onStop 之后調用。
2、如果Activity 即將被銷毀,則onSaveInstanceState 不會被調用。

對于第二句的理解,舉個簡單例子:

Activity 在前臺時,此時按Home鍵回到桌面,會執行onSaveInstanceState;若是按back鍵/主動finish,此時雖然會執行到onStop,但是不會執行onSaveInstanceState。

onRestoreInstanceState 調用時機

現在已經弄清楚onSaveInstanceState 調用時機,接著來分析 onRestoreInstanceState 什么時候執行。
調用棧如下:


image.png

黃色部分的方法也很眼熟,它是Activity.onStart()方法的調用者:

    public void handleStartActivity(ActivityClientRecord r,
                                    PendingTransactionActions pendingActions) {
        final Activity activity = r.activity;
        ...
        //最終執行到Activity.onStart()
        activity.performStart("handleStartActivity");
        r.setState(ON_START);
        ...
        if (pendingActions.shouldRestoreInstanceState()) {
            if (r.isPersistable()) {
                //從持久化存儲里恢復數據
                if (r.state != null || r.persistentState != null) {
                    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                            r.persistentState);
                }
            } else if (r.state != null) {
                //從內存里恢復數據
                mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
            }
        }
        ...
    }

小結:

1、onRestoreInstanceState 在onStart()方法之后執行。
2、pendingActions.shouldRestoreInstanceState() 返回值是執行onRestoreInstanceState()方法的關鍵,它是在哪賦值的呢?接下來會分析。
3、r.state 不能為空,畢竟沒數據無法恢復。

通過以上分析,結合Activity生命周期,onSaveInstanceState /onRestoreInstanceState 調用時機如下:


image.png

onRestoreInstanceState 與onCreate 參數差異

onCreate參數也是Bundle類型,實際上這個參數就是onSaveInstanceState里保存的Bundle,這個Bundle分別傳遞給了onCreate和onRestoreInstanceState,而onCreate里的Bundle可能為空(新建非重建的情況下),onRestoreInstanceState 里的Bundle必然不為空。
官方注釋也說了在onRestoreInstanceState里處理數據的恢復更靈活。

3、onSaveInstanceState/onRestoreInstanceState 觸發場景

橫豎屏觸發的場景

在前面的分析中,與Activity 生命周期關聯可能會讓人有種印象:
onSaveInstanceState 調用之后onRestoreInstanceState 就會被調用。
而事實并非如此,舉個簡單例子:
Activity 處在前臺時,此時退回到桌面,onSaveInstanceState 會被執行。而后再讓Activity 回到前臺,onStart()方法執行后,發現onRestoreInstanceState 并沒有被調用。

也就是說onSaveInstanceState/onRestoreInstanceState 的調用不一定是成對出現的。

還記得在分析onRestoreInstanceState 遺留了個問題:pendingActions.shouldRestoreInstanceState() 返回值如何確定的 ?
在橫豎屏切換時,onRestoreInstanceState 被調用了,說明pendingActions.shouldRestoreInstanceState() 在橫豎屏切換時返回了true,接著來看看其來龍去脈:

#PendingTransactionActions.java
    //判斷是否需要執行onRestoreInstanceState 方法
    public boolean shouldRestoreInstanceState() {
        return mRestoreInstanceState;
    }

    //設置標記
    public void setRestoreInstanceState(boolean restoreInstanceState) {
        mRestoreInstanceState = restoreInstanceState;
    }

只需要找到setRestoreInstanceState()在何處調用即可。
直接說結論:

ActivityThread.handleLaunchActivity() 里設置了setRestoreInstanceState(true)

而handleLaunchActivity()在兩種情況下被調用:


image.png

橫豎屏時屬于重建 Activity,因此onRestoreInstanceState 能被調用。
而從后臺返回到前臺,并沒有新建Activity也沒有重建Activity,因此onRestoreInstanceState 不會被調用。
又引申出另一個問題:為啥新建Activity 時onRestoreInstanceState 沒被調用?
答案:因為新建Activity 時,ActivityClientRecord 是全新的對象,它所持有的Bundle state 對象為空,因此不會調用到onRestoreInstanceState。

其它配置項更改的場景

除了橫豎屏切換時會重建Activity,還有以下配置項更改會重建Activity:


image.png

當然,還有一些不常涉及的配置項,比如所在地區更改等。

重建Activity 的細節

image.png

當需要重建Activity 時,AMS 發出指令,會執行到ActivityThread.handleRelaunchActivity()方法。

#ActivityThread.java
    public void handleRelaunchActivity(ActivityClientRecord tmp,
                                       PendingTransactionActions pendingActions) {
        ...
        //從Map 里獲取緩存的ActivityClientRecord
        ActivityClientRecord r = mActivities.get(tmp.token);
        ...
        //將ActivityClientRecord 傳遞下去
        handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
        ...
    }

mActivities 定義如下:

final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();

以IBinder 為key,存儲ActivityClientRecord。
當新建Activity 時,存入ActivityClientRecord,當銷毀Activity 時,移除ActivityClientRecord。

再來分析handleRelaunchActivityInner():

#ActivityThread.java
    private void handleRelaunchActivityInner(...) {
        ...
        if (!r.paused) {
            //最終執行到onPause
            performPauseActivity(r, false, reason, null /* pendingActions */);
        }
        if (!r.stopped) {
            //最終執行到onStop
            callActivityOnStop(r, true /* saveState */, reason);
        }
        //最終執行到onDestroy
        handleDestroyActivity(r.token, false, configChanges, true, reason);
        //創建新的Activity 實例
        handleLaunchActivity(r, pendingActions, customIntent);
    }

通過分析Activity 重建的細節,有以下結論:

1、Activity 重建過程中,先將原來的Activity 進行銷毀(從onResume--onStop-->onDestroy 的生命周期)。
2、雖然是不同的Activity 對象,但重建時使用的ActivityClientRecord 卻是相同的,而ActivityClientRecord 最終是被ActivityThread 持有,它是全局的。這也是onSaveInstanceState/onRestoreInstanceState 能夠存儲與恢復數據的本質原因。

當然也可以通過配置告訴系統在配置項變更時不重建Activity:

        <activity android:name=".viewmodel.ViewModelActivity" android:configChanges="orientation|screenSize"></activity>

比如以上配置,當橫豎屏切換時,不會重建Activity,而配置項的變更會通過Activity.onConfigurationChanged()方法回調。

4、onSaveInstanceState/onRestoreInstanceState 為啥不能存放大數據?

onSaveInstanceState/onRestoreInstanceState 的參數都是Bundle 類型,思考一下為什么需要定義為Bundle類型呢?
Android IPC 精講系列 中有提到過,Android 進程間通信方式大多時候使用的是Binder,而要想自定義數據能夠通過Binder傳輸則需要實現Parcelable 接口,Bundle 實現了Parcelable 接口。

由此我們推測,onSaveInstanceState/onRestoreInstanceState 可能涉及到進程間通信,才會用Bundle 來修飾形參。但之前說的ActivityClientRecord是存儲在當前進程的啊,貌似和其它進程沒有關聯呢?
要分析這個問題,實際上只需要在onSaveInstanceState 存儲一個比較大的數據,看看報錯時的堆棧。

    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("say", "hello world");
        //存儲2M 數據
        outState.putByteArray("big", new byte[1024*1024*2]);
    }

保存2M 的數據,通常來說這是超出了Binder的限制,當調用onSaveInstanceState 時會有報錯信息:


image.png

果然還是crash了。
找到 PendingTransactionActions ,它實現了Runnable 接口,在其run方法里:

#PendingTransactionActions.java
    public void run() {
        try {
            //提交給ActivityTaskManagerService 處理,屬于進程間通信
            //mState 即是onSaveInstanceState 保存的數據
            ActivityTaskManager.getService().activityStopped(
                    mActivity.token, mState, mPersistentState, mDescription);
        } catch (RemoteException ex) {
            ...
        }
    }

而在ActivityThread.java 里有個方法:

    public void reportStop(PendingTransactionActions pendingActions) {
        mH.post(pendingActions.getStopInfo());
    }

該方法用于告知系統,咱們的Activity 已經變為Stop狀態了,最終會執行到PendingTransactionActions.run()方法。
小結一下:

onSaveInstanceState 存儲的數據,在onStop執行后,當前進程需要將Stop狀態傳遞給ATM(ActivityTaskManagerService 運行在system_server進程),因為跨進程傳遞(Binder)有大小限制,因此onSaveInstanceState 不能傳遞大量數據。

5、與Jetpack ViewModel 區別

onSaveInstanceState 與 ViewModel 都是將數據放在ActivityClientRecord 的不同字段里。


image.png

1、onSaveInstanceState 用Bundle存儲數據便于跨進程傳遞,而ViewModel 是Object存儲數據,不需要跨進程,因此它沒有大小限制。
2、onSaveInstanceState 在onStop 之后調用,比較頻繁。而ViewModel 存儲數據是onDestroy 之后。
3、onSaveInstanceState 可以選擇是否持久化數據到文件里(該功能由ATM 實現,存儲到xml里),而ViewModel 沒有這功能。

更多的區別后續分析 ViewModel 時會提到。
測試地址

本文基于Android 10.0。

您若喜歡,請點贊、關注,您的鼓勵是我前進的動力

持續更新中,和我一起步步為營系統、深入學習Android

1、Android各種Context的前世今生
2、Android DecorView 必知必會
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分發全套服務
6、Android invalidate/postInvalidate/requestLayout 徹底厘清
7、Android Window 如何確定大小/onMeasure()多次執行原因
8、Android事件驅動Handler-Message-Looper解析
9、Android 鍵盤一招搞定
10、Android 各種坐標徹底明了
11、Android Activity/Window/View 的background
12、Android Activity創建到View的顯示過
13、Android IPC 系列
14、Android 存儲系列
15、Java 并發系列不再疑惑
16、Java 線程池系列
17、Android Jetpack 實踐與原理系列

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

推薦閱讀更多精彩內容