AndroidX Lifecycle v2.1.0 在 ViewModel 中引入 viewModelScope
,当 ViewModel 被销毁时它会自动取消协程任务,这个特性真的好用。本文介绍 viewModelScope
使用和内部实现方式,分析 ViewModel 是如何自动取消协程的。android
当咱们在 ViewModel 里面须要引入协程,首先要在 ViewModel 中新建一个 CoroutineScope, 用来管理全部协程任务,同时须要 onCleared()
方法里面取消协程任务,模板代码实现以下:ide
class MyViewModel : ViewModel() {
private val viewModelJob = SupervisorJob()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
override fun onCleared() {
super.onCleared()
viewModelJob.cancel() // Cancel all coroutines
}
fun launchDataLoad() {
uiScope.launch {
sortList()
// Modify UI
}
}
suspend fun sortList() = withContext(Dispatchers.Default) {
// Heavy work
}
}
复制代码
然而,不少状况咱们会常常忘记取消协程,致使出现内存泄漏等各类问题。 遇到这种场景,可使用 ViewModel 扩展属性 viewModelScope
来优化代码。优化
注意 lifecycle-viewmodel-ktx 版本号: 2.1.0-beta01ui
viewModelScope
管理协程的方式与咱们在 ViewModel 引入协程的方式同样,使用很是简单,模板代码实现以下:this
class MyViewModel : ViewModel() {
fun launchDataLoad() {
viewModelScope.launch {
sortList()
// Modify UI
}
}
suspend fun sortList() = withContext(Dispatchers.Default) {
// Heavy work
}
}
复制代码
// androidx.lifecycle.ViewModel.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))
}
internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
复制代码
分析 viewModelScope
源码有 3 点须要关注:spa
SupervisorJob
而不是用 Job
Closeable
接口viewModelScope
默认使用 Dispatchers.Main
, 方便 Activity 和 Fragment 更新 UIViewModel 类经过 HashMap 存储 CoroutineScope 对象,当使用 getTag(JOB_KEY)
方法获取对象不存在时,建立一个新的 CoroutineScope 并调用 setTagIfAbsent(JOB_KEY, scope)
方法存储新建的 CoroutineScope 对象。ViewModel 被销毁时内部会执行 clear()
方法,在 clear()
方法中遍历调用 closeWithRuntimeException
取消了 viewModelScope
的协程,实现流程很是清晰。相关代码以下:code
// androidx.lifecycle.ViewModel
// clear() -> closeWithRuntimeException() -> coroutineContext.cancel()
private final Map<String, Object> mBagOfTags = new HashMap<>();
<T> T getTag(String key) {
synchronized (mBagOfTags) {
return (T) mBagOfTags.get(key);
}
}
<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;
}
@MainThread
final void clear() {
mCleared = true;
if (mBagOfTags != null) {
for (Object value : mBagOfTags.values()) {
closeWithRuntimeException(value);
}
}
onCleared();
}
private static void closeWithRuntimeException(Object obj) {
if (obj instanceof Closeable) {
try {
((Closeable) obj).close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
复制代码
若是你也正在使用 MVVM 和协程,很是推荐在 ViewModel 中使用 viewModelScope
方式。不只简化 ViewModel 代码,并且还能管理协程生命周期。协程