当前位置:   article > 正文

Android ViewModel 引入协程

viewmodel 协程

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

ViewModel 引入协程

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

  1. class MyViewModel : ViewModel() {
  2. private val viewModelJob = SupervisorJob()
  3. private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
  4. override fun onCleared() {
  5. super.onCleared()
  6. viewModelJob.cancel() // Cancel all coroutines
  7. }
  8. fun launchDataLoad() {
  9. uiScope.launch {
  10. sortList()
  11. // Modify UI
  12. }
  13. }
  14. suspend fun sortList() = withContext(Dispatchers.Default) {
  15. // Heavy work
  16. }
  17. }
  18. 复制代码

然而,很多情况我们会经常忘记取消协程,导致出现内存泄漏等各种问题。 遇到这种场景,可以使用 ViewModel 扩展属性 viewModelScope 来优化代码。

viewModelScope 方式

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

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

  1. class MyViewModel : ViewModel() {
  2. fun launchDataLoad() {
  3. viewModelScope.launch {
  4. sortList()
  5. // Modify UI
  6. }
  7. }
  8. suspend fun sortList() = withContext(Dispatchers.Default) {
  9. // Heavy work
  10. }
  11. }
  12. 复制代码

viewModelScope 内部实现

  1. // androidx.lifecycle.ViewModel.viewModelScope
  2. private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"
  3. val ViewModel.viewModelScope: CoroutineScope
  4. get() {
  5. val scope: CoroutineScope? = this.getTag(JOB_KEY)
  6. if (scope != null) {
  7. return scope
  8. }
  9. return setTagIfAbsent(JOB_KEY,
  10. CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main))
  11. }
  12. internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
  13. override val coroutineContext: CoroutineContext = context
  14. override fun close() {
  15. coroutineContext.cancel()
  16. }
  17. }
  18. 复制代码

分析 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 的协程,实现流程非常清晰。相关代码如下:

  1. // androidx.lifecycle.ViewModel
  2. // clear() -> closeWithRuntimeException() -> coroutineContext.cancel()
  3. private final Map<String, Object> mBagOfTags = new HashMap<>();
  4. <T> T getTag(String key) {
  5. synchronized (mBagOfTags) {
  6. return (T) mBagOfTags.get(key);
  7. }
  8. }
  9. <T> T setTagIfAbsent(String key, T newValue) {
  10. T previous;
  11. synchronized (mBagOfTags) {
  12. previous = (T) mBagOfTags.get(key);
  13. if (previous == null) {
  14. mBagOfTags.put(key, newValue);
  15. }
  16. }
  17. T result = previous == null ? newValue : previous;
  18. if (mCleared) {
  19. closeWithRuntimeException(result);
  20. }
  21. return result;
  22. }
  23. @MainThread
  24. final void clear() {
  25. mCleared = true;
  26. if (mBagOfTags != null) {
  27. for (Object value : mBagOfTags.values()) {
  28. closeWithRuntimeException(value);
  29. }
  30. }
  31. onCleared();
  32. }
  33. private static void closeWithRuntimeException(Object obj) {
  34. if (obj instanceof Closeable) {
  35. try {
  36. ((Closeable) obj).close();
  37. } catch (IOException e) {
  38. throw new RuntimeException(e);
  39. }
  40. }
  41. }
  42. 复制代码

结论

如果你也正在使用 MVVM 和协程,非常推荐在 ViewModel 中使用 viewModelScope 方式。不仅简化 ViewModel 代码,而且还能管理协程生命周期。

转载于:https://juejin.im/post/5cd6f33a6fb9a0323416663e

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/运维做开发/article/detail/765065
推荐阅读
相关标签
  

闽ICP备14008679号