Android原始碼分析之備忘錄模式

或許北音發表於2019-02-27

前言

剛看到Java設計模式中的備忘錄模式,心思一轉,就想到了Android開發中Activity的兩個重要的方法onSaveInstanceState和onRestoreInstanceState,這兩個方法能夠保證我們在開發應用時,遇到未知問題,導致Activity非正常退出時候,在Activity在隨後時間被系統殺死之前會回撥這兩個方法,儲存記錄Activity相關的資訊,以便在下次返回Activity的時候能夠恢復這些資料。

Android原始碼分析

之前文章講到了Java設計模式中的備忘錄模式,今天就根據這個模式來看看Android中是如何實現備忘錄模式的(原始碼基於Android6.0)。

  • 首先來看一下Activity的onSaveInstanceState方法

      final void performSaveInstanceState(Bundle outState) {
          onSaveInstanceState(outState);
          saveManagedDialogs(outState);
          mActivityTransitionState.saveState(outState);
          if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
      }複製程式碼
  • 由上面可以看出,Android6.0原始碼將onSaveInstanceState包含在了performSaveInstanceState中,具體的onSaveInstanceState方法如下

      protected void onSaveInstanceState(Bundle outState) {
          //1.儲存當前視窗的檢視樹狀態,呼叫的是windoe的實現類phonewindow的方法
          outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
          //2.儲存Fragments的狀態
          Parcelable p = mFragments.saveAllState();
          if (p != null) {
              outState.putParcelable(FRAGMENTS_TAG, p);
          }
          //3.如果使用者設定了Activity的ActivityLifecycleCallbacks
          //那麼呼叫這些ActivityLifecycleCallbacks的onSaveInstanceState進行儲存
          getApplication().dispatchActivitySaveInstanceState(this, outState);
      }複製程式碼

    上面的方法分為三部分:
    (1)儲存視窗的檢視樹的狀態
    (2)儲存fragment的狀態
    (3)呼叫Activity的ActivityLifecycleCallbacks的onSaveInstanceState方法進行儲存狀態

  • 下面我們來看第一步,Window的saveHierarchyState由其實現類PhoneWindow的saveHierarchyState方法實現,具體程式碼如下:

      @Override
      public Bundle saveHierarchyState() {
          Bundle outState = new Bundle();
          if (mContentParent == null) {
              return outState;
          }
      SparseArray<Parcelable> states = new SparseArray<Parcelable>();
      // 1.呼叫mContentParent的saveHierarchyState方法,儲存當前檢視內容,這裡儲存著整個檢視樹的內容
      mContentParent.saveHierarchyState(states);
      // 將檢視樹結構放到outState中
      outState.putSparseParcelableArray(VIEWS_TAG, states);
      // 2.儲存當前介面的中獲取的焦點資訊
      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.");
              }
          }
      }
      // 3.儲存整個皮膚的狀態
      SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();
      savePanelState(panelStates);
      if (panelStates.size() > 0) {
          outState.putSparseParcelableArray(PANELS_TAG, panelStates);
      }
      // 4.儲存actionbar的狀態
      if (mDecorContentParent != null) {
          SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
          mDecorContentParent.saveToolbarHierarchyState(actionBarStates);
          outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
      }
      return outState;複製程式碼

    }

  • 上面方法中分別儲存了頁面的主要資訊,包括UI,actionbar的相關資訊。其中這個mContentParent是我們通過Activity的setContentView方法設定的內容檢視,它是整個內容檢視的根節點,儲存了它的層級結構中的view狀態,就相當於儲存了使用者介面的狀態。它是一個ViewGroup物件,但這個saveHierarchyState方法是View的一個方法,如下:

      public void saveHierarchyState(SparseArray<Parcelable> container) {
          // ViewGroup呼叫的父類View的方法,其父類View用呼叫此方法儲存狀態
          dispatchSaveInstanceState(container);
      }複製程式碼
  • View方法沒有直接儲存,而是呼叫dispatchSaveInstanceState方法間接儲存,這裡便是真正儲存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獲取自身狀態(View的預設狀態空)
              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.以key為id,state為value儲存到container中
                  container.put(mID, state);
              }
          }
      }複製程式碼
  • View自身的onSaveInstanceState方法

      @CallSuper
      protected Parcelable onSaveInstanceState() {
          mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
          // View類預設的儲存狀態為空
          if (mStartActivityRequestWho != null) {
              BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
              state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
              return state;
          }
          return BaseSavedState.EMPTY_STATE;
      }複製程式碼
  • 在View類中的saveHirearchyState方法中呼叫dispatchSaveInstanceState方法來儲存自身的狀態,而ViewGroup則覆寫了dispatchSaveInstanceState方法來儲存自身以及子檢視的狀態,如下:

      @Override
      protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
          //ViewGroup覆寫View的dispatchSaveInstanceState方法,儲存自身的狀態
          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);
              }
          }
      }複製程式碼
  • 在ViewGroup的dispatchSaveInstanceState方法中,首先呼叫super.dispatchSaveInstanceState儲存自身的狀態,然後遍歷子檢視,呼叫子檢視的dispatchSaveInstanceState方法來儲存它們的狀態。其中在View的具體儲存過程中我們可以看出,只有View設定了唯一性的id,View才會進行記錄。此外,在View中我們看到返回的是空狀態,這意味著我們需要儲存View狀態時,需要覆寫onSaveInstanceState方法,將要儲存的資料放到Parcelable並將它返回。這裡我們可以看一下TextView的實現過程:

      @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;
              }
          }
          //儲存TextView的start,end以及文字內容
          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();
              if (mEditor != null) {
                  ss.editorState = mEditor.saveInstanceState();
              }
              return ss;
          }
          return superState;
      }複製程式碼
  • 呼叫View的onSaveInstanceState方法後就得到了View要儲存的資料,到這裡便執行到了第三步。至此,經過一層層的遍歷,整個內容檢視樹便儲存下來了。

      if (state != null) {
          // Log.i("View", "Freezing #" + Integer.toHexString(mID)
          // + ": " + state);
          // 3.以key為id,state為value儲存到container中
          container.put(mID, state);
      }複製程式碼
  • 儲存完Window的檢視樹資訊後,便執行儲存Fragment的狀態資訊、回退棧等。這個儲存Fragment的狀態資訊也是呼叫它的onSaveInstanceState方法,儲存Fragment中View檢視樹狀態,最好就是呼叫使用者設定的ActivityLifecycleCallbacks的onSaveInstanceState方法,讓使用者能夠再做一些額外的處理。到這裡,整個儲存過程就完成了。


  • 上面分析了Activity在未知狀態下銷燬前儲存的資訊,這些儲存的資訊都儲存在了Bundle資料中,那系統又是如何恢復資料的呢?在Activity被銷燬onStop方法執行之前,系統會呼叫ActivityThread的performStopActivity方法,如下:

      //包含stop方法
      final void performStopActivity(IBinder token, boolean saveState) {
          // 獲取ActivityClientRecord物件
          ActivityClientRecord r = mActivities.get(token);
          // 執行方法,saveState就是表示是否要儲存的狀態
          performStopActivityInner(r, null, false, saveState);
      }
    
      private void performStopActivityInner(ActivityClientRecord r,
          StopInfo info, boolean keepShown, boolean saveState) {
          if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
          if (r != null) {
              if (!keepShown && r.stopped) {
                  //省略
              }
              if (info != null) {
                  try {
                      info.description = r.activity.onCreateDescription();
                  } catch (Exception e) {
                      //省略
                  }
              }
              // 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的OnStop函式
                      r.activity.performStop();
                  } catch (Exception e) {
                     //省略
                  }
                  r.stopped = true;
              }
              r.paused = true;
          }
      }
    
      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 {
              // 呼叫mInstrumentation的callActivityOnSaveInstanceState函式
              // 實際上呼叫的是Activity的callActivityOnSaveInstanceState函式
              mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
          }
       }
    
      public void callActivityOnSaveInstanceState(Activity activity, Bundle outState) {
          activity.performSaveInstanceState(outState);
      }複製程式碼
  • 在performStopActivity中,通過token獲取一個ActivityClientRecord物件,這個物件就儲存了Acvtivyt的資訊。之後呼叫performStopActivityInner,其方法執行大致分為4部:
    (1)判斷是否需要儲存Activtiy的狀態
    (2)如果需要儲存,呼叫onSaveInstanceState方法
    (3)將資訊儲存到ActivityClientRecord物件的stat欄位中
    (4)呼叫Actvity的onStop方法

  • 由上可以知道,在onStop方法執行之前,系統會根據情況選擇是否儲存Actvity的狀態,並且將這些狀態儲存在mActivities中,這個mActivities維護了一個Activity的資訊表,當Activity重啟時候,會從mActivities中查詢到對應的ActivityClientRecord,如果有資訊,則呼叫Activity的onResoreInstanceState方法,在ActivityThread的performLaunchActivity方法中,具體如下:

       private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
          // 省略
          Activity activity = null;
          try {
              java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
              // 1.構建Activity
              activity = mInstrumentation.newActivity(
                      cl, component.getClassName(), r.intent);
              //省略
          } catch (Exception e) {
              //省略
          }
          try {
              // 2.建立一個Application
              Application app = r.packageInfo.makeApplication(false, mInstrumentation);
              if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
              if (activity != null) {
                  // 3.建立Context,型別為ContextImpl
                  Context appContext = createBaseContextForActivity(r, activity);
                  CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                  Configuration config = new Configuration(mCompatConfiguration);
                  if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                          + r.activityInfo.name + " with config " + config);
                  // 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);
    
                  if (customIntent != null) {
                      activity.mIntent = customIntent;
                  }
                  r.lastNonConfigurationInstances = null;
                  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) {
                      //省略
                  }
                  r.activity = activity;
                  r.stopped = true;
                  if (!r.activity.mFinished) {
                      activity.performStart();
                      r.stopped = false;
                  }
                  // 6.呼叫Actvity的OnRestoreInstanceState恢復初始狀態
                  if (!r.activity.mFinished) {
                     //省略
                  }
                  if (!r.activity.mFinished) {
                     //省略
                  }
              }
              r.paused = true;
              // 將Activity的資訊記錄物件存到mActivities中
              mActivities.put(r.token, r);
          } catch (SuperNotCalledException e) {
              throw e;
    
          } catch (Exception e) {
             //省略
          }
    
          return activity;
      }複製程式碼
  • 上面可以看出,系統會判斷ActivityClientRecord物件的state是否為空,不為空則通過Activity的onSaveInstanceState獲取其UI狀態資訊,通過這些資訊傳遞給Activity的onCreate方法,使得使用者可以在onCreate方法中恢復UI上的狀態。

總結
以上的分析可以看出,在整個過程中,Activity扮演了CareTaker角色,負責儲存、恢復Ui的狀態資訊;Activity、Fragment、View等物件為Originator角色,也就是扮演儲存狀態的物件;Memoto則是有Bundle類扮演,單純的負責資料的支援(容器)。Activit在異常退出時,會根據情況,選擇是否需要儲存相關狀態資訊,在重新啟動時候,也會根據ActivityClientRecord物件是否儲存Activity的狀態,選擇性的恢復其初始狀態。這樣下來,就保證了在非正常情況下銷燬Activity時不會丟失資料,很好的提升使用者體驗。

深度擴充

備忘錄設計模式實戰解析

參考文獻:Android原始碼設計模式解析與實戰

相關文章