android kotlin 协程(六) 源码浅析

948 阅读5分钟

本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布[2023-03-02]

前言: kotlin协程源码十分庞大, 本篇只能吧我理解的源码聊一聊,不会特别深入研究,只会浅浅的看看表层. 本来计划协程系列是10篇左右,后续是flow热流冷流之类的, 冷流操作符之类的应该不会在写了, flow当作Rxjava来用就可以,后续可能还会写一篇关于热流的文章. 也可能没有:) 主要是不好写,文字写出来还是比较生硬....

如果没有看过前几篇,建议先看看前几篇, 本篇遇到前几篇的知识不会重复说!

launch 浅析

源码阅读从最简单的一个launch开始!

image-20230227135052971

在launch的时候, 会执行 CoroutineScope.newCoroutineContext函数 这里会传入一个EmptyCoroutineContext

CoroutineScope.newCoroutineContext 会走 foldCopies , 这个函数是用来合并2个协程的

先来看看 foldCopies的参数

  • coroutineContext // 可以看出,此时coroutineContext为JobImpl 我们稍后来看看它是在什么时候赋值的
  • context // 默认什么都没有传,是 EmptyCoroutineContext
  • true // isNewCoroutine 是否创建新的Coroutine

我们在创建CoroutineScope的时候,会对coroutineContext赋值

image-20230227135752795

我们在创建协程作用域的时候, 会初始化一个Job, Job的默认实现为JobImpl

好了,在回到 CoroutineScope.newCoroutineContext方法

image-20230227140733819

执行完foldCopies后,

我们知道此时返回结果combined = jobImpl,

最后当返回的时候 if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)

最终给他添加一个默认调度器 [Dispatchers.Default]

tips: ContinuationInterceptor 是协程拦截器, 下面会说,不要急

再次回到主线流程 image-20230227142255809

此时newContext = [JobImpl , Dispatchers.Default]

如果说,有子协程的情况下,newCoroutineContext 这个方法就会使用最新的coroutineContext, 例如这样:

image-20230227142733877

这都是 foldCopies 的功劳, foldCopies的细节就不看了,没意义 [比较烧脑,不想看]

再接着往下走:

image-20230227143116362

在聊协程启动模式的时候说过,coroutineStart一共有4中模式

  • DEFAULT
  • LAZY
  • UNDISPATCHED
  • ATOMIC // 实验中

此时会判断是否是懒加载模式, 很明显不是懒加载,所以会走 StandaloneCoroutine

StandaloneCoroutine 会通过 handleJobException 捕获一些异常.

比如说在JVM中使用Main线程的时候,会提示

Module with the Main dispatcher had failed to initialize. For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used

在这里就能捕获到

image-20230227143623791

在继续往下看:

image-20230227144214390

接下来就是开启一个协程

这里通过kotlin 的特性重载操作符,直接去它类中找到对应的方法即可

因为此时 协程启动模式是DEFAULT,那么直接看:

block.startCoroutineCancellable(receiver, completion)

即可

在接着往下看

createCoroutineUnintercepted(receiver, completion) 函数 源码位置

这个函数需要去kotiln官网看,太深了,我没看,我找到了 提供给需要的人!

这里看看拦截的方法

image-20230227145413801

可以看出,此时就会分发 continuation

先来看看不写 ContinuationInterceptor 是什么效果

image-20230227150205851

这段代码写过无数次, 因为协程默认启动模式需要调度,所以协程体内的代码还没来得及执行,main函数就已经结束了

那么在来看看添加协程拦截器后有什么变化

image-20230227150346152

可以看出, 协程会立即执行,我猜测是拦截的过程中会立即触发恢复,所以才会有这样的效果, 可惜的是我没找到恢复的代码

跟随源码手动创建协程

刚才看源码的时候看到了这段代码

image-20230227151308197

开启一个协程的时候, 通过函数扩展 扩展了 startCoroutineCancellable

那么我们是否也可以尝试的写一写

这里有一个注意点:

我们并不能调用 startCoroutineCancellable因为它是内部方法,我们无法直接调用

image-20230227152931128

只能拿 createCoroutine 来代替

来看看我们的代码:

image-20230227155331085

这里我把范型都替换成了具体的值,否则的话看的更迷糊

data class TestBean(val message: String)
fun main() {
    fun test(receiver: TestBean, block: suspend TestBean.() -> String) {
            block.createCoroutine(receiver, object : Continuation<String> { // 创建协程
                override val context: CoroutineContext
                    get() = EmptyCoroutineContext

                override fun resumeWith(result: Result<String>) {// 当协程恢复的时候执行
                    println("resume:$result")
                }
            })
    }

    val bean = TestBean("测试数据")

    test(bean) {
        println("我是test内部方法${this}")
        "我是返回数据"
    }
}

代码都写完了,但是这里并没有任何结果!

没有结果是正常现象,因为我们只是创建了一个协程,默认是挂起状态, 只有恢复的时候才会执行代码

例如这样:

image-20230227155649646

如果你觉得这样很麻烦,也可以直接

将 createCoroutine 改为 startCoroutine

  • createCoroutine // 创建协程 [默认挂起]
  • startCoroutine // 直接开启一个协程

image-20230227160020862

那么我们自定义CoroutineContxt是否还起作用呢?

image-20230227162655105

这里的时候,我们通过Job#cancel() 可以发现,并没有取消协程,恢复代码还是继续执行了

这是因为当我们使用 launch{} 开启一个协程的时候, 系统帮我们维护了job的各种状态

这里很简单,我们自己维护job即可

image-20230227162907083

系统代码指定是没有这么简单,不过模拟一下就可以了,没必要太纠结

那如果是异常如何捕获呢?

image-20230227163443183

需要注意的是,通过 CoroutineExceptionHandler捕获异常,这里监听的是 resumeWith方法

image-20230227163857448

只需要再次向上throw一下即可

简单的

刚才我们介绍的是有receiver的 startCoroutine,还有一个没有receiver的

image-20230227164714391

直接来看代码:

image-20230227164845823

这段代码很简答, 只是创建一个普通的协程,并恢复, 当然也可以通过CoroutineContext来控制协程,上面已经提到了,这里就不重复了

有创建协程手动恢复,就有直接创建并恢复

image-20230227165108121

有receiver和没有receiver的区别也很明显, 那就是在suspend方法体中是否有receiver.

总结

本篇我们阅读了 CoroutineScope#launch{}

并学习了手动管理协程的4个方法:

  • (suspend R.() -> T).startCoroutine // 有receiver直接恢复协程

  • (suspend R.() -> T).createCoroutine // 有receiver创建协程,需要手动恢复

  • (suspend () -> T).startCoroutine // 没有receiver直接恢复协程

  • (suspend () -> T).createCoroutine // 没有receiver创建协程,需要手动恢复

这四胞胎说实话,是真没啥用,但就是得了解一下.. 看的头疼:)

完整代码

原创不易,您的点赞就是对我最大的支持!