前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ViewModel中引入协程

ViewModel中引入协程

作者头像
蜻蜓队长
发布2019-05-17 14:40:38
1.4K0
发布2019-05-17 14:40:38
举报
文章被收录于专栏:Android机动车

AndroidX Lifecycle v2.1.0 在 ViewModel 中引入 viewModelScope,当 ViewModel 被销毁时它会自动取消协程任务,这个特性真的好用。本文介绍 viewModelScope 使用和内部实现方式,分析 ViewModel 是如何自动取消协程的。

ViewModel 引入协程

当我们在 ViewModel 里面需要引入协程,首先要在 ViewModel 中新建一个 CoroutineScope, 用来管理所有协程任务,同时需要 onCleared() 方法里面取消协程任务,模板代码实现如下:

代码语言:javascript
复制
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 来优化代码。

viewModelScope 方式

注意 lifecycle-viewmodel-ktx 版本号: 2.1.0-beta01

viewModelScope 管理协程的方式与我们在 ViewModel 引入协程的方式一样,使用非常简单,模板代码实现如下:

代码语言:javascript
复制
class MyViewModel : ViewModel() {

    fun launchDataLoad() {
        viewModelScope.launch {
            sortList()
            // Modify UI
        }
    }

    suspend fun sortList() = withContext(Dispatchers.Default) {
        // Heavy work
    }
}

viewModelScope 内部实现

代码语言:javascript
复制
// 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 点需要关注:

  1. 注意使用 SupervisorJob 而不是用 Job
  2. 为了 ViewModel 能够取消协程,需要实现 Closeable 接口
  3. viewModelScope 默认使用 Dispatchers.Main, 方便 Activity 和 Fragment 更新 UI

ViewModel 内部取消协程

ViewModel 类通过 HashMap 存储 CoroutineScope 对象,当使用 getTag(JOB_KEY) 方法获取对象不存在时,创建一个新的 CoroutineScope 并调用 setTagIfAbsent(JOB_KEY, scope) 方法存储新建的 CoroutineScope 对象。ViewModel 被销毁时内部会执行 clear() 方法,在 clear() 方法中遍历调用 closeWithRuntimeException 取消了 viewModelScope 的协程,实现流程非常清晰。相关代码如下:

代码语言:javascript
复制
// 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 代码,而且还能管理协程生命周期。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-05-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android机动车 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ViewModel 引入协程
  • viewModelScope 方式
  • viewModelScope 内部实现
  • ViewModel 内部取消协程
  • 结论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档