ViewModel 中使用 viewModelScope 代替 CoroutineScope

798 阅读1分钟

使用

在 ViewModel 中使用协程时,一种写法是在 ViewModel 里实现 CoroutineScope 接口,然后在 onCleared() 方法里取消 job

在 AndroidX Lifecycle v2.1.0 中,在 ViewModel 里引入了 viewModelScope ,既不需要额外的实现,也会随着 ViewModel 的销毁而销毁。

viewModelScope 的使用很简单,在 ViewModel 内部直接调用即可。

class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // Coroutine that will be canceled when the ViewModel is cleared.
        }
    }
}

launchasync 的调用方式和 CoroutineScope 中使用一致。

默认是 Dispatchers.Main

原理

viewModelScope 是 ViewModel 的一个扩展属性,定义如下:

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() , 会根据 Tag 将生成的 Scope 保存到一个 HashMap 里。后续使用时通过 Tag 取之前创建的 Scope

最开始是保存在 ConcurrentHashMap 里,但后面换成了 HashMap ,原因是前者可能丢失旧的 apis 的值。

当 ViewModel 销毁时,它会调用 clear() 方法,在该方法内部,会清理掉 HashMap 里的所有值。

@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();
}

清理的要求是里面保存的对象必须实现了 Closeable ,在 closeWithRuntimeException() 里是通过调用 Closeable 接口的 close() 来实现的。

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

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

参考文章:

Easy Coroutines in Android: viewModelScope

Android ViewModel 引入协程