在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);// ……
}