一、概述
在開發過程中,不可避免地會遇到Activity
被回收的場景, Activity
被回收有兩種情況:主動和被動。
- 當
Activity
是被主動回收時,例如按下了Back
鍵,那麼這時候是無法恢復的,因為系統認為你已經不再需要它了。 - 在被動回收的情況下,雖然這個
Activity
的例項已經被銷燬了,但是系統在新建一個Activity
例項的時候,會帶上先前被回收Activity
的資訊,這些資訊是被儲存在Bundle
的鍵值對,這裡面有些是系統幫我們讀寫的,例如Activity
當中View
的狀態,這部分的資訊並不需要我們擔心。(為了系統能幫我們恢復狀態,必須要為每個View
都指定一個id
),如果我們希望儲存更多臨時的資訊,而這些資訊又沒有必要寫入到持久化的儲存當中,這時候我們應該重寫onSaveInstanceState
方法,在該回撥當中會傳入一個Bundle
物件,只需要把儲存的資訊放到裡面,之後再在onRestoreInstanceState
和onCreate
方法中把它讀取出來就可以了,這裡面有兩點需要注意的: onRestoreInstance
只有在Bundle
不為空時才會回撥,而在onCreate
方法當中是通過判空操作來判斷是否有需要恢復的狀態的。- 在重寫
onRestoreInstanceState
方法時,應當先呼叫super
方法,這樣由系統負責儲存的部分才能夠恢復。
二、疑問
關於狀態的儲存和恢復,實現方法很簡單,我們主要了解一下它內部的原理,主要有這麼幾個問題:
2.1 對於View
的狀態,是怎麼做到自動恢復的
由於文件要求我們必須呼叫super
方法,那麼就可以知道儲存View
狀態的程式碼入口必然在Activity
當中,我們看下Activity
的預設實現:
<!-- Activity.java -->
protected void onSaveInstanceState(Bundle outState){
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState);
}
//看到mWindow,自然就想到了PhoneWindow.java
<!-- PhoneWindow.java -->
public Bundle saveHierarchyState() {
mContentParent.saveHierarchyState(states); //這裡是儲存View狀態的地方。
outState.putInt(FOCUSED_ID_TAG, focusedView.getId()); //儲存Focus。
outState.putSparseParcelableArray(PANELS_TAG, panelStates); //儲存Panel狀態。
}
<!-- View.java -->
public void saveHierachyState(SparseArray<Parcelable> container) {
dispatchSaveInstanceState(container);
}
protected void dispatchSaveInstceState(Parcelable> container) {
Parcelable state = onSaveInstanceState();
container.put(mID, state);
}
複製程式碼
整個儲存View
狀態的流程如下:
- 呼叫
Activity
的onSaveInstanceState
方法 - 該方法又呼叫
mWindow.saveHierarchyState
,把返回的結果儲存到WINDOW_HIERARCHY_TAG
這個Key
對應的Value
中 mWindow
的實現類PhoneWindow
當中:- 呼叫根佈局的
saveHierarchyState
方法,這裡面會從根佈局按樹形結構遍歷,呼叫每個ViewGroup/View
的onSaveInstanceState
- 儲存
FoucusView
同時,我們也可以得到結論,儲存的前提有兩個
View
的子類必須實現了onSaveInstanceState
- 它必須要有一個
ID
,這個ID
作為Bundle
的key
,這也為我們實現自定義 View 時,需要儲存狀態提供了思路。
2.2 onSaveInstanceState
呼叫時機
下面看下第二個問題,有了前面分析Activity
生命週期的經驗,我們直接在ActivityThread
中找相關的程式碼:
<!-- ActivityThread.java -->
//在ActivityThread中,一共有3處呼叫了 saveInstanceState
private void handleRelaunchActivity(ActivityClientRecord tmp) {
performPauseActivity(r.token, false, r.isPreHoneycomb());
if (r.state == null && !r.stopped && !r.isPreHoneycomb) {
//儲存
}
}
final Bundle performPauseActivity(ActivityRecord r, boolean finished, boolean saveState) {
if (!r.activity.mFinished && saveState) { //這裡saveState的條件是!r.isPreHoneycomb。
//儲存
}
mInstrumentation.callActivityOnPause(r.activity);
}
//這個函式呼叫的地方有:
1.private void handleWindowVisibility(IBinder token, boolean show) //這裡saveState是false
2.public void performStopActivity(IBinder token, boolean saveState) //LocalActivityManager調過來的也是false
3.private void handleStopActivity(IBinder token, boolean show, int configChanges) //這裡是true,通過 ActivityManagerSupervisor 調過來的
private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShow, boolean saveState) {
if (r.state == null) {
//儲存
}
r.activity.performStop();
}
複製程式碼
看完這三個呼叫的地方,問題又解決了,結論就是:
- 如果是
honeycomb
之後的版本,那麼在performPauseActivity
時是不會儲存的,而對於honeycomb
之前的版本,會在回撥onPause()
之前儲存。 - 如果在
performPauseActivity
時沒有儲存,那麼在執行performStopActivityInner
時r.state
為空並且是從handleStopActivity
過來的,那麼會在onPause()
和onStop()
之間儲存狀態,其它情況下不會儲存狀態。 - 在
ReLaunchAcitivity
時,也會儲存狀態。
2.3 onRestoreInstanceState
呼叫時機
在Activity
的生命週期解析中分析onStart()
方法的時候我們已經知道,它是在onStart()
和onResume()
之間被呼叫的。