小知识,大挑战!本文正在参与“ 程序员必备小知识 ”创作活动
本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金
📚 如果您是 Android 平台上协程的初学者,请查阅上一篇文章: Android协程(Coroutines)系列-入门
❓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实例。