標籤(空格分隔):Actiivty 狀態保存和恢復
當我們在前臺和後臺切換,或者橫豎屏切換的時候,Activity會被重新創建,Android系統默認是幫我們自動保存和恢復了和這個Activity有關的一些狀態,涉及到ActivityThread Ams的調度機制,這裏暫時不要去case,我們主要看包括界面ui上view的狀態,如何保證能夠恢復回來。我們都知道,保存的時候會調用onSaveInstanceState保存一些數據到bundle中,恢復的時候會調用onRestoreInstanceState來恢復。那麼這套機制又是什麼樣子的?保存的是哪些信息?又是以一個什麼流程去恢復的。
1.Activity.onSaveInstanceState
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);
}
這個方法中,outState是在ActiivtyThread中新出來的,此時將mWindow的state保存到了bundle中。然後獲取了當前應用的Application對象,分發交給ActivityLifecycleCallbacks去處理,等於Application預留的接口,這裏不用去關注,除非有主動去註冊。下面分析mWindow.saveHierarchyState的具體實現,mWindow對象是一個PhoneWindow對象。
2.PhoneWindow.saveHierarchyState
@Override
public Bundle saveHierarchyState() {
Bundle outState = new Bundle();
if (mContentParent == null) {
return outState;
}
SparseArray<Parcelable> states = new SparseArray<Parcelable>();
mContentParent.saveHierarchyState(states);
outState.putSparseParcelableArray(VIEWS_TAG, states);
// 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.");
}
}
}
// save the panels
SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();
savePanelState(panelStates);
if (panelStates.size() > 0) {
outState.putSparseParcelableArray(PANELS_TAG, panelStates);
}
if (mDecorContentParent != null) {
SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
mDecorContentParent.saveToolbarHierarchyState(actionBarStates);
outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
}
return outState;
}
這個方法中,mContentParent對應的是頂級視圖DecorView下的child(id爲 com.android.internal.R.id.content),如果它爲空,直接返回。接下來分別是創建了三個SparseArray<Parcelable>,它是android提供的一個類似HashMap的類,效率比Map高,不過key必須爲Interger,分別用來保存view ,panels和actionbar state的狀態,構建了整個window對象的state。這裏我們着重分析view層的state。因爲其他兩個也是類似的效果。分析mContentParent.saveHierarchyState的實現。mContentParent是一個ViewGroup對象.
3.View.saveHierarchyState(SparseArray)
public void saveHierarchyState(SparseArray<Parcelable> container) {
dispatchSaveInstanceState(container);
}
View和ViewGroup均由實現這個dispatchSaveInstanceState方法,類似組合模式我們見的比較多,可以推測ViewGroup中會繼續分發給的child去保存狀態,下面分別看看view和viewgroup中的實現,看是否是這樣一回事。
View中的dispatchSaveInstanceState實現:
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號是否有設置,以及根據mViewFlags來判斷是否需要保存,一般來說如果這個view沒有被設置id,系統會認爲你根本就沒有進行操作,會認爲不需要去保存狀態,當然這裏只是系統給出的一個規則,不用去糾結好還是不好,實際使用過程中我們需要去遵守就好,接下來回調onSaveInstanceState去保存狀態,這個接口是給用戶根據不同的情況去客製化保存的一些信息,每一個view都有自己的實現方式,最後將保存的Parcelable對象存儲到container中,以mId–Parcelable鍵值對的形式存儲。
ViewGroup中的dispatchSaveInstanceState實現:
@Override
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);
}
}
}
看代碼實現,和我們推測的一致,分爲兩步,首先是super.dispatchRestoreInstanceState先保存自己的狀態,而後是遍歷去保存子child的state。以遞歸view樹的形式去執行。通過在ViewGroup以及android的主要容器LinearLayout,RelativeLayout中發現,有些類似的容器也是不需要保存的,只需要它的子child保存了一些信息即可在重新layout的時候去恢復ui顯示,因此對於容器來說,一般也就不需要保存狀態了。當然如果有特殊需要,比如動態改變了容器的顏色,padding值等等,那麼要想恢復也必須手動去處理保存和恢復了。
由於View是一個所有控件的父類,一般也極少直接去使用,我們來看看典型的TextView究竟是保存了哪些state
TextView.onSaveInstanceState
@Override
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();
return ss;
}
return superState;
}
從這段保存的code來看,對於TextView主要是保存了它的mText信息和selection信息,一般來說TextView是不滿足條件的,對於它的子類EditText可以滿足條件,主要工作就是保存了mText信息到了SavedState的text字段中。有了這個初步的印象,下面來看看恢復的流程。
4.Activity.onRestoreInstanceState
protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (mWindow != null) {
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
mWindow.restoreHierarchyState(windowState);
}
}
}
與保存類似,onRestoreInstanceState首先會通過Bundle拿到前面onSaveInstanceState存儲在WINDOW_HIERARCHY_TAG key中的bundle對象,那麼這兩個bundle對象是一個嗎?這裏我可以解釋下,這兩個bundle對象是同一個,每一個Activity創建的時候會同時新建一個ActivityClientRecord的binder對象,用以和遠程的ams進行通信,調度Actiivty的生命週期,同時在ActivityThread的ArrayMap<>[IBinder, ActivityClientRecord] mActivities對象中會保存這些ActivityClientRecord的信息。在ActivityClientRecord中的字段token就是遠程ams中標識一個Activity的標示符。mActivities中的key即是對應的這些token。這裏說了這麼多,就需要明白一點,當一個Activity恢復的時候,也就是relaunch的時候,會根據token去mActivities查找ActivityClientRecord,所以是會公用上一次的ActivityClientRecord對象的。這裏繼續跟蹤可以發現,onSaveInstanceState的Bunlde對象就是ActivityClientRecord對象中的state字段,恢復的時候也是取的state字段,一步步傳給onRestoreInstanceState去恢復。到這裏爲止,我們已經知道onSaveInstanceState和onRestoreInstanceState操作的是同一個Bundle對象,並且對應的就是ActivityClientRecord對象中的state字段。
取到onSaveInstanceState保存的信息之後,接下來分析mWindow.restoreHierarchyState的實現。
5.PhoneWindow.restoreHierarchyState(Bundle)
@Override
public void restoreHierarchyState(Bundle savedInstanceState) {
if (mContentParent == null) {
return;
}
SparseArray<Parcelable> savedStates
= savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
if (savedStates != null) {
mContentParent.restoreHierarchyState(savedStates);
}
// 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.");
}
}
// restore the panels
SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG);
if (panelStates != null) {
restorePanelState(panelStates);
}
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.");
}
}
}
這裏我們可以看到,和PhoneWindow.saveHierarchyState就是一個一一對應的關係,也是一個逆向的過程。首先是取到保存在bunlde對象VIEWS_TAG中的SparseArray對象,然後交給mContentParent.restoreHierarchyState(savedStates)去處理。
6.View.restoreHierarchyState(SparseArray)
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()");
}
}
}
}
ViewGroup中dispatchRestoreInstanceState實現如下:
@Override
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);
}
}
}
由此可以看到,在一個恢復case中,也會以遞歸view樹的形式去恢復,如果是ViewGroup首先會恢復自己的state,然後遞歸去恢復它的child。在View中dispatchRestoreInstanceState方法中,獲取保存在mID key中的Parcelable對象,回調onRestoreInstanceState接口,會去真正的執行restore state操作。這個接口中我們可以實現自己的業務邏輯。
下面具體來看我們上面TextView保存的邏輯和恢復的過程
TextView.onRestoreInstanceState
@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 (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() {
setError(error);
}
});
}
}
可以看到保存和恢復就是一個寫入和讀取的過程,整個過程也是一一對應的關係。是一個可逆的過程。將這些數據恢復之後,系統重新佈局,也就達到了恢復ui界面的目的。
至此,關於Activitty狀態保存onSaveInstanceState和恢復onRestoreInstanceState也就分析完了,主要是一個流程問題,整個過程中保存和恢復都是共享的同一份bundle實例。對於特殊情況,系統並未handle的case,我們需要自己去實現onSaveInstanceState和onRestoreInstanceState方法。