原文作者 :Manuel Vivo
原文地址: Easy Coroutines in Android: viewModelScope
译者 : 京平城
想省时间的读者可以直接看文章最后的译者总结
Scopes in ViewModels
CoroutineScope管理着它所创建的coroutines,当CoroutineScope被cancel的时候,它所创建的
coroutines也都会被cancel。当你在ViewModel中使用coroutines的时候,这个特性非常有用。
当ViewModel销毁的时候,所有正在运行的异步任务也应当停止,不然会造成资源的浪费和潜在的memory leak风险。
首先我们来看一下,在不使用ViewModelScope的情况下,我们是如何在ViewModel中使用coroutines的。
利用SupervisorJob来创建一个新的CoroutineScope,这个CoroutineScope将和ViewModel的生命周期一致。
在onCleared()方法中调用SupervisorJob的cancel方法,那么uiScope下的所有coroutines都会被cancel。
class MyViewModel : ViewModel() {
/**
* This is the job for all coroutines started by this ViewModel.
* Cancelling this job will cancel all coroutines started by this ViewModel.
*/
private val viewModelJob = SupervisorJob()
/**
* This is the main scope for all coroutines launched by MainViewModel.
* Since we pass viewModelJob, you can cancel all coroutines
* launched by uiScope by calling viewModelJob.cancel()
*/
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
/**
* Cancel all coroutines when the ViewModel is cleared
*/
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
/**
* Heavy operation that cannot be done in the Main Thread
*/
fun launchDataLoad() {
uiScope.launch {
sortList() // happens on the background
// Modify UI
}
}
// Move the execution off the main thread using withContext(Dispatchers.Default)
suspend fun sortList() = withContext(Dispatchers.Default) {
// Heavy work
}
}
下面我们来看看使用viewModelScope是怎么帮助我们简化上述代码的。
viewModelScope means less boilerplate code
使用AndroidX lifecycle v2.1.0中新增的扩展属性viewModelScope能帮助我们把上述代码简化为
class MyViewModel : ViewModel() {
/**
* Heavy operation that cannot be done in the Main Thread
*/
fun launchDataLoad() {
viewModelScope.launch {
sortList()
// Modify UI
}
}
suspend fun sortList() = withContext(Dispatchers.Default) {
// Heavy work
}
}
如果要使用viewModelScope的话,首先请在你的build.gradle中添加
implementation "androidx.lifecycle.lifecycle-viewmodel-ktx$lifecycle_version"
接下来我们来探究一下viewModelScope的实现
Digging into viewModelScope
private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
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))
}
在ViewModel中使用了ConcurrentHashSet来保存CoroutineScope,CoroutineScope的key就是JOB_KEY
通过this.getTag(JOB_KEY)来获取CoroutineScope,如果没有就创建一个
setTagIfAbsent(JOB_KEY, CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
译者:我并没有能够在ViewModel源码中找到ConcurrentHashSet,只发现了一个hashmap和一行注释,不清楚是原作者的笔误还是代码已经发生了变化.
// Can't use ConcurrentHashMap, because it can lose values on old apis (see b/37042460)
@Nullable
private final Map<String, Object> mBagOfTags = new HashMap<>();
当ViewModel执行clear操作的时候会在onCleared()方法之前先调用clear()方法。
在clear()方法中会循环遍历所有实现了Closeable接口的对象,并且调用他们的close()方法。
@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) {
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);
}
}
}
我们来看一下viewModelScope存在ViewModel中的那个CloseableCoroutineScope对象
internal class CloseableCoroutineScope(
context: CoroutineContext
) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
CloseableCoroutineScope实现了Closeable接口,并且当ViewModel执行clear操作的时候,它的close方法会被调用coroutineContext.cancel()
它的相关实现如下
/**
* Cancels [Job] of this context with an optional cancellation cause.
* See [Job.cancel] for details.
*/
public fun CoroutineContext.cancel(cause: CancellationException? = null) {
this[Job]?.cancel(cause)
}
最后会调用job的cancel方法。
译者总结:
ViewModelScope是ViewModel的一个扩展属性,实现了CoroutineScope接口,是一个自定义的CoroutineScope。
当调用viewModelScope.launch(launch是CoroutineScope的一个扩展方法)的时候,它会在ViewModel的内部存储(hashmap)里添加一个CloseableCoroutineScope对象(即使多次调用viewModelScope.launch也只会创建一次CloseableCoroutineScope对象)。
该对象实现了Closeable接口和CoroutineScope接口,它的CoroutineContext是SupervisorJob + Dispatchers.Main.immediate。
在ViewModel执行clear操作的时候,会循环遍历内部存储(hashmap),并且执行实现了Closeable接口对象的close()方法,那么就相当于帮我们自动执行了viewModelScope里的SupervisorJob()的cancel()方法
再直白一点说就是ViewModelScope帮我们省去了在ViewModel中手动创建一个CoroutineScope对象的步骤
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
并且帮我们省去了在onCleared()方法中手动调用job的cancel()方法
/**
* Cancel all coroutines when the ViewModel is cleared
*/
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}