接下来我们将学习如何在Android应用中使用Kotlin协程。这是管理后台线程的推荐方法,可通过减少回调需求来简化代码。协程是一项Kotlin功能,可将长时间运行的任务(例如数据库或网络访问)的异步回调转换为顺序代码。
下面给出一个代码段,从中您可以大致了解将要进行的操作。
javascript
// Async callbacks
networkRequest { request ->
// Successful network request
databaseSave(result) { row ->
// Result saved
}
}
系统使用协程将基于回调的代码转换为顺序代码。
scss
// The same code with coroutines
val result = networkRequest()
// Successful network request
databaseSave(result)
// Result saved
您将从一款使用架构组件构建的现有应用入手,该应用为长时间运行的任务使用回调样式。
下载代码: 从命令行使用下列命令克隆 GitHub 代码库:
shell
$ git clone https://github.com/googlecodelabs/kotlin-coroutines.git
首先,我们来看看起始示例应用。
- 如果已下载
kotlin-coroutines
zip文件,请将其解压。 - 在Android Studio中打开
coroutines-codelab
项目。 - 选择
start
应用模块。 - 点击
Run按钮,然后选择模拟器或者链接必须能够运行的Android设备。此时应显示Kotlin协程屏幕:

在您点按屏幕后,此初始应用会使用线程在经过短暂延迟后增加计数。它还会从网络中提取新标题并将其显示在屏幕上。现在就试试看吧,您应该会看到计数和消息在短暂延迟后出现变换。
此应用使用架构组件将MainActivity
中的界面代码与MainViewModel
的应用逻辑分割开。

MainActivity
显示界面、注册点击监听器,并且可以显示Snackbar
。它将事件传递给MainViewModel
,并根据MainViewModel
中的LiveData
更新屏幕。MainViewModel
处理onMainViewClicked
中的事件,并将使用LiveData
与MainActivity
通信。Executor
定义BACKGROUND
,后者可以在后台线程上运行内容。TitleRepository
从网络提取结果,并将结果保存到数据库。
向项目添加协程
要在Kotlin中使用协程,您必须在项目的build.gradle(Module: app)
文件中添加coroutines-core
库。
Android上的写成作为核心库和Android专用扩展函数提供:
kotlinx-coroutines-core
用于在Kotlin中使用协程的主接口kotlin-coroutines-android
在协程中指出Android主线程。
此初始应用已在build.gradle
中包含依赖项。创建新的应用项目时,您需要打开build.gradle(Module:app)
并将写成依赖项添加到项目中。
arduino
dependencies {
...
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:x.x.x"
implementation "org.jetbranins.kotlinx-coroutines-android:x.x.x"
}
在Android上,避免阻塞主线程是非常必要的。主线程是一个处理所有界面更新的线程,也是调用所有点击处理程序和其他界面回调的线程。因此,主线程必须顺畅运行才能确保出色的用户体验。
为了避免用户在使用您的应用时感觉到任何卡顿,主线程必须每隔16毫秒或更短时间 更新一次屏幕,也就是每秒约60帧。许多常见任务所需的时间都比这个时间长,例如解析大型JSON数据集、将数据写入数据库或从网络提取数据。因此,从主线程调用此类代码可能会导致应用暂停、卡顿甚至冻结。如果您阻塞主线程太久,应用甚至可能会崩溃并显示一个应用无响应对话框。
回调模式
在不阻塞主线程的情况下执行长时间运行的任务的一种模式是回调。通过使用回调,您可以在后台线程上启动长时间运行的任务。任务完成后,系统会调用回调函数,以在主线程上告知您结果。
我们来看一个回调模式的示例:
kotlin
// Slow request with callbacks
@UiThread
fun makeNetworkRequest() {
// The slow network request runs on another thread
slowFetch { result ->
// When the result is ready, this callback will get the result
show(result)
}
// makeNetworkRequest() exits after calling slowFetch without waiting for the result
}
由于此代码带有@UiThread
注解,因此它必须足够快地运行以在主线程上执行,也就是说,它需要非常快地返回,以便下一次屏幕更新不会出现延迟。不过,由于slowFetch
需要几秒钟甚至几分钟才能完成,因此主线程不能等待结果。show(result)
回调允许slowFetch
在后台线程上运行,并在准备就绪后返回结果。
使用协程移除回调
回调是一种很好的模式,但也存在缺点。过多使用回调的代码可能会变得难以读取和推演。此外,回调也不允许使用某些语言功能,例如异常。
Kotlin协程使您能够基于回调的代码转换为顺利代码。顺序编写的代码通常易于阅读,甚至可以使用异常等语言功能。
最后,两者所做的事情完全相同:等待长时间运行的任务获得结果,然后继续执行。不过,两者的代码看起来却截然不同。
关键字suspend
是Kotlin将函数(即函数类型)标记为可供协程使用的方式。当协程调用标记为suspend
的函数时,它不会像常规函数调用一样在函数返回之前进行阻塞,而是挂起 执行,直到结果就绪为止,然后从上次停止的位置恢复 并使用返回的结果。当它挂起并等待结果时,它会取消阻塞正在运行它的线程,以便其他函数或协程可以运行。
例如,在下面的代码中,makeNetworkRequest()
和slowFetch()
都是suspend
函数。
kotlin
// Slow request with coroutines
@UiThread
suspend fun makeNetworkRequest() {
// slowFetch is another suspend function so instead of
// blocking the main thread makeNetworkRequest will `suspend` until the result is
// ready
val result = slowFecth()
// continue to execute after the result is ready
show(result)
}
// slowFetch is main-safe using coroutines
suspend fun slowFetch(): SlowResul {...}
与回调版本一样,makeNetworkRequest
必须立即从主线程返回,因为它被标记为@UiThread
。这意味着,它通常无法调用slowFetch
等阻塞方法。这里体现了suspend
关键字的神奇之处。
bash
重要提示:suspend关键字不指定运行代码的线程。挂起函数可以在后台线程或主线程上运行。
与基于回调的代码相比,协程代码可以利用更少的代码实现取消阻塞当前线程的相同效果。由于它具有顺序样式,因此可以轻松地链接多个长时间运行的任务,而无需创建多个回调。例如,如果代码从两个网络端点提取结果并将结果保存到数据库,此代码可以编写为协程中的函数,而无需回调。类似以下代码:
kotlin
// Request data from network and save it to database with coroutines
// Because of the @WorkerThread, this function cannot be called on the
// main thread without causing an error
@WorkerThread
suspend fun makeNetworkRequest() {
// slowFetch and anotherFetch are suspend functions
val slow = slowFetch()
val another = anotherFetch()
// save is the regular function and will block this thread
database.save(slow, another)
}
// slowFetch is main-safe using coroutines
suspend fun slowFetch(): SlowResult { ... }
// anotherFetch is main-safe using coroutines
suspend fun anotherFetch(): AnotherResult { ... }
协程的其他名称
其他语言的async
和await
模式基于协程。如果您熟悉此模式,就会发现suspend
关键字类似于async
。但Kotlin中,调用suspend
函数时,await()
是隐式的。
Kotlin有一个Deferred.await()
方法,用于等待一个通过async
构建器启动的协程的结果。