快速上手 Kotlin 开发系列之什么是协程
Posted 张鹿鹿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快速上手 Kotlin 开发系列之什么是协程相关的知识,希望对你有一定的参考价值。
站在巨人的肩膀上做个笔记,摘录自:https://kaixue.io/kotlin-coroutines-1
协程是什么
协程的概念并没有官方的或者统一的定义,协程原本是一个跟线程非常类似的用于处理多任务的概念,是一种编程思想,并不局限于特定的语言。
那在 Kotlin 中的协程是什么呢?
其实就是一套有 Kotlin 官方提供的线程 API。就像 Java 的 Executor 和 android 的 AsyncTask,Kotlin 协程也对 Thread 相关的 API 做了一套封装,让我们不用过多关心线程也可以方便地写出并发操作,这就是 Kotlin 的协程。
协程的好处
既然 Java 有了 Executor,Android 又有了 Handler 和 AsyncTask 来解决线程间通信,而且现在还有 RxJava,那用协程干嘛呢?协程好在哪儿呢?
协程的好处,本质上跟其他的线程 API 一样,都是为了方便使用,但由于它借助了 Kotlin 的语言优势所以比起其他的实现方案要更方便一点。
协程最基本的功能就是并发也就是多线程,用协程你可以把任务切到后台执行:
launch(Dispatchers.IO)
//耗时
切到前台:
launch(Dispatchers.Main)
//更新 UI
这种写法很简单,但它不能算是协程直接使用 Thread 的优势,因为 Kotlin 专门添加了一个函数来简化对线程的直接使用:
thread
...
而 Kotlin 最大的好处是在于你可以把运行在不同线程的代码,写在同一个代码块里,上下两行代码,线程切走再切回来,这是在写 Java 时绝对做不到的。它可以用看起来同步的方式写出异步代码,这就是 Kotlin 最有名的 非阻塞式挂起
。例如:
CoroutineScope(Dispatchers.Main).launch
val bitmap = suspendingGetBitmap()// 网络请求,后台线程
imageView.setImageBitmap(bitmap)// 更新 UI 主线程
Java 中我们处理这样的场景需要使用回调来解决,一旦逻辑复杂很容易会出现两层或 n 层回调,这就陷入了回调地狱
。
而 Kotlin 的协程就完全消除了回调!另外大家不要忘了,回调式可不止多了几个缩进那么简单,它也限制了我们的能力。
比如,我们有个需求,他需要分别执行两次网络请求,然后把结果合并后再展示到界面,按照正常思路这两个接口应该同时发起请求,然后把结果做融合。
但是这种场景如果使用 Java 的回调式就会比较吃力,可能会把两个接口做串行的请求,但这很明显是垃圾代码!(暂时先不考虑 RxJava)
而使用协程可轻松实现,依然是上下两行代码:
launch(Dispatchers.Main)
val avatar = async getAvatar() //获取用户头像
val logo = async getLogo() //获取 Logo
mergeShowUI(avatar.await(), logo.await())//合并展示
所以由于 Kotlin 消除了并发任务之间协作的难度,协程可以让我们轻松的写出复杂的并发代码。这些就是协程优势所在!
async 会在后面的章节中介绍。
协程的基本使用
配置协程
要想在 Android 工程中使用 Kotlin 协程,需要配置相应的依赖:
根目录下的 build.gradle 配置版本:
buildscript
...
ext.kotlin_coroutines = '1.3.1'
...
app 下的 build.gradle:
dependencies
...
// 👇 依赖协程核心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines"
// 👇 依赖当前平台所对应的平台库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines"
...
创建协程
上面提到的 launch 函数不是一个顶层函数,是不能直接使用的,可以通过下面的三种方法来创建:
// 方法一,使用 runBlocking 顶层函数
runBlocking
getImage(imageId)
// 方法二,使用 GlobalScope 单例对象
// 👇 可以直接调用 launch 开启协程
GlobalScope.launch
getImage(imageId)
// 方法三,自行通过 CoroutineContext 创建一个 CoroutineScope 对象
// 👇 需要一个类型为 CoroutineContext 的参数
val coroutineScope = CoroutineScope(context)
coroutineScope.launch
getImage(imageId)
- 方法一通常用于单元测试场景,业务开发一般不会用它,因为它是线程阻塞的;
- 方法二和方法一的区别在于不会线程阻塞。但在 Android 中并不推荐这种用法,因为它的生命周期与 Application 一致,且不可取消;
- 方法三为推荐方式,可以通过传入的 context 参数来管理协程的生命周期(注:这里的 context 和 Android 里的不是一个)
使用协程
接下来开始介绍协程的具体使用,最简单的方式上面已经介绍了:
launch(Dispatchers.IO)
...
getImage(imageId)
...
这个 launch 函数表示含义就是我要创建一个新的协程,并在指定的线程上运行它,这个被创建的协程是谁?就是你传给 launch 的那些代码:
...
getImage(imageId)
...
这段连续的代码就是协程。
所以我们就清楚了什么时候用协程,就是当你切线程或者指定线程的时候。
例如,子线程中获取图片,主线程中更新 UI:
launch(Dispatchers.IO)
val image = getImage(imageId)
launch(Dispatchers.Main)
iv.setImageBitmap(image)
???什么情况,还是回调地狱???
如果只用 launch 函数协程并不比直接使用线程更加方便,但是协程里有一个很厉害的函数 withContext().
这个函数可以指定线程来执行代码,并且在执行完成之后 自动把线程切回来 继续执行。
launch(Dispatchers.Main)
val image = withContext(Dispatchers.IO)
getImage(imageId)
iv.setImageBitmap(image)
这种写法跟刚才看起来区别不大。 但是如果有了更多的线程切换区别就体现出来了,由于有了自动切回来
的功能,协程消除了并发代码在协作时的嵌套,直接写成了上下关系的代码就能让多线程之间进行协作,这就是协程。
以上就是本节内容,欢迎大家关注👇👇👇
以上是关于快速上手 Kotlin 开发系列之什么是协程的主要内容,如果未能解决你的问题,请参考以下文章