在Activity被回收之前,系統(tǒng)會(huì)調(diào)用Activity#onSaveInstanceState(Bundle outState)
來保存View的狀態(tài)到傳入的outState
對(duì)象中。
在Activity被重新創(chuàng)建時(shí),Activity#onCreate(Bundle savedInstanceState)
和Activity#onRestoreInstanceState(Bundle savedInstanceState)
會(huì)傳入保存的狀態(tài)信息并恢復(fù)View的狀態(tài)。
用戶也可以重載Activity#onSaveInstanceState()
方法來保存額外的Activity狀態(tài),并在Activity.onCreate()
或者Activity#onRestoreInstanceState()
獲取這些狀態(tài)。
這里主要看View的狀態(tài)是怎么保存和重新獲取的。
先說一下Bundle類,在Android代碼里的注釋是:
A mapping from String values to various Parcelable types.
可以簡單把Bundle看成一個(gè)Map<String, Object>,其中Object都實(shí)現(xiàn)了Parcelable接口。Bundle類有一系列的set和get方法用來操作Map中的值。
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);
}
這個(gè)方法做了三件事,1.保存Activity對(duì)應(yīng)的Window的State信息,并存放在outState中,2.保存所有Fragements的信息,如果不為零,也存放在outState中,3.調(diào)用外部注冊(cè)的一些回調(diào)方法。保存Fragments的信息是通過保存Fragment的根View的狀態(tài)實(shí)現(xiàn)的,這和保存Window的State信息類似。所以看mWindow.saveHierarchyState()
方法,這里mWindow是一個(gè)PhoneWindow對(duì)象,找到PhoneWindow#saveHierarchyState()
方法:
public Bundle saveHierarchyState() {
//step 1:
Bundle outState = new Bundle();
if (mContentParent == null) {
return outState;
}
// step 2: save View states
SparseArray<Parcelable> states = new SparseArray<Parcelable>();
mContentParent.saveHierarchyState(states);
outState.putSparseParcelableArray(VIEWS_TAG, states);
// step 3: save the focused view id
View focusedView = mContentParent.findFocus();
if (focusedView != null) {
if (focusedView.getId() != View.NO_ID) {
outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
} else {
if (false) {
Log.d(TAG, "couldn't save which view has focus because the focused view "
+ focusedView + " has no id.");
}
}
}
// step 4: save the panels state
SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();
savePanelState(panelStates);
if (panelStates.size() > 0) {
outState.putSparseParcelableArray(PANELS_TAG, panelStates);
}
// step 5: save actionBar state
if (mDecorContentParent != null) {
SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
mDecorContentParent.saveToolbarHierarchyState(actionBarStates);
outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
}
return outState;
}
這個(gè)方法依次做了這些事情:
1.創(chuàng)建Bundle對(duì)象用來返回,判斷Window是否有對(duì)應(yīng)的mContentParent這個(gè)View對(duì)象,如果沒有直接返回
2.保存View的信息
3.保存當(dāng)前View焦點(diǎn)的信息
4.保存抽屜信息
5.保存ActionBar信息。
我們關(guān)注第二步保存View信息: mContentParent.saveHierarchyState(states);
.這里states
是一個(gè)SparseArray<Parcelable>
對(duì)象,所有View信息都會(huì)保存在states
中。SparseArray
可以理解為Map<Integer,Object>,是Android系統(tǒng)為了優(yōu)化內(nèi)存創(chuàng)建的類。
看一下View#saveHierarchyState(SparseArray<Parcelable> container)
:
public void saveHierarchyState(SparseArray<Parcelable> container) {
dispatchSaveInstanceState(container);
}
直接調(diào)用了View#dispatchSaveInstanceState(SparseArray<Parcelable> container)
:
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);
}
}
}
檢查一下View是否設(shè)置了ID,如果沒有id直接返回。這里我們可以看到,如果View沒有設(shè)置id,是不會(huì)保存它的狀態(tài)的。設(shè)置了ID之后,獲取狀態(tài),并將狀態(tài)以ID為key存儲(chǔ)在SparseArray
中。這里可以看出,如果View樹中兩個(gè)View的id相同,那么后一個(gè)View的SavedState
會(huì)覆蓋前一個(gè)SavedState
。當(dāng)然這種情況下findViewById()
也會(huì)出問題。
保存View狀態(tài)是在onSaveInstanceState()
中實(shí)現(xiàn)的:
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;
}
這個(gè)方法檢查了一下mStartActivityRequestWho
這個(gè)String對(duì)象是否為null,mStartActivityRequestWho
這個(gè)對(duì)象只有在調(diào)用了View#startActivityForResult
時(shí)才會(huì)設(shè)置,這時(shí)標(biāo)記一下state中的狀態(tài),為null時(shí),直接返回一個(gè)空狀態(tài)。
在View類中保存狀態(tài)里用了三個(gè)方法,其中saveHierachyState()
方法直接傳遞給了dispatchSaveInstanceState()
方法。dispatchSaveInstanceState()
用來分發(fā)保存狀態(tài)的行為,這個(gè)方法的默認(rèn)行為是在有ID的情況下保存自身的state,沒有id的情況下什么都不做。所以ViewGroup可以選擇重寫這個(gè)方法,將保存狀態(tài)的行為分發(fā)到子類中。onSaveInstanceState()
方法用來具體地保存當(dāng)前View的狀態(tài),自定義View可以選擇重寫這個(gè)方法。
看一下ViewGroup#dispatchSaveInstanceState()
方法
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
super.dispatchSaveInstanceState(container);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
View c = children[i];
if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
c.dispatchSaveInstanceState(container);
}
}
}
首先調(diào)用View#dispatchSaveInstanceState()
保存自身的狀態(tài),然后遍歷子View,調(diào)用對(duì)應(yīng)的dispatchSaveInstanceState()
方法,這里實(shí)現(xiàn)的View樹的遍歷。
看一下TextView#onSaveInstanceState()
是怎么保存狀態(tài)的:
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
// Save state if we are forced to
boolean save = mFreezesText;
int start = 0;
int end = 0;
if (mText != null) {
start = getSelectionStart();
end = getSelectionEnd();
if (start >= 0 || end >= 0) {
// Or save state if there is a selection
save = true;
}
}
if (save) {
SavedState ss = new SavedState(superState);
// XXX Should also save the current scroll position!
ss.selStart = start;
ss.selEnd = end;
if (mText instanceof Spanned) {
Spannable sp = new SpannableStringBuilder(mText);
if (mEditor != null) {
removeMisspelledSpans(sp);
sp.removeSpan(mEditor.mSuggestionRangeSpan);
}
ss.text = sp;
} else {
ss.text = mText.toString();
}
if (isFocused() && start >= 0 && end >= 0) {
ss.frozenWithFocus = true;
}
ss.error = getError();
if (mEditor != null) {
ss.editorState = mEditor.saveInstanceState();
}
return ss;
}
return superState;
}
首先判斷TextView是否需要保存狀態(tài),如果mFreezesText設(shè)置為true,或者TextView處于被選中的狀態(tài),那么需要保存狀態(tài)。不許要保存保存狀態(tài)的話直接返回super.onSaveInstanceState()
.
TextView中有個(gè)內(nèi)部類SavedState用來表示狀態(tài),將對(duì)應(yīng)的狀態(tài)設(shè)置好之后直接返回即可。
至此,整個(gè)保存狀態(tài)的過程已經(jīng)走完,總結(jié)一下:
1.在Activity被回收時(shí),會(huì)觸發(fā)一個(gè)SaveState的事件。
2.跟其他的事件一樣,SaveState事件從Activity->Window->View傳遞到最大的View,然后遍歷View樹保存狀態(tài)
3.狀態(tài)保存在一個(gè)SparseArray中,以View的ID作為key。
4.自定義View可以重載onSaveInstanceState()
來保存自己的狀態(tài),參考TextView的實(shí)現(xiàn)方法。
Restore的過程:Activity#onRestoreInstanceState()
方法:
protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (mWindow != null) {
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
mWindow.restoreHierarchyState(windowState);
}
}
}
跟保存的過程一樣,傳遞到Window#restoreHierarchyState()
方法中:
public void restoreHierarchyState(Bundle savedInstanceState) {
// step 1.
if (mContentParent == null) {
return;
}
// step 2.
SparseArray<Parcelable> savedStates
= savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
if (savedStates != null) {
mContentParent.restoreHierarchyState(savedStates);
}
// step 3. restore the focused view
int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID);
if (focusedViewId != View.NO_ID) {
View needsFocus = mContentParent.findViewById(focusedViewId);
if (needsFocus != null) {
needsFocus.requestFocus();
} else {
Log.w(TAG,
"Previously focused view reported id " + focusedViewId
+ " during save, but can't be found during restore.");
}
}
// step 4. restore the panels
SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG);
if (panelStates != null) {
restorePanelState(panelStates);
}
// step 5.
if (mDecorContentParent != null) {
SparseArray<Parcelable> actionBarStates =
savedInstanceState.getSparseParcelableArray(ACTION_BAR_TAG);
if (actionBarStates != null) {
doPendingInvalidatePanelMenu();
mDecorContentParent.restoreToolbarHierarchyState(actionBarStates);
} else {
Log.w(TAG, "Missing saved instance states for action bar views! " +
"State will not be restored.");
}
}
}
跟保存的過程完全一致,分五步。我們只看第三步:mContentParent.restoreHierarchyState(savedStates)
,這里調(diào)用View#restoreHierarchyState(savedStates)
方法:
public void restoreHierarchyState(SparseArray<Parcelable> container) {
dispatchRestoreInstanceState(container);
}
一樣直接傳遞到View#dispatchRestoreInstanceState()
方法:
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()");
}
}
}
}
對(duì)于View的這個(gè)方法,從SparseArray中用mID獲取到state,然后調(diào)用View#onRestoreInstanceState()
方法。看一下ViewGroup對(duì)應(yīng)的方法:
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
super.dispatchRestoreInstanceState(container);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
View c = children[i];
if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
c.dispatchRestoreInstanceState(container);
}
}
}
先調(diào)用View版本的dispatchRestoreInstanceState()
方法,然后遍歷childView,調(diào)用對(duì)應(yīng)的dispatchRestoreInstanceState()
方法。
最后看一下View#onRestoreInstanceState()
方法:
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 ");
}
if (state != null && state instanceof BaseSavedState) {
mStartActivityRequestWho = ((BaseSavedState) state).mStartActivityRequestWhoSaved;
}
}
這個(gè)方法很簡單,直接獲取mStartActivityRequestWho對(duì)象,對(duì)于自定義View,需要重寫這個(gè)方法,獲取自己的狀態(tài)。同樣看一下TextView#onRestoreInstanceState()
方法:
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState)state;
super.onRestoreInstanceState(ss.getSuperState());
// XXX restore buffer type too, as well as lots of other stuff
if (ss.text != null) {
setText(ss.text);
}
if (ss.selStart >= 0 && ss.selEnd >= 0) {
if (mText instanceof Spannable) {
int len = mText.length();
if (ss.selStart > len || ss.selEnd > len) {
String restored = "";
if (ss.text != null) {
restored = "(restored) ";
}
Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
"/" + ss.selEnd + " out of range for " + restored +
"text " + mText);
} else {
Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
if (ss.frozenWithFocus) {
createEditorIfNeeded();
mEditor.mFrozenWithFocus = true;
}
}
}
}
if (ss.error != null) {
final CharSequence error = ss.error;
// Display the error later, after the first layout pass
post(new Runnable() {
public void run() {
if (mEditor == null || !mEditor.mErrorWasChanged) {
setError(error);
}
}
});
}
if (ss.editorState != null) {
createEditorIfNeeded();
mEditor.restoreInstanceState(ss.editorState);
}
}
首先判斷state是否為TextView保存的狀態(tài),如果不是,直接調(diào)用super,然后獲取對(duì)應(yīng)的狀態(tài)設(shè)置給TextView。
可以看出Restore過程和Save過程完全相同。