理解Kotlin协程

921 阅读5分钟

1. 为什么使用Kotlin协程

在 Android 上,避免阻塞主线程是非常必要的。主线程是一个处理所有界面更新的线程,也是调用所有点击处理程序和其他界面回调的线程。因此,主线程必须顺畅运行才能确保出色的用户体验

在实际开发中我们会遇到这种场景

  • 创建子线程执行耗时操作,然后切换到主线程处理界面显示逻辑;但是如果我们在一个接口请求完成后,拿到这个接口返回的结果,在需要去请求另一个接口时,逻辑就会十分复杂,就会出现回调地狱;并且如果在需要考虑接口失败的场景呢?
  • 使用Rxjava优化上面的场景;没有内存泄漏、支持取消、正确的使用线程,但是它比较复杂,如 subscribeOnobserveOnmap 或者 subscribe,都需要学习

这个时候kotlin 协程就出场了,它可以帮我们解决上面的痛点

2.什么是协程

看下官方的定义,协程是一种并发设计模式。

特点

协程是我们在 Android 上进行异步编程的推荐解决方案。值得关注的特点包括:

  • 轻量:您可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。**挂起比阻塞节省内存,且支持多个并行操作。
  • 内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。**
  • 内置取消支持取消操作会自动在运行中的整个协程层次结构内传播。
  • Jetpack 集成:许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供您用于结构化并发。

那协程到底是什么?

官方定义协程是一种轻量级的线程并且举例:创建10000个线程和创建10000个协程来比较协程是比线程轻量,但是协程是线程上层的封装,是基于线程的。要比较也应该和线程池比较。所以这种并不能说明协程比线程轻量。协程的轻量更多的是表现在协程的使用上面。 所以协程其实就是在一个线程挂起并且自己可以在另一个线程恢复的一段程序。

kotlin协程引入的核心功能

Kotlin 协程引入的核心功能是:在某一时刻暂停协程后,有能力在未来恢复它;也就是我们说的挂起,恢复

协程挂起原理

我们看下面一段代码

20220829164331.jpg

看看Decompile后的代码

20220829164535.jpg

20220829164612.jpg

20220829164708.jpg

我们可以看到

1.为什么返回值变成了Object

我们定义的(89行)getUser()方法返回值变成了Object,并且多了个Continuation 类型的参数,这是为什么呢?

  • 我们知道挂起函数必须在协程作用域或者挂起函数里面才能调用,就是因为编辑器通过CPS转换帮我们加入了入参Continuation ,普通函数没有这个参数,所以无法调用挂起函数。
  • 用suspend修饰的方法也有可能没有真正挂起,当真正挂起时,这个方法会返回CoroutineSingletons.COROUTINE_SUSPENDED,,没有真正挂起时这块返回的就是我们方法需要返回的User对象,所以这块用了Object作为函数的返回值

2.流程分析

  • 当我们调用getUser()方法时,会创建ContinuationImpl对象(100行),然后将入参Continuation传递进去。这个内部类有两个参数Object result; int label;,result是用来保存我们方法的返回值,label是一个状态机,初始化为0;所以第一次进入此方法的时候就会进入switch方法(118行)里面;
  • 上面label初始值是0,此时进入case 0,执行我们getUser里面的delay函数,label置为1,并且返回COROUTINE_SUSPENDED,也就是这块就挂起了。
  • 上面提到的ContinuationImplBaseContinuationImpl的子类,当delay执行完成后,会调用resumeWith方法恢复挂起状态到运行。此方法在BaseContinuationImpl被重写,里面最终会调用自己的抽象方法invokeSuspend,携带数据返回的结果,
  • 此时也就会回到我们ContinuationImplinvokeSuspend方法里面(107行)。会用变量result保存下方法返回结果,然后再次调用getUser()方法,因为在case 0的时候将label置为了1,所以此时走switchcase1,校验一下异常,因为之前恢复的实话result已经返回并赋值了,所以此时continuation也就有返回的数据结果,此时返回此数据结果。我们调用的地方也就能获取到这个结果了。

至此协程流程分析完毕 总结:

  1. 与基于回调的代码相比,协程代码可以利用更少的代码实现取消阻塞当前线程的相同效果。由于它具有顺序样式,因此可以轻松地链接多个长时间运行的任务,而无需创建多个回调。
  2. 协程是在一个线程挂起并可以在另一个线程恢复的一段程序。它的轻量更多的提现在它的使用上。
  3. 协程的挂起时基于CPS转换+状态机实现的