kotlin协程

213 阅读3分钟

1. 协程定义

从 Android 开发者的角度去理解它们的关系:

  • 我们所有的代码都是跑在线程中的,而线程是跑在进程中的。
  • 协程没有直接和操作系统关联,但它不是空中楼阁,它也是跑在线程中的,可以是单线程,也可以是多线程。
  • 单线程中的协程总的执行时间并不会比不用协程少。
  • Android 系统上,如果在主线程进行网络请求,会抛出 NetworkOnMainThreadException,对于在主线程上的协程也不例外,这种场景使用协程还是要切线程的。

协程设计的初衷是为了解决并发问题,让 「协作式多任务」 实现起来更加方便。这里就先不展开「协作式多任务」的概念,等我们学会了怎么用再讲。

视频里讲到,协程就是 Kotlin 提供的一套线程封装的 API,但并不是说协程就是为线程而生的。

2. 协程好在哪里

我们需要先了解一下「闭包」这个概念,调用 Kotlin 协程中的 API,经常会用到闭包写法。其实闭包并不是 Kotlin 中的新概念,在 Java 8 中就已经支持。

我们先以 Thread 为例,来看看什么是闭包:

// 创建一个 Thread 的完整写法
Thread(object : Runnable {
    override fun run() {
        ...
    }
})

// 满足 SAM,先简化为
Thread({
    ...
})

// 使用闭包,再简化为
Thread {
    ...
}

形如 Thread {...} 这样的结构中 {} 就是一个闭包。

在 Kotlin 中有这样一个语法糖:当函数的最后一个参数是 lambda 表达式时,可以将 lambda 写在括号外。这就是它的闭包原则。

在这里需要一个类型为 Runnable 的参数,而 Runnable 是一个接口,且只定义了一个函数 run,这种情况满足了 Kotlin 的 SAM,可以转换成传递一个 lambda 表达式(第二段),因为是最后一个参数,根据闭包原则我们就可以直接写成 Thread {...}(第三段) 的形式。

对于上文所使用的 launch 函数,可以通过闭包来进行简化 :

🏝️
launch {
    ...
}

3. 基本使用

launch 函数不是顶层函数,是不能直接用的,可以使用下面三种方法来创建协程:

🏝️
// 方法一,使用 runBlocking 顶层函数
runBlocking {
    getImage(imageId)
}

// 方法二,使用 GlobalScope 单例对象
//            👇 可以直接调用 launch 开启协程
GlobalScope.launch {
    getImage(imageId)
}

// 方法三,自行通过 CoroutineContext 创建一个 CoroutineScope 对象
//                                    👇 需要一个类型为 CoroutineContext 的参数
val coroutineScope = CoroutineScope(context)
coroutineScope.launch {
    getImage(imageId)
}
  • 方法一通常适用于单元测试的场景,而业务开发中不会用到这种方法,因为它是线程阻塞的。
  • 方法二和使用 runBlocking 的区别在于不会阻塞线程。但在 Android 开发中同样不推荐这种用法,因为它的生命周期会和 app 一致,且不能取消(什么是协程的取消后面的文章会讲)。
  • 方法三是比较推荐的使用方法,我们可以通过 context 参数去管理和控制协程的生命周期(这里的 context 和 Android 里的不是一个东西,是一个更通用的概念,会有一个 Android 平台的封装来配合使用)。