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] 复制代码
Job
、Dispatchers
与CoroutineName
都实现了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
方法,所以我们再来看下Element
的get
方法
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
集合中对应Key
的CoroutineContext
实例。
作者:小Fuคิดถึง
链接:https://juejin.cn/post/7019228411881062413