为了活动小家电-CoroutineScope取消处理(二)

75 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的13天,点击查看活动详情

为了活动小家电,接着上篇搞!

延迟取消

即使job处理得好,也可以终止。这部分很容易解决,但是在使用while语句的时候其实有一个问题,在cancel发生的时候很重要。

当此代码检查 isActive 时,它​​会根据 isActive 的状态终止。

private suspend fun suspendLoop() = withContext(Dispatchers.IO) {
    while (isActive) {
        println("工作存活着")
        delay(1)
    }
}

如果你只是简单地操作如下图的无限循环,它是行不通的。

private suspend fun suspendLoop() = withContext(Dispatchers.IO) {
    while (true) {
        println("工作存活着")
        delay(1)
    }
}

如果你看这段代码,你可以看到至少需要使用 isActive 来控制无限循环。

在 AAC ViewModel 中使用协程的推荐方式

这一段,我们将看看如何在 Android 中使用最多的 ViewModel 中使用协程。

class TestViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // 清除 ViewModel 时将取消的协程。
        }
    }
}

viewModelScop 在这里谈论什么?

这个 viewModelScop 对应于一个可以使用 ViewModel ktx 的扩展。

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:lastVersion'

所以,稍微看一下内部代码,如下所示,和之前看到的 MainScope() 的代码是一样的,只是 Dispatches 的用法略有不同。

/**
 * ViewModel 绑定一个CoroutineScope,当viewmodel的onCleared被回调时取消该协程作用域
 */
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))
        }

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

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

那么这个协程什么时候结束呢?最后的可关闭继承部分很重要。

实现Closeable,在AAC-ViewModel的内部代码中有明确的,在这段代码中有一个叫closeWithRuntimeException的代码。

正如您在这段代码中看到的,如果它是 Closeable,您可以看到可以再次调用 close。

综上所述,Android onDestroy 调用的时候,ViewModel 的 clear 被调用,并且 closeWithRuntimeException 被调用,自然也就结束了 AAC-ViewModel 的 viewModelScope。关于ViewModel的源码解析可以参考我之前的文章

public abstract class ViewModel {

  @MainThread
  final void clear() {
      mCleared = true;
      if (mBagOfTags != null) {
          synchronized (mBagOfTags) {
              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);
          }
      }
  }
}

总结

我们检查了 CoroutineScope 的各种方法。

他们中的一些人自己照顾自己,但最终有很多部分是用户需要了解和使用的。如上所见,如果使用 MainScope 并使用 CoroutineScope,则终止处理是必不可少的。

并且发现取消也不一定无条件终止。

简而言之

  • 需要根据生命周期使用范围。
  • 根据生命周期,必须调用范围的取消。
  • 取消并不意味着所有范围都被无条件终止。
  • 在无限循环中,要取消,必须检查 isActive。