在讲协程的如何切换线程之前,有必要先了解下协程的上下文是什么?它的结构是什么样的?以及我们如何使用它?今天带着该问题来认识它。
CoroutineContext
协程上下文都是继承自CoroutineContext,它是一个接口,内部方法以及内部类如下:
它的实现子类有如下:
比如我们常见的EmptyCoroutineContext,它的内部实现如下:
可以看到它的get、fold、plus、minusKey几个方法都是默认实现,你可以理解它就是个空壳子的context。
Element
在讲CoroutineContext内部结构之前,先来认识下Element,它也实现了CoroutineContext接口:
Element中有一个key的属性,这里可以理解key就是当前Element的唯一标识。实现一个context的时候需要指明它的key是啥,此处就是用该key来标识 get:如果传进来的key和自己的key相等,则返回自己,否则返回null fold:将初始值和当前element返回给lambda,让lambda自己去处理 minusKey:如果传进来的key和自己相同,则返回EmptyCoroutineContext,否则返回自己,其实是删除对应key的context.
写了3个context,然后用"+“拼接:
自定义context的时候,需要继承自AbstractCoroutineContextElement,它是继承自Element,因为它强制要求需要一个key作为context的标识,一般key的element标识是当前context,看上面的One这个context,它的key拥有的element是One。
输出日志如下:
One()+Two()+Three()得到的是一个CombinedContext,get方法通过One这个key取到了One这个取对应的Context
日志如下:
可以看到我给One的context拼接了一个EmptyCoroutineContext时候,得到的是它自己,"+“是重载了context的plus方法,看下plus方法的实现:
|
|
1.如果传进来的context是EmptyCoroutineContext,则返回自己,所以上面的One()+EmptyCoroutineContext,得到的是One这个context 2.context.fold,会把初始值和context传给闭包,所以acc是当前context,element是传进来的context 3.acc.minuskey(element.key),如果传进来的context的key和当前context的key相等,则返回传进来的context,所以新的context会把旧的context给覆盖掉了 4.如果传进来的context的key和当前context的key不相等,removed则是当前context,查看当前context中是否有ContinuationInterceptor类型的context,我们的dispatcher都是属于该类型,需要单独处理 5.如果context中没有ContinuationInterceptor类型的context,则初始化出一个CombinedContext的context,所以上面的One()+Two()+Three()是一个CombinedContext的context 6.如果当前context中存在ContinuationInterceptor类型的context,则继续判断当前context是不是ContinuationInterceptor类型的context 7.如果是ContinuationInterceptor类型的context,则把传进来的context和当前的context组合成CombinedContext的context 8.如果当前的context不是一个ContinuationInterceptor类型的context,则把当前当前的context和传进来的context新组合成一个CombinedContext的context,再和前面的ContinuationInterceptor组合成一个新的CombinedContext的context
CombinedContext
|
|
它是直接继承自CoroutineContext,有两个比较重要的属性:
left:CoroutineContext,它是左边的节点
element:Element,当前节点
其实和链表的结构有点类似,left相当于next节点。
get:递归节点,直到left节点不是CombinedContext类型的
fold:先把left和初始值组成一个初始值,然后再把这个初始值和当前节点传给闭包
minusKey:
1.如果当前节点中找到了该key,则返回left节点
2.如果找不到,则继续在left节点中找
3.如果找不到返回this
4.如果找到了则返回当前节点
5.否则继续往左边再找
整个分析来看,协程中的context如果是多个context拼接的时候如果传进来的是EmptyCoroutineContext,则只保存自己。如果传进来的context的key和当前context的key一样,则会覆盖掉原来的context。如果都不满足,则采用链表的形式插入到原来的context头节点上,如果传进来的是ContinuationInterceptor类型的,则会把该类型放到头节点。
类图
再来一张本次讲解的context类图: