Android ViewModelScope 如何自動取消協程

語言: CN / TW / HK

theme: condensed-night-purple

先看一下 ViewModel 中的 ViewModelScope 是何方神聖

kotlin 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)) }

可以看到這個是一個擴充套件方法,

再點選 setTagIfAbsent 方法進去

java <T> T setTagIfAbsent(String key, T newValue) { T previous; synchronized (mBagOfTags) { previous = (T) mBagOfTags.get(key);//第一次肯定為null if (previous == null) { mBagOfTags.put(key, newValue);//null 儲存 } } T result = previous == null ? newValue : previous; if (mCleared) {//判斷是否已經clear了 // It is possible that we'll call close() multiple times on the same object, but // Closeable interface requires close method to be idempotent: // "if the stream is already closed then invoking this method has no effect." (c) closeWithRuntimeException(result); } return result; }

可以看到 這邊 會把 我們的 ViewModel 儲存到 ViewModel 內的 mBagOfTags 中

這個 mBagOfTags 是

java private final Map<String, Object> mBagOfTags = new HashMap<>();

這個時候 我們 viewModel 就會持有 我們 viewModelScope 的協程 作用域了。

那..這也只是 表述了 我們 viewModelScope 存在哪裡而已,

什麼時候清除呢?

先看一下 ViewModel 的生命週期

可以看到 ViewModel 的生命週期 會在 Activity onDestory 之後會被呼叫。

那...具體哪裡調的?

翻看原始碼可以追溯到 ComponentActivity 的預設構造器內

java public ComponentActivity() { /*省略一些*/ getLifecycle().addObserver(new LifecycleEventObserver() { @Override public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { if (event == Lifecycle.Event.ON_DESTROY) { if (!isChangingConfigurations()) { getViewModelStore().clear(); } } } }); }

可以看到內部會通對 Lifecycle 新增一個觀察者,觀察當前 Activity 的生命週期變更事件,如果走到了 Destory ,並且 本次 Destory 並非由於配置變更引起的,才會真正呼叫 ViewModelStore 的 clear 方法。

跟進 clear 方法看看

```java public class ViewModelStore {

private final HashMap<String, ViewModel> mMap = new HashMap<>();

/**
 *  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 內部實現 用 HashMap 儲存 ViewModel

於是在 clear 的時候,會逐個遍歷呼叫 clear方法

再次跟進 ViewModel 的 clear 方法

java @MainThread final void clear() { mCleared = true; // Since clear() is final, this method is still called on mock objects // and in those cases, mBagOfTags is null. It'll always be empty though // because setTagIfAbsent and getTag are not final so we can skip // clearing it if (mBagOfTags != null) { synchronized (mBagOfTags) { for (Object value : mBagOfTags.values()) { // see comment for the similar call in setTagIfAbsent closeWithRuntimeException(value); } } } onCleared(); }

可以發現我們最初 存放 viewmodelScope 的 mBagOfTags

這裡面的邏輯 就是對 mBagOfTags 儲存的資料 挨個提取出來並且呼叫 closeWithRuntimeException

跟進 closeWithRuntimeException

java private static void closeWithRuntimeException(Object obj) { if (obj instanceof Closeable) { try { ((Closeable) obj).close(); } catch (IOException e) { throw new RuntimeException(e); } } }

該方法內會逐個判斷 物件是否實現 Closeable 如果實現就會呼叫這個介面的 close 方法,

再回到最初 我們 viewModel 的擴充套件方法那邊,看看我們 viewModelScope 的真正面目

```java internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope { override val coroutineContext: CoroutineContext = context

override fun close() {
    coroutineContext.cancel()
}

} ```

可以明確的看到 我們的 ViewModelScope 實現了 Closeable 並且充寫了 close 方法,

close 方法內的實現 會對 協程上下文進行 cancel。

至此我們 可以大致整理一下

  1. viewModelScope 是 ViewModel 的擴充套件成員,該物件是 CloseableCoroutineScope,並且實現了 Closeable 介面
  2. ViewModelScope 儲存在 ViewModel 的 名叫 mBagOfTags 的HashMap中 啊
  3. ViewModel 儲存在 Activity 的 ViewModelStore 中,並且會監聽 Activity 的 Lifecycle 的狀態變更,在ON_DESTROY 且 非配置變更引起的事件中 對 viewModelStore 進行清空
  4. ViewModelStore 清空會對 ViewModelStore 內的所有 ViewModel 逐個呼叫 clear 方法。
  5. ViewModel的clear方法會對 ViewModel的 mBagOfTags 記憶體儲的物件進行呼叫 close 方法(該物件需實現Closeable 介面)
  6. 最終會會呼叫 我們 ViewModelScope 的實現類 CloseableCoroutineScope 的 close 方法中。close 方法會對協程進行 cancel。