在Activity被回收之前,系统会调用Activity#onSaveInstanceState(Bundle outState)
来保存View的状态到传入的outState
对象中。
在Activity被重新创建时,Activity#onCreate(Bundle savedInstanceState)
和Activity#onRestoreInstanceState(Bundle savedInstanceState)
会传入保存的状态信息并恢复View的状态。
用户也可以重载Activity#onSaveInstanceState()
方法来保存额外的Activity状态,并在Activity.onCreate()
或者Activity#onRestoreInstanceState()
获取这些状态。
这里主要看View的状态是怎么保存和重新获取的。
先说一下Bundle类,在Android代码里的注释是:
A mapping from String values to various Parcelable types.
可以简单把Bundle看成一个Map<String, Object>,其中Object都实现了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);
}
这个方法做了三件事,1.保存Activity对应的Window的State信息,并存放在outState中,2.保存所有Fragements的信息,如果不为零,也存放在outState中,3.调用外部注册的一些回调方法。保存Fragments的信息是通过保存Fragment的根View的状态实现的,这和保存Window的State信息类似。所以看mWindow.saveHierarchyState()
方法,这里mWindow是一个PhoneWindow对象,找到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;
}
这个方法依次做了这些事情:
1.创建Bundle对象用来返回,判断Window是否有对应的mContentParent这个View对象,如果没有直接返回
2.保存View的信息
3.保存当前View焦点的信息
4.保存抽屉信息
5.保存ActionBar信息。
我们关注第二步保存View信息: mContentParent.saveHierarchyState(states);
.这里states
是一个SparseArray<Parcelable>
对象,所有View信息都会保存在states
中。SparseArray
可以理解为Map<Integer,Object>,是Android系统为了优化内存创建的类。
看一下View#saveHierarchyState(SparseArray<Parcelable> container)
:
public void saveHierarchyState(SparseArray<Parcelable> container) {
dispatchSaveInstanceState(container);
}
直接调用了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是否设置了ID,如果没有id直接返回。这里我们可以看到,如果View没有设置id,是不会保存它的状态的。设置了ID之后,获取状态,并将状态以ID为key存储在SparseArray
中。这里可以看出,如果View树中两个View的id相同,那么后一个View的SavedState
会覆盖前一个SavedState
。当然这种情况下findViewById()
也会出问题。
保存View状态是在onSaveInstanceState()
中实现的:
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;
}
这个方法检查了一下mStartActivityRequestWho
这个String对象是否为null,mStartActivityRequestWho
这个对象只有在调用了View#startActivityForResult
时才会设置,这时标记一下state中的状态,为null时,直接返回一个空状态。
在View类中保存状态里用了三个方法,其中saveHierachyState()
方法直接传递给了dispatchSaveInstanceState()
方法。dispatchSaveInstanceState()
用来分发保存状态的行为,这个方法的默认行为是在有ID的情况下保存自身的state,没有id的情况下什么都不做。所以ViewGroup可以选择重写这个方法,将保存状态的行为分发到子类中。onSaveInstanceState()
方法用来具体地保存当前View的状态,自定义View可以选择重写这个方法。
看一下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);
}
}
}
首先调用View#dispatchSaveInstanceState()
保存自身的状态,然后遍历子View,调用对应的dispatchSaveInstanceState()
方法,这里实现的View树的遍历。
看一下TextView#onSaveInstanceState()
是怎么保存状态的:
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是否需要保存状态,如果mFreezesText设置为true,或者TextView处于被选中的状态,那么需要保存状态。不许要保存保存状态的话直接返回super.onSaveInstanceState()
.
TextView中有个内部类SavedState用来表示状态,将对应的状态设置好之后直接返回即可。
至此,整个保存状态的过程已经走完,总结一下:
1.在Activity被回收时,会触发一个SaveState的事件。
2.跟其他的事件一样,SaveState事件从Activity->Window->View传递到最大的View,然后遍历View树保存状态
3.状态保存在一个SparseArray中,以View的ID作为key。
4.自定义View可以重载onSaveInstanceState()
来保存自己的状态,参考TextView的实现方法。
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)
,这里调用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()");
}
}
}
}
对于View的这个方法,从SparseArray中用mID获取到state,然后调用View#onRestoreInstanceState()
方法。看一下ViewGroup对应的方法:
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);
}
}
}
先调用View版本的dispatchRestoreInstanceState()
方法,然后遍历childView,调用对应的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;
}
}
这个方法很简单,直接获取mStartActivityRequestWho对象,对于自定义View,需要重写这个方法,获取自己的状态。同样看一下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保存的状态,如果不是,直接调用super,然后获取对应的状态设置给TextView。
可以看出Restore过程和Save过程完全相同。