粗糙的谈一下Kotlin中的CoroutineScope


简单说明

CoroutineScope其实定义了协程的生命周期,比如在Activity中启动的协程,在Activity销毁的时候应该要取消.

而GlobalScope则是对应整个APP的生命周期,即使Activity已经销毁,CoroutineScope依然继续运行,可能导致协程泄漏,内存泄漏.

CoroutineScope

先来看一下它的定义:

public interface CoroutineScope {

    // 不建议使用普通的代码访问这个属性,除非使用Job()用于一些高级使用场景
    // 按照约定,这个属性应该包含一个Job对象用于强制执行结构化并发,比如可以这么用coroutineContext[Job]
    public val coroutineContext: CoroutineContext
}

可以看到,就是一个只带一个Field的简单接口.

CortoutineScope中封装了CoroutineContext,用于各种CortoutineScope的extention方法,比如GlobalScope.launch.

可以使用诸如下面这样的代码去创建CoroutineScope:

private val job = Job()
val coroutineScope = CoroutineScope(Dispatchers.Main + job)

再比如ViewModel提供的协程实现:

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()
    }
}

这里都会对CoroutineContext进行 +操作,这里其实就是一个操作符重载.

    public operator fun plus(context: CoroutineContext): CoroutineContext =
        if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
            context.fold(this) { acc, element ->
                val removed = acc.minusKey(element.key)
                if (removed === EmptyCoroutineContext) element else {
                    // make sure interceptor is always last in the context (and thus is fast to get when present)
                    val interceptor = removed[ContinuationInterceptor]
                    if (interceptor == null) CombinedContext(removed, element) else {
                        val left = removed.minusKey(ContinuationInterceptor)
                        if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                            CombinedContext(CombinedContext(left, element), interceptor)
                    }
                }
            }

创建自定义的CoroutineScope

  • 使用CoroutineScope()方法

        fun createScope(){
            CoroutineScope(Dispatchers.Main).launch {
                // doSomething()
            }
        }

    这里的CoroutineScope是一个方法:

    public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
        ContextScope(if (context[Job] != null) context else context + Job())

    需要注意的是,在组件生命周期结束时记得cancel操作

如果不使用GlobalScope,使用什么Scope?

如果不想自己自定义CoroutineScope,可以有下面这些选择:

  • 使用MainScope

      class MyAndroidActivity {
          private val scope = MainScope()
    
          override fun onDestroy() {
              super.onDestroy()
              scope.cancel()
          }
     }

    这里的MainScope其实也是一个简单的拓展实现:

    public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

    注意到这里使用的是Dispatchers.Main,因此不要在里面做耗时操作.可以与withContext()方法组合使用.

  • 使用AndroidX提供的ViewModelScope等.

    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()
        }
    }

文章作者: 姜康
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 姜康 !
评论
 上一篇
粗糙的谈一下Kotlin中的协程 粗糙的谈一下Kotlin中的协程
概念协程运行在协程上下文中(CoroutineContext)。 协程上下文包含一个协程调度器(CoroutineDispatcher),它可以将协程限制在一个特定的线程中执行,或者将协程分配到一个线程池中,或者让它不受限制的运行。 Cor
2020-10-29
下一篇 
image_picker的使用 image_picker的使用
简单记录一下image_picker的使用 从相册选择图片 static Future<File> pickImageFromGallery() async { final pickedFile = awai
2020-10-27
  目录