在Android
的组件Activity
中,有这样一对方法: onSaveInstanceeState
和 onRestoreInstanceState
这两对方法,可以让我在Activiy
被异常销毁时,保存状态;以及在Activity
重建时,恢复状态。
比如:当我们在输入框中输入了内容,此时因为种种原因,将App退至了后台。这个处于后台的App很有可能因为内存不足、其他配置,被系统杀死。
当我们恢复这个页的时候,希望它能够保存住我们原来输入的内容。
除了,我们自己手动保存,也可以利用系统的onSaveInstanceState
和onRestoreInstanceState
那么,在Android已有的系统中,是如何做的呢?
我们查阅EditText,发现它的父类TextView做了保存状态与恢复状态的处理,但是根据条件(freezesText || hasSelection
)做了保存与恢复,如果只用TextView用于展示,并不会触发保存与恢复。
TextView的状态保存与恢复。
// 保存状态
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
// Save state if we are forced to
final boolean freezesText = getFreezesText();
boolean hasSelection = false;
int start = -1;
int end = -1;
if (mText != null) {
start = getSelectionStart();
end = getSelectionEnd();
if (start >= 0 || end >= 0) {
// Or save state if there is a selection
hasSelection = true;
}
}
// 满足此条件时,才进行保存数据
if (freezesText || hasSelection) {
SavedState ss = new SavedState(superState);
if (freezesText) {
if (mText instanceof Spanned) {
final Spannable sp = new SpannableStringBuilder(mText);
if (mEditor != null) {
removeMisspelledSpans(sp);
sp.removeSpan(mEditor.mSuggestionRangeSpan);
}
ss.text = sp;
} else {
ss.text = mText.toString();
}
}
if (hasSelection) {
// XXX Should also save the current scroll position!
ss.selStart = start;
ss.selEnd = end;
}
if (isFocused() && start >= 0 && end >= 0) {
ss.frozenWithFocus = true;
}
ss.error = getError();
if (mEditor != null) {
ss.editorState = mEditor.saveInstanceState();
}
return ss;
}
return superState;
}
// 恢复状态
@Override
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 (mSpannable != null) {
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(mSpannable, 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);
}
}
onSaveInstanceState&onRestoreInstanceState的执行时机
这两个函数在什么情况下使用?比如开发者模式中开启了不保留活动、屏幕方向发生改变等原因,导致Activity(视图)被销毁或重建时,会执行。
被销毁时,执行onSaveInstanceState
重建时,执行onRestoreInstanceState
当然,这两个函数的执行也是有一些条件的,比如,View必须指定了Id,Id在整个视图(PhoneWindow)内必须唯一,如果不唯一则会在恢复状态时报错(保存时不会报错)。
在View的默认实现中,如果发现id一样,则会在恢复状态时报错。
@CallSuper
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 but "
+ "received " + state.getClass().toString() + " instead. This usually happens "
+ "when two views of different type have the same id in the same hierarchy. "
+ "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure "
+ "other views do not use the same id.");
}
// …… 省略剩余代码
}
我们在自定义View和使用第三方控件等情况下,需要合理处理这两个函数,否则会导致崩溃。
这里附一张InstanceState的执行时机图:
save的状态保存在哪里?restore的数据怎么取?
在View中,执行了onSaveInstanceState()
后,View会将获取到的结果,保存在一个SparseArray中,这个SparseArray是从最根部的PhoneWindow中传递进来的,整个PhoneWindow中只有一份。
view中执行,container.put(mID, state);
就会把自己要保存的数据放置到SparseArray中。
如果视图中存在id相同的View,那么后面保存的替换掉之前保存的。
在恢复数据时,也是从SparseArray中以当前View的Id为可以,获取保存的数据。获取到就是上一次保存时最后存储的数据。
Parcelable state = container.get(mID);
if (state != null) {
// ……
onRestoreInstanceState(state);
// ……
}