阅读 290

Android协程(Coroutines)系列-深入理解CoroutineContext上下文

❓Android协程哪里使用了上下文?

其实Android协程(Coroutines)系列-入门文章中的GlobalScope.launch中的构造方法中需要传入上下文,如果不传,默认是EmptyCoroutineContext,源码如下:

public fun CoroutineScope.launch(     context: CoroutineContext = EmptyCoroutineContext,     start: CoroutineStart = CoroutineStart.DEFAULT,     block: suspend CoroutineScope.() -> Unit ): Job {     val newContext = newCoroutineContext(context)     val coroutine = if (start.isLazy)         LazyStandaloneCoroutine(newContext, block) else         StandaloneCoroutine(newContext, active = true)     coroutine.start(start, coroutine, block)     return coroutine } 复制代码

作用域和上下文的区别只在于使用目的的不同

  • 作用域用于管理协程;

  • 而上下文只是一个记录协程运行环境的集合

???? CoroutineContext

协程的上下文,它包含用户定义的一些数据集合,这些数据与协程密切相关。它类似于map集合,可以通过key来获取不同类型的数据。同时CoroutineContext的灵活性很强,如果其需要改变只需使用当前的CoroutineContext来创建一个新的CoroutineContext即可。

来看下CoroutineContext的定义

@SinceKotlin("1.3") public interface CoroutineContext {     /**      * Returns the element with the given [key] from this context or `null`.      */     public operator fun <E : Element> get(key: Key<E>): E?     /**      * Accumulates entries of this context starting with [initial] value and applying [operation]      * from left to right to current accumulator value and each element of this context.      */     public fun <R> fold(initial: R, operation: (R, Element) -> R): R     /**      * Returns a context containing elements from this context and elements from  other [context].      * The elements from this context with the same key as in the other one are dropped.      */     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)                     }                 }             }     /**      * Returns a context containing elements from this context, but without an element with      * the specified [key].      */     public fun minusKey(key: Key<*>): CoroutineContext     /**      * Key for the elements of [CoroutineContext]. [E] is a type of element with this key.      */     public interface Key<E : Element>     /**      * An element of the [CoroutineContext]. An element of the coroutine context is a singleton context by itself.      */     public interface Element : CoroutineContext {         /**          * A key of this coroutine context element.          */         public val key: Key<*>         public override operator fun <E : Element> get(key: Key<E>): E? =             @Suppress("UNCHECKED_CAST")             if (this.key == key) this as E else null         public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =             operation(initial, this)         public override fun minusKey(key: Key<*>): CoroutineContext =             if (this.key == key) EmptyCoroutineContext else this     } } 复制代码

每一个CoroutineContext都有它唯一的一个Key其中的类型是Element,我们可以通过对应的Key来获取对应的具体对象。通过例子来了解

fun main() {     var context = Job() + Dispatchers.IO + CoroutineName("名字")     println("$context")     println("${context[CoroutineName]}")     context = context.minusKey(Job)     println("minusKey:$context") } 复制代码

输出

[JobImpl{Active}@6193b845, CoroutineName(名字), Dispatchers.IO] CoroutineName(名字) minusKey:[CoroutineName(名字), Dispatchers.IO] 复制代码

JobDispatchersCoroutineName都实现了Element接口。

如果需要结合不同的CoroutineContext可以直接通过+拼接,本质就是使用了plus方法。

/**  * Returns a context containing elements from this context and elements from  other [context].  * The elements from this context with the same key as in the other one are dropped.  */ 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)                 }             }         } 复制代码

plus的实现逻辑是将两个拼接的CoroutineContext封装到CombinedContext中组成一个拼接链,同时每次都将ContinuationInterceptor添加到拼接链的最尾部.

翻译

/**  * 合并两个上下文,如果有重复的,用右侧的替换,其次,确保拦截器在最后的位置  * */   public operator fun plus(context: CoroutineContext): CoroutineContext =           if (context === EmptyCoroutineContext) {               // fast path -- avoid lambda creation               //如果要合并的是一个空上下文,直接返回当前的上下文               this           } else {               //如果左右两个上下文都是有内容的               context.fold(this) { acc, element ->                   //取出右侧的上下文的key,acc.minusKey计算出左侧上下文除去这个key后剩下的上下文内容                   val removed = acc.minusKey(element.key)                   if (removed === EmptyCoroutineContext) {                       //如果左侧剩下的是空上下文,说明左侧也是只有这一个key对应的值                       //所以,直接右侧替换左侧,,,返回右侧element即可                       element                   } else {                       //如果左侧的上下文有自己的内容,接下来就是要合并两个上下文了,                       // 同时将相同key值的替换为右侧的                       // make sure interceptor is always last in the context (and thus is fast to get when present)                       // 确保拦截器始终位于上下文中的最后一个(因此在出现时可以快速获取)                       //感觉这里得context 已经开始都是一个CombinedContext类型了吧                       val interceptor = removed[ContinuationInterceptor]                       if (interceptor == null) {                           //如果左侧没有拦截器,直接将左右合并                           CombinedContext(removed, element)//A                       } else {                           //如果左侧有拦截器,则先取出其他的上下文                           val left = removed.minusKey(ContinuationInterceptor)                           if (left === EmptyCoroutineContext) {                               //如果其他的是空上下文,,则说明左侧只剩下一个拦截器,直接将拦截器合并到右侧的                               CombinedContext(element, interceptor)//B:  注意参数位置,和A处对比                           } else {                               //如果左侧除了拦截器,还有其他的上下文,为了确保拦截器在最后,应该将其他的和右侧合并,                               //最后再合并拦截器到右侧                               CombinedContext(CombinedContext(left, element), interceptor)                           }                       }                   }               }           } 复制代码

那么CombinedContext又是什么呢?

@SinceKotlin("1.3") internal class CombinedContext(    private val left: CoroutineContext,    private val element: Element ) : CoroutineContext, Serializable {    override fun <E : Element> get(key: Key<E>): E? {        var cur = this        while (true) {            cur.element[key]?.let { return it }            val next = cur.left            if (next is CombinedContext) {                cur = next            } else {                return next[key]            }        }    }    ``` } 复制代码

那么这个Key到底是什么呢?我们来看下CoroutineName

public data class CoroutineName(     /**      * User-defined coroutine name.      */     val name: String ) : AbstractCoroutineContextElement(CoroutineName) {     /**      * Key for [CoroutineName] instance in the coroutine context.      */     public companion object Key : CoroutineContext.Key<CoroutineName>     /**      * Returns a string representation of the object.      */     override fun toString(): String = "CoroutineName($name)" } 复制代码

很简单它的Key就是CoroutineContext.Key<CoroutineName>,当然这样还不够,需要继续结合对于的operator get方法,所以我们再来看下Elementget方法

public override operator fun <E : Element> get(key: Key<E>): E? =     @Suppress("UNCHECKED_CAST")     if (this.key == key) this as E else null 复制代码

所以我们就可以直接通过类似于Map的方式来获取整个协程中CoroutineContext集合中对应KeyCoroutineContext实例。


作者:小Fuคิดถึง
链接:https://juejin.cn/post/7019228411881062413


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