Activitty狀態保存onSaveInstanceState和恢復onRestoreInstanceState

標籤(空格分隔):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方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章