ViewModel 憑什麼能保存重建數據

打開官網,咱們能夠看到 ViewModel 的描述:html

The ViewModelclass allows data to survive configuration changes such as screen rotations.java


同時還給出了 ViewModel 的生命週期圖:
android

image.png

經過該圖咱們能夠很清晰的看到,ViewModel 與 Activity 的生命週期幾乎是一致的,但這並無引發個人關注,我在乎的是描述中的那句話, 在發生屏幕旋轉時,ViewModel 實例依然存在 ,爲了驗證這句話,我寫了一個小 demo 來佐證,示例 demo 以下:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val mainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        Log.e("TAG", "onCreate:$mainViewModel")
 }
  override fun onSaveInstanceState(outState: Bundle?) {
        super.onSaveInstanceState(outState)
        Log.e("TAG", "onSaveInstanceState:")
 }

 override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
        super.onRestoreInstanceState(savedInstanceState)
        Log.e("TAG", "onRestoreInstanceState:")
 }
複製代碼

操做步驟很簡單,就是操做屏幕旋轉,佐證下 ViewModel 實例是否還是 Activity 旋轉屏幕以前的實例,打印結果以下:app

2019-08-13 21:45:55.381  E/TAG: onCreate:com.codelang.jetpack.MainViewModel@b02354
2019-08-13 21:46:06.435  E/TAG: onSaveInstanceState:
2019-08-13 21:46:06.608  E/TAG: onCreate:com.codelang.jetpack.MainViewModel@b02354
2019-08-13 21:46:06.617  E/TAG: onRestoreInstanceState:
複製代碼

經過日誌咱們能夠看到,在發生屏幕旋轉時,旋轉以前的 MainViewModel 與旋轉後的 MainViewModel 內存地址一致,驗證了官網對 ViewModel 描述的正確性。ide

咱們知道,在屏幕發生旋轉時,整個 Activity 都會被銷燬和重建,與之所對應的對象和變量也都會被從新初始化,但 ViewModel 的實例並無受之影響,重建以後還是以前的實例,難道 ViewModel 實例被 Activity 以外的某個變量持有?帶着這樣的疑問咱們來跟蹤 ViewModel 的源碼,看看是如何作到這點的。this

分析

初始化 ViewModel

咱們來看下 ViewModel 對象的初始化:google

val mainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)spa

一、跟蹤 ViewModelProviders.of日誌

public static ViewModelProvider of(@NonNull FragmentActivity activity,@Nullable Factory factory) {
    Application application = checkApplication(activity);
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    // 關鍵點:activity.getViewModelStore()
    return new ViewModelProvider(activity.getViewModelStore(), factory);
}
複製代碼

默認會初始化一個 factory,該 factory 是一個反射初始化 MainViewModel::class.java 的工廠類,關鍵點咱們稍後再說,ViewModelProvider 的初始化須要傳入 activity 的 ViewModelStore 和 factory。code

二、跟蹤 get(MainViewModel::class.java)

public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        ...
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
  } 

  public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        // 一、 
        ViewModel viewModel = mViewModelStore.get(key);
		// 二、
        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } 
        ...
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            //三、
            viewModel = (mFactory).create(modelClass);
        }
        //四、
        mViewModelStore.put(key, viewModel);
        //五、
        return (T) viewModel;
   }
複製代碼

序列號解釋:

  1. 根據 key 從 Activity 的 ViewModelStore 中取出 ViewModel
  2. 判斷取出的 ViewModel 是否與 get 的 ViewModel 實例一致,是的話,直接返回 ViewModel 對象
  3. 經過 factory 來反射初始化 ViewModel 對象
  4. 將 ViewModel 存到 Activity 的 ViewModelStore 中
  5. 返回 ViewModel 對象

從整個過程來看,ViewModelStore 這個對象很是重要,ViewModel 的存儲與獲取都與和他有關,Activity 銷燬重建也是從 ViewModelStore 中獲取 ViewModel 的實例,而且這個實例一直是同一個對象。

獲取 ViewModelStore

一、咱們來講說關鍵點: activity.getViewModelStore()

FragmentActivity.class

static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
        FragmentManagerNonConfig fragments;
} 

public ViewModelStore getViewModelStore() {
        //一、
        if (mViewModelStore == null) {
            //二、
            NonConfigurationInstances nc =
                     //三、
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            //三、
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
 }
複製代碼

序列號解釋:

  1. 檢查全局變量 mViewModelStore 是否爲空
  2. NonConfigurationInstances 是 FragmentActivity 的一個靜態內部類
  3. 獲取 NonConfigurationInstances 對象
  4. 若是 viewModelStore 仍爲空,則建立一個 ViewModelStore

二、咱們來看下 getLastNonConfigurationInstance 是如何拿到 FragmentActivity.NonConfigurationInstances

Activity.class

static final class NonConfigurationInstances {
        Object activity;
        HashMap<String, Object> children;
        FragmentManagerNonConfig fragments;
        ArrayMap<String, LoaderManager> loaders;
        VoiceInteractor voiceInteractor;
} 

public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
 }
