context是一特定场景下的执行环境,我们会在context中配置资源和其它对象。针对浮动框场景下,举个例子:1
2
3
4
5
6class FloatPanelContext {
private Context mContext;
private View mRootView;
// ...
// get set
}
但是kotlin的coroutine的context的实现,有点独特
CoroutineContext
1 | public interface CoroutineContext { |
CoroutineContext是一个interface,里边有个嵌套的interface是Element,Element是继承自CoroutineContext,Element中有一个对应的Key。每个操作对象都是直接或间接地实现了Element,比如说Job,CoroutineDispatcher,ContinuationInterceptor。
CoroutineContext定义了两个操作符重载函数,get和plus,get是操作符[]
,plus是操作符+
,还有个minusKey。要理解这些函数的意义,得先搞明白协程上下文的表示方法。
链表
协程上下文其实是一个左向链表,至于链表,举个例子,通常是这样的1
data class Node(var next: Node)
但是这个左向链表,是这样的1
data class Node(var left: Node)
从结构上看没啥改变,只不过属性名不同。但是呢,它的意义不同,对于普通的链表,链表引用指向头,对于左向链表,链表引用指向尾。
CombinedContext
这个表示协程上下文的链表中,每个节点是CombinedContext
,它的定义主要如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44internal 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]
}
}
}
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(left.fold(initial, operation), element)
public override fun minusKey(key: Key<*>): CoroutineContext {
element[key]?.let { return left }
val newLeft = left.minusKey(key)
return when {
newLeft === left -> this
newLeft === EmptyCoroutineContext -> element
else -> CombinedContext(newLeft, element)
}
}
private fun size(): Int {
var cur = this
var size = 2
while (true) {
cur = cur.left as? CombinedContext ?: return size
size++
}
}
// ...
// ...
}
在这个重载了操作符[]
的get方法中,遍历链表,先在当前节点的中按照Key去寻找Element,若未找到,去从left节点中寻找。
fold方法的作用是,把自己集成到参数initial上。
minusKey方法的作用是,把这个链表上与参数key相同的Element去掉。如果是某一个节点的element的key相同,就把这个节点去掉。
size既是链表的长度。
回到CoroutineContext
现在回到CoroutineContext中,再看它的重载了+
操作符的plus方法,1
2
3
4
5
6
7
8
9
10
11
12
13
14public 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)
}
}
}
把参数context加到本链表中,用的是fold方法,操作方式是那一个lambda表达式,那个lambda表达式主要做了一件事是从链表中取出来Key是ContinuationInterceptor的节点,并放到链表的尾部,因为它是左向链表,放到尾部可以直接取出来用。为什么放到尾部,还不知道,也可能是约定好的。
总结
- 协程的上下文并不是通常定义的一个类,而是具有链表结构的列表,每一个节点是Element,Element继承自CoroutineContext;
- CoroutineContext重载了
+
和[]
,+
可以把新的Element加到链表上,[]
的参数是Key,这又可以使得整个链表具有了map的属性; - CombinedContext是上下文链表的节点,它重写了一些方法,这些方法中的逻辑实现,采用了遍历和递归的方式,遍历是对链表的遍历,递归是通过调用操作符
[]
进行递归查找。