前言
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 橫豎屏地表現
可以看出,從豎屏到橫屏再恢復到豎屏,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,然后在橫豎屏切換時查看這倆方法的調用棧:
第一個紅色框表示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)在生命周期中的哪個階段被調用的。
調用棧如下:
看看上圖標黃色的方法,這方法很眼熟,在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 什么時候執行。
調用棧如下:
黃色部分的方法也很眼熟,它是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 調用時機如下:
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()在兩種情況下被調用:
橫豎屏時屬于重建 Activity,因此onRestoreInstanceState 能被調用。
而從后臺返回到前臺,并沒有新建Activity也沒有重建Activity,因此onRestoreInstanceState 不會被調用。
又引申出另一個問題:為啥新建Activity 時onRestoreInstanceState 沒被調用?
答案:因為新建Activity 時,ActivityClientRecord 是全新的對象,它所持有的Bundle state 對象為空,因此不會調用到onRestoreInstanceState。
其它配置項更改的場景
除了橫豎屏切換時會重建Activity,還有以下配置項更改會重建Activity:
當然,還有一些不常涉及的配置項,比如所在地區更改等。
重建Activity 的細節
當需要重建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 時會有報錯信息:
果然還是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 的不同字段里。
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 實踐與原理系列