Android 設計模式 - 備忘錄模式
備忘錄模式
備忘錄這種設計模式用來儲存一個物件的屬性備份,它的uml圖如下

通常我們會有需求要求儲存一個物件的一些屬性表現為另外一個物件的形式作為備份,如上面的uml類圖中Originator這個物件可以建立一個Memoto用來儲存Originator的一些屬性(成員變數)這個物件可以儲存到Creataker中.
一個簡單的需求展示如何使用該模式.
通常我們玩遊戲的時候都會去存檔,在這裡就可以使用備忘錄模式來完成存檔的過程:
show code
public class Memoto { public int mCheckPoint = 1; public int mLifeValue = 100; public String mWeapon = "沙漠之鷹"; @Override public String toString() { return "Memoto{" + "mCheckPoint=" + mCheckPoint + ", mLifeValue=" + mLifeValue + ", mWeapon='" + mWeapon + '\'' + '}'; } }
public class CallOfDuty { private int mCheckPoint = 1; private int mLifeValue = 100; private String mWeapon = "沙漠之鷹"; public void play(){ System.out.println("玩遊戲 : "+String.format("第%d關",mCheckPoint)+" 奮戰殺敵中"); mLifeValue -= 10; System.out.println("進度升級啦"); mLifeValue++; System.out.println("到達 "+String.format("第%d關",mCheckPoint)+" 關"); } //退出遊戲 public void quit(){ System.out.println("-----------"); System.out.println("退出前的遊戲屬性 : "+this.toString()); System.out.println("退出遊戲"); System.out.println("-----------"); } /** * 建立備忘錄 */ public Memoto createMemoto(){ Memoto memoto = new Memoto(); memoto.mCheckPoint = mCheckPoint; memoto.mLifeValue = mLifeValue; memoto.mWeapon = mWeapon; return memoto; } public void restore(Memoto memoto){ mCheckPoint = memoto.mCheckPoint; mLifeValue = memoto.mLifeValue; mWeapon = memoto.mWeapon; } @Override public String toString() { return "CallOfDuty{" + "mCheckPoint=" + mCheckPoint + ", mLifeValue=" + mLifeValue + ", mWeapon='" + mWeapon + '\'' + '}'; } }
public class Caretaker { private Memoto mMemoto;//備忘錄 /** * 存檔 */ public void archive(Memoto memoto){ this.mMemoto = memoto; } /** * 獲取存檔 */ public Memoto getMemoto(){ return mMemoto; } }
最後呼叫
public static void main(String[] args){ CallOfDuty game = new CallOfDuty(); game.play(); Caretaker caretaker = new Caretaker(); caretaker.archive(game.createMemoto()); game.quit(); CallOfDuty newGame = new CallOfDuty(); newGame.restore(caretaker.getMemoto()); }
這裡利用Caretaker來儲存CallOfDuty的狀態(CallOfDuty也就是uml中的Originator).
Android中的備忘錄模式
protected void onSaveInstanceState(Bundle outState) { //1.儲存當前視窗的檢視樹狀態 outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState()); outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId); //2.儲存Fragment狀態 Parcelable p = mFragments.saveAllState(); if (p != null) { outState.putParcelable(FRAGMENTS_TAG, p); } if (mAutoFillResetNeeded) { outState.putBoolean(AUTOFILL_RESET_NEEDED, true); getAutofillManager().onSaveInstanceState(outState); } //3.如果使用者還設定了Activity的ActivityLifeCycleCallBacks那麼呼叫ActivityLifeCycleCallBacks的onSaveInstanceState進行儲存狀態 getApplication().dispatchActivitySaveInstanceState(this, outState); }
從上面程式碼來看onSaveInstanceState主要做了下面三個操作
(1)儲存視窗的檢視樹狀態
(2)儲存Fragment的狀態
(3)呼叫Activity的ActivityLifeCycleCallBacks的onSaveInstanceState進行儲存狀
我們先來看看第一步中儲存視窗的檢視樹狀態,Activity中的Window是PhoneWindow,我們看PhoneWindow中saveHierarchyState是如何實現的.
@Override public Bundle saveHierarchyState() { Bundle outState = new Bundle(); if (mContentParent == null) { return outState; } //通過SparseArray來儲存,這相當於一個key為整形的map SparseArray<Parcelable> states = new SparseArray<Parcelable>(); //呼叫mContentParent的saveHierarchyState方法,這個mContentParent就是呼叫Activity的setContentView函式設定的內容檢視,它是內容檢視的根節點,在這裡儲存整棵檢視樹的結構. mContentParent.saveHierarchyState(states); //將檢視樹放在outState中 outState.putSparseParcelableArray(VIEWS_TAG, states); // 儲存當前獲取焦點的view. final View focusedView = mContentParent.findFocus(); //持有焦點的view必須設定id,否則重新進入該介面時不會恢復其焦點狀態 if (focusedView != null && focusedView.getId() != View.NO_ID) { outState.putInt(FOCUSED_ID_TAG, focusedView.getId()); } //儲存整個面板的狀態 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這一個過程,mContentParent就是在Activity中setContentView設定的內容檢視,它是整個內容檢視的根節點,儲存它的層級結構view狀態也就儲存了使用者介面的狀態。mContentParent是一個viewgroup物件但是saveHierarchyState並不是viewgroup的物件,它是在view中的。
public void saveHierarchyState(SparseArray<Parcelable> container) { dispatchSaveInstanceState(container); }
真正儲存view狀態的函式
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { //1 注意這裡view中如果沒有id,那麼這個view狀態將不會被儲存. if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) { mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED; //2 呼叫onSaveInstanceState函式獲取自身的狀態 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); //3 將自身狀態存放在container中,key為id , value為自身的狀態. container.put(mID, state); } } } @CallSuper @Nullable protected Parcelable onSaveInstanceState() { //view本身預設返回 return BaseSavedState.EMPTY_STATE; }
viewgroup還重寫了dispatchSaveInstanceState函式,它的dispatchSaveInstanceState函式如下:
@Override 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); } } }
這裡我們可以看到dispatchSaveInstanceState實現中首先會呼叫 super.dispatchSaveInstanceState去儲存它自身的狀態,然後會呼叫它子view的dispatchSaveInstanceState,這裡其實有點像事件的向下傳遞過程,它是一個樹形的結構.
上面程式碼中我們還需要注意到註釋1的地方,它是指如果一個view沒有id它是不會被儲存狀態的,這裡的id實際上是我們在xml中定義的id.用來標示view的唯一性.
下面我們來分析一下TextView的onSaveInstanceState過程.
@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; }
可以看到它返回了一個SavedState物件,SavedState是實現了Parcelable介面,此時正好返回到註釋2這個位置. 然後被put到container中.
在儲存了Activity的狀態後,接下來就是Fragment的狀態了, 這個Fragment狀態的儲存過程也是呼叫onSaveInstanceState方法,過程和Activity其實很類似。 最後就是呼叫使用者設定的AcvitityLifecycleCallbacks的onSaveInstanceState方法,讓使用者也再做一些額外的處理,至此,整個儲存過程就完成了.
經過一層一層的迴圈,最後的儲存物件都放在了Bundle中,那麼這個Bundle是儲存到哪的呢?
onSaveInstanceState是在Activity onStop()之前, Activity的onStop方法在ActivityThread的performStopActivity函式中,相關程式碼如下:
final void performStopActivity(IBinder token, boolean saveState, String reason) { //獲取ActivityClientRecord ActivityClientRecord r = mActivities.get(token); //執行performStopActivityInner,saveState標示是否要儲存狀態 performStopActivityInner(r, null, false, saveState, reason); }
private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShown, boolean saveState, String reason) { // Next have the activity save its current state and managed dialogs... if (!r.activity.mFinished && saveState) { if (r.state == null) { //執行Activity的OnSaveInstanceState函式 callCallActivityOnSaveInstanceState(r); } } if (!keepShown) { try { // Now we are idle. //執行Activity的OnSaveInstanceState函式 r.activity.performStop(false /*preserveWindow*/); } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { throw new RuntimeException( "Unable to stop activity " + r.intent.getComponent().toShortString() + ": " + e.toString(), e); } } r.stopped = true; EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(), r.activity.getComponentName().getClassName(), reason); } } }
private void callCallActivityOnSaveInstanceState(ActivityClientRecord r) { r.state = new Bundle(); r.state.setAllowFds(false); if (r.isPersistable()) { r.persistentState = new PersistableBundle(); mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state, r.persistentState); } else { //呼叫Instrumentation的callActivityOnSaveInstanceState函式,實際上會呼叫Activity的onSaveInstanceState mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state); } }
performStopActivity中通過token獲得一個ActivityClientRecord物件,這個物件就是用來儲存儲存view狀態的Bundle物件. performStopActivityInner這個函式主要做了如下操作:
(1),判斷是否要儲存Activity狀態
(2),如果需要儲存Activity狀態,呼叫onSaveInstanceState函式
(3),將儲存的資訊放在ActivityClientRecord的state欄位中
(4),Activity的onStop方法
這裡我們將view狀態儲存到ActivityClientRecord中,ActivityClientRecord是放在mActivities中的,mActivities維護了一個Activity資訊表,當Activity重啟後會查詢mActivities中對應的ActivityClientRecord資訊,那麼則會呼叫Activity的onRestoreInstanceState函式.
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { //程式碼省略 //1 建立Context,型別為ContextImpl ContextImpl appContext = createBaseContextForActivity(r); Activity activity = null; try { java.lang.ClassLoader cl = appContext.getClassLoader(); //2 構建Activity activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); //程式碼省略 } catch (Exception e) { } try { //3 建立一個Application Application app = r.packageInfo.makeApplication(false, mInstrumentation); if (localLOGV) Slog.v(TAG, "Performing launch of " + r); if (localLOGV) Slog.v( TAG, r + ": app=" + app + ", appName=" + app.getPackageName() + ", pkg=" + r.packageInfo.getPackageName() + ", comp=" + r.intent.getComponent().toShortString() + ", dir=" + r.packageInfo.getAppDir()); if (activity != null) { CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mCompatConfiguration); if (r.overrideConfig != null) { config.updateFrom(r.overrideConfig); } if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity " + r.activityInfo.name + " with config " + config); Window window = null; if (r.mPendingRemoveWindow != null && r.mPreserveWindow) { window = r.mPendingRemoveWindow; r.mPendingRemoveWindow = null; r.mPendingRemoveWindowManager = null; } appContext.setOuterContext(activity); //4 關聯appContext, Application物件到Activity中 activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor, window, r.configCallback); if (customIntent != null) { activity.mIntent = customIntent; } r.lastNonConfigurationInstances = null; checkAndBlockForNetworkAccess(); activity.mStartedActivity = false; int theme = r.activityInfo.getThemeResource(); if (theme != 0) { activity.setTheme(theme); } activity.mCalled = false; //5 呼叫Activity的OnCreate方法. if (r.isPersistable()) { mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); } else { mInstrumentation.callActivityOnCreate(activity, r.state); } if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + r.intent.getComponent().toShortString() + " did not call through to super.onCreate()"); } r.activity = activity; r.stopped = true; if (!r.activity.mFinished) { activity.performStart(); r.stopped = false; } if (!r.activity.mFinished) { if (r.isPersistable()) { if (r.state != null || r.persistentState != null) { //6 呼叫Activity的OnRestoreInstanceState恢復狀態 mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state, r.persistentState); } } else if (r.state != null) { mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state); } } } r.paused = true; //7 將Activity資訊記錄在mActivities中 mActivities.put(r.token, r); } catch (SuperNotCalledException e) { // } catch (Exception e) { // } return activity; }
註釋6處,系統會判斷ActivityClientRecord物件中的state是否為空,如果不為空則說明了儲存了該Activity狀態,此時會通過OnRestoreInstanceState恢復儲存在ActivityClientRecord中的ui狀態資訊.
在這個過程中
Activity扮演Creataker角色
Bundle扮演Memoto角色
View,ViewGroup,Fragment扮演Originator角色
總結:
備忘錄模式通過儲存一個物件成員變數的快照,在合適的時候恢復,從而達到一個備份的效果.
【附錄】

資料圖
需要資料的朋友可以加入Android架構交流QQ群聊:513088520
點選連結加入群聊【Android移動架構總群】: 加入群聊
獲取免費學習視訊,學習大綱另外還有像高階UI、效能優化、架構師課程、NDK、混合式開發(ReactNative+Weex)等Android高階開發資料免費分享。