Android Architecture Component之:深層次理解ViewModel

上一篇我們從源碼層面上分析了LiveData的內部實現,今天我們來走進ViewModel的內心。

  1. 回顧如何獲取一個ViewModel實例:

    ViewModelProvider(this, MainViewModelFactory())[MainViewModel::class.java]

    那麼我們可以從ViewModelProvider的構造器入手。

  2. ViewModelProvider類:

    我們都是使用ComponentActivity或者Fragment作爲第一個參數,他們都實現了ViewModelStoreOwner接口,所以我們需要關注上面這個構造方法。我們這裏查看ComponentActivity類對於getViewModelStore()方法的實現:

  3. getLastNonConfigurationInstance()方法:

    /*

    Retrieve the non-configuration instance data that was previously returned by onRetainNonConfigurationInstance(). This will be available from the initial onCreate and onStart calls to the new instance, allowing you to extract any useful dynamic state from the previous instance. Note that the data you retrieve here should only be used as an optimization for handling configuration changes. You should always be able to handle getting a null pointer back, and an activity must still be able to restore itself to its previous state (through the normal onSaveInstanceState(Bundle) mechanism) even if this function returns null.

    */

    onRetainNonConfigurationInstance()方法:

    會發現在onRetainNonConfigurationInstance()方法,創建了NonConfigurationInstances實例,將viewModelStore存儲在其中,最終作爲結果值返回。

    回顧一下我們目前爲止整個的分析流程(僞代碼):

    ViewModelProvider constructor{
    
    ​     ComponentActivity.getViewModelStore(){
    
    ​         getLastNonConfigurationInstance(); 
    
    ​             (從這個方法我們會聯繫到onRetainNonConfigurationInstance()方法)
    
    ​     }
    
    }

    換句話說,目前爲止的分析都只是在ViewModelProvider的構造器中,我們知道ViewModelStore以及Factory相關信息已經存儲在了ViewModelProvider中。

  4. ViewModelProvider.get()方法:

    通過上面的源碼分析,ViewModel的實例實際上是存儲在ViewModelStore中的。

    • viewModel不爲null:返回該實例
    • viewModel爲null:調用factory的create方法,並將create方法返回的ViewModel實例再次put到ViewModelStore中去,最後纔將實例對象返回。
  5. ViewModelStore類:

    store,譯作商店,存儲;那麼ViewModelStore可以被認爲是ViewModel的商店。

    public class ViewModelStore {
    
       private final HashMap<String, ViewModel> mMap = new HashMap<>();
    
       final void put(String key, ViewModel viewModel) {
           ViewModel oldViewModel = mMap.put(key, viewModel);
           if (oldViewModel != null) {
               oldViewModel.onCleared();
           }
       }
    
       final ViewModel get(String key) {
           return mMap.get(key);
       }
    
       Set<String> keys() {
           return new HashSet<>(mMap.keySet());
       }
    
       /**
        *  Clears internal storage and notifies ViewModels that they are no longer used.
        */
       public final void clear() {
           for (ViewModel vm : mMap.values()) {
               vm.clear();
           }
           mMap.clear();
       }
    }

    這是ViewModelStore類所有的代碼,不超過30行,就一個HashMap,鍵是一個String類型,值則是ViewModel實例。我們回顧一下ViewModelProvider.get()方法,看看鍵到底長什麼樣:

    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
       String canonicalName = modelClass.getCanonicalName();
       if (canonicalName == null) {
           throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
       }
       return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
    private static final String DEFAULT_KEY =
           "androidx.lifecycle.ViewModelProvider.DefaultKey";

    DEFAULT_KEY + ":" + modelClass.canonicalName,組成了key。

    至於,我們對於ViewModelProvider還有ViewModelStore的工作原理大致理清楚了。

  6. ViewModel類自身:

    public abstract class ViewModel {
    
       @Nullable
       private final Map<String, Object> mBagOfTags = new HashMap<>();
       private volatile boolean mCleared = false;
    
       @SuppressWarnings("WeakerAccess")
       protected void onCleared() {}
    
       @MainThread
       final void clear() {
           mCleared = true;
           if (mBagOfTags != null) {
               synchronized (mBagOfTags) {
                   for (Object value : mBagOfTags.values()) {
                       // see comment for the similar call in setTagIfAbsent
                       closeWithRuntimeException(value);
                   }
               }
           }
           onCleared();
       }
    
       @SuppressWarnings("unchecked")
       <T> T setTagIfAbsent(String key, T newValue) {
           T previous;
           synchronized (mBagOfTags) {
               previous = (T) mBagOfTags.get(key);
               if (previous == null) {
                   mBagOfTags.put(key, newValue);
               }
           }
           T result = previous == null ? newValue : previous;
           if (mCleared) {
               closeWithRuntimeException(result);
           }
           return result;
       }
    
       @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
       <T> T getTag(String key) {
           if (mBagOfTags == null) {
               return null;
           }
           synchronized (mBagOfTags) {
               return (T) mBagOfTags.get(key);
           }
       }
    
       private static void closeWithRuntimeException(Object obj) {
           if (obj instanceof Closeable) {
               try {
                   ((Closeable) obj).close();
               } catch (IOException e) {
                   throw new RuntimeException(e);
               }
           }
       }
    }

    這是我把代碼中註釋刪掉後,ViewModel類所有的源碼。我們只需要關注兩點:

    • mBagOfTags:這是一個HashMap,然後圍繞這個HashMap的兩個方法:setTagIfAbsent()和getTag()分別用於往該HashMap中存儲,而getTag()則根據key來獲取value。也就是說,我們可以通過這兩個方法往ViewModel對象存取信息。可能你在項目中並沒有使用過,別急,我們待會看看viewModelScope的實現。

    • clear()方法:onCleared()是留給開發者去重寫從而實現自己的一些清理工作。

      final void clear() {
       mCleared = true;
       if (mBagOfTags != null) {
           synchronized (mBagOfTags) {
               for (Object value : mBagOfTags.values()) {
                   // see comment for the similar call in setTagIfAbsent
                   closeWithRuntimeException(value);
               }
           }
       }
       onCleared();
      }
      
      private static void closeWithRuntimeException(Object obj) {
           if (obj instanceof Closeable) {
               try {
                   ((Closeable) obj).close();
               } catch (IOException e) {
                   throw new RuntimeException(e);
               }
           }
       }

      會把mBagOfTags的值都拿出來遍歷一遍,如果是Closeable接口的子類,則調用close()方法,關閉資源。

  7. 在ViewModel中使用協程,我們都會viewModelScope這個擴展屬性來啓動一個子協程,例如我們在前一篇

    Android Architecture Component之:深層次理解LiveData 中:

    viewModelScope.launch {
       repeat(5) {
           delay(2000)
           periodTextData.value = "count: $it"
       }
    }

    我們爲什麼會使用這個viewModelScope屬性呢?它到底是個啥,我們來看它的實現:

    private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
    
    /**
    * [CoroutineScope] tied to this [ViewModel].
    * This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called
    *
    * This scope is bound to
    * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
    */
    val ViewModel.viewModelScope: CoroutineScope
           get() {
               val scope: CoroutineScope? = this.getTag(JOB_KEY)
               if (scope != null) {
                   return scope
               }
               return setTagIfAbsent(JOB_KEY,
                   CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
           }
    
    internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
       override val coroutineContext: CoroutineContext = context
    
       override fun close() {
           coroutineContext.cancel()
       }
    }
    • viewModelScope屬性的get():會調用我們前面提到的getTag方法以及setTagIfAbsent方法。
    • CloseableCoroutineScope類:該類實現了兩個接口,Closeable和CoroutineScope。這個Closeable接口是不是剛剛在closeWithRuntimeException方法裏碰到過。那麼也就是說,ViewModel的clear()方法裏,會調用CloseableCoroutineScope裏的close方法:coroutineContext.cancel(),從而使得由viewModelScope啓動所有子協程都被取消了,避免了泄漏。
  8. ViewModel的clear()方法什麼時候會被調用?

    我們只需要查看androidx.activity.ComponentActivity的構造方法:

    一目瞭然,註冊了一個Observer,當接收到ON_DESTROY事件時,會調用當前ComponentActivity對應的ViewModelStore對象的clear方法:

    public final void clear() {
       for (ViewModel vm : mMap.values()) {
           vm.clear();
       }
       mMap.clear();
    }

    從而就會調用對應ViewModel實例的clear方法。對於addObserver方法的分析,見 前一篇 對於LiveData的源碼分析。

  9. 對於Fragment而言,分析是類似,其對應的mViewModelStores是在一個叫FragmentManagerViewModel類中,該類本身會繼承自ViewModel類。有了前面的分析基礎,大家也能夠輕鬆對該類進行分析,這裏就不在贅述了。

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