複製代碼

mLastNonConfigurationInstances 是 Activity 的一個叫 NonConfigurationInstances 的靜態內部類。

三、咱們來看下 mLastNonConfigurationInstances 是如何初始化的

Activity.class

final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback) {
 ...
 mLastNonConfigurationInstances = lastNonConfigurationInstances;  
 ... 
}
     
複製代碼

在 Activity attach 的時候被外部傳入進來,那麼,是誰觸發的 attach 呢?咱們知道,Activity 的整個建立都是 ActivityThread 來操做的。

四、咱們來看下 ActivityThread

咱們找到觸發 Activity.attach() 的地方

ActivityThread.class

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
         ActivityInfo aInfo = r.activityInfo;
         ...
         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);
		     ...
         mActivities.put(r.token, r);
         ...
 }
複製代碼

咱們注意到 r.lastNonConfigurationInstances 這個對象,這個對象是從 ActivityClientRecord  中取出來的,而且又將 ActivityClientRecord 存儲到了 ActivityThread 的全局變量 mActivities 中。

五、咱們來看看 ActivityClientRecord 是怎麼來的
下面簡潔下調用鏈:

performLaunchActivity -> handleLaunchActivity -> handleRelaunchActivityInner -> handleRelaunchActivity

最終走到了 handleRelaunchActivity 方法:

@Override
  public void handleRelaunchActivity(ActivityClientRecord tmp, PendingTransactionActions pendingActions) {
      ...
      //一、經過 Activity 的 token 獲取對應的 ActivityClientRecord
      ActivityClientRecord r = mActivities.get(tmp.token);
      ...
      handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");  
      ...
  }
複製代碼

最終,咱們找到了 ActivityClientRecord 的來源,所以,整個獲取 ViewModelStore 的調用鏈就出來了:

image.png

咱們再回到 Activity 的 getViewModelStore 方法,只有在 ViewModelStore 爲空的狀況下才會走這個調用鏈,這麼作的目的是爲了不頻繁走調用鏈才能拿到 ViewModel 的問題,但重建後,ViewModelStore 確定是爲空的,因此,確定是有一個地方只取一次調用鏈拿到 ViewModelStore 對象,結果就是在 onCreate 方法中

FragmentActivity.class

protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
		
        NonConfigurationInstances nc =
                // 拿到調用鏈的 ViewModelStore 對象
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
            mViewModelStore = nc.viewModelStore;
        }
        ...
複製代碼

既然有獲取,那必然就會有存儲的過程,獲取的源頭是在 ActivityThread 的 mActivities 中,那存儲必然也是在 mActivities 中。

存儲 ViewModelStore

經過 mActivities.get 的方式,在 ActivityThread 中查找相關引用:

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    ...
     if (getNonConfigInstance) {
         try {
             // 一、
             r.lastNonConfigurationInstances
                 // 二、
                 = r.activity.retainNonConfigurationInstances();
         } catch (Exception e) {
            ...
         }
     }
    ...
        
複製代碼

序列號解釋:

  1. 該處就是 Activity attach 時傳入的  Activity.NonConfigurationInstances 對象
  2. 獲取 Activity.NonConfigurationInstances 對象

仔細看方法,原來是 performDestroyActivity ,也就是說,在重建 Activity 前,會將 ViewModelStore 給保存起來給 ActivityClientRecord,ActivityClientRecord 是存放在 ActivityThread 的全局 mActivities 集合中的,等到重建後,再從 mActivities 中取出 ActivityClientRecord,再把 ViewModelStore 經過 Activity 的 attach 方法再傳入,這也印證了咱們的 demo 和官方的示例圖,不過咱們仍是得來看看 r.activity.retainNonConfigurationInstances(); 是怎麼存儲起來的

一、activity.retainNonConfigurationInstances

Activity.class

NonConfigurationInstances retainNonConfigurationInstances() {
        //一、
        Object activity = onRetainNonConfigurationInstance();
        HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
        ...
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.activity = activity;
        nci.children = children;
        nci.fragments = fragments;
        nci.loaders = loaders;
        ...
        return nci;
    }
複製代碼

retainNonConfigurationInstances 是一個建立 Activity.NonConfigurationInstances 的過程,根據上述調用鏈圖可知,Activity.NonConfigurationInstances 會引用 FragmentActivity.NonConfigurationInstances 對象,引用部分在序列號 1 處 onRetainNonConfigurationInstance 方法

二、onRetainNonConfigurationInstance

FragmentActivity.class

@Override
 public final Object onRetainNonConfigurationInstance() {
        ...
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        // 一、
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
  }
複製代碼

到這裏就很清楚了,FragmentActivity.NonConfigurationInstances 會持有 mViewModelStore 對象進行存儲。

咱們來更改下官方的示例圖:

image.png
相關文章
相關標籤/搜索