Kotlin 协程(Coroutines) 是一种轻量级的并发编程方案,专为简化异步编程和复杂并发操作而设计。例如 网络请求、数据库查询、大量计算 等。相比 Java 传统的多线程,协程更加高效、可控、易读。在 Android 开发中,协程能有效解决传统异步编程(如 AsyncTask、Handler、回调嵌套)的痛点,使代码更简洁、可读性更高,同时避免内存泄漏和线程阻塞问题。
线程是非常重量级的,它需要依靠操作系统的调度才能实现不同线程之间的切换,协程却可以仅在编程语言的层面就能实现不同协程之间的切换,从而大大提升了并发编程的运行效率。
什么是协程?
协程本质上是可暂停和恢复的任务,它不会阻塞线程,而是挂起(suspend),等到合适的时机再继续执行。
👉 你可以把它理解为 “改进版的线程”,但比线程更轻量、更灵活。
🔹 特点:
- 轻量级:多个协程可在单个线程上运行,避免了线程的创建和销毁的开销。
- 非阻塞:任务不会阻塞线程,而是挂起等待,从而提高资源利用率。
- 结构化并发:Kotlin 提供了作用域(CoroutineScope),确保协程不会泄漏,且任务可以被有序管理。
- 简洁代码:相比传统回调(Callback),协程代码更加直观,避免 “回调地狱”。
启动协程
Kotlin 协程通过launch或async启动:
import kotlinx.coroutines.*
fun main() = runBlocking { // 创建一个协程作用域
launch {
delay(1000L) // 挂起 1 秒(不会阻塞线程)
println("Kotlin 协程")
}
println("Hello,")
}🔹 输出:
Hello,
Kotlin 协程runBlocking {}:创建主协程,阻塞主线程,直到内部协程执行完毕。launch {}:创建一个新的协程,并在后台运行。delay(1000L):挂起当前协程 1 秒,但不会阻塞主线程。
挂起函数(suspend)
Kotlin 提供 suspend 关键字,使得普通函数变成可挂起的异步函数,可以在不阻塞当前线程的情况下暂停协程的执行。:
suspend fun fetchData(): String {
delay(2000L) // 模拟耗时任务
return "数据加载完成"
}
fun main() = runBlocking {
println("开始请求数据...")
val result = fetchData() // 调用挂起函数
println(result)
}🔹 输出:
开始请求数据...
(等待 2 秒)
数据加载完成注意:
suspend不能单独使用,只能在另一个suspend或协程作用域中调用。
suspendCoroutine
suspendCoroutine 是 Kotlin 协程提供的一个桥接函数,用于将传统的回调式异步代码转换为挂起函数。
挂起(Suspend)的完整生命周期:
- 触发挂起:当协程执行到
suspendCoroutine时,立即暂停当前协程(非阻塞线程) - 释放线程:原线程(如主线程)可继续执行其他任务(如渲染UI、处理点击事件)
- 等待回调:异步操作在后台执行(网络请求、数据库查询等)
- 恢复执行:通过
continuation.resume()唤醒协程,从挂起点继续执行
例子:
suspend fun <T> Call<T>.await(): T = suspendCoroutine { continuation ->
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
response.body()?.let { continuation.resume(it) } // 成功恢复
?: continuation.resumeWithException(...) // 空数据异常
}
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t) // 失败传递
}
})
}核心优势
| 特点 | 说明 |
|---|---|
| 不阻塞线程 | 挂起期间线程可以处理其他任务 |
| 代码同步写法 | 看起来像同步代码,实际是异步执行 |
| 回调地狱消失 | 不需要嵌套回调,代码更清晰 |
launch vs async
Kotlin 提供了两种方式来创建协程:
| 方式 | 作用 |
|---|---|
launch | 启动一个协程,不会返回结果,适用于 执行任务 |
async | 启动一个协程,返回 Deferred<T>,适用于 计算并获取结果 |
示例:launch(不返回值)
launch {
delay(1000L)
println("使用 launch 启动的协程")
}示例:async(返回结果)
val deferred = async {
delay(1000L)
"Hello, Kotlin" // 返回值
}
println(deferred.await()) // 等待结果async 返回 Deferred<T>,必须调用 await() 获取值。
协程作用域(CoroutineScope)
Kotlin 提供了多个协程作用域:
| 作用域 | 作用 |
|---|---|
GlobalScope.launch | 全局协程,生命周期与应用相同,不推荐使用 |
runBlocking | 阻塞当前线程,通常用于测试或 main 函数 |
CoroutineScope | 推荐方式,提供结构化并发,管理协程生命周期 |
**示例:使用 **CoroutineScope
class MyRepository {
// 关键点 ①:创建自定义协程作用域,指定 IO 线程池
private val scope = CoroutineScope(Dispatchers.IO)
fun fetchData() {
// 关键点 ②:在 scope 内启动协程
scope.launch {
val data = apiRequest() // 挂起函数
println(data)
}
}
// 关键点 ③:模拟网络请求的挂起函数
suspend fun apiRequest(): String {
delay(2000L) // 模拟耗时
return "数据加载完成"
}
}代码解释
🔑 关键点 ①:CoroutineScope(Dispatchers.IO)
- 作用:创建一个独立的协程作用域,所有在此作用域内启动的协程共享同一个生命周期。
- Dispatchers.IO:专门用于执行 I/O 密集型任务(如网络请求、文件读写),底层使用线程池优化。
对比:如果使用 GlobalScope,协程生命周期与整个应用绑定,容易泄漏。
🔑 关键点 ②:scope.launch
- 功能:在指定作用域内启动一个协程,自动继承
scope的调度器和生命周期。 - 优势:当调用
scope.cancel()时,所有通过该作用域启动的协程都会被自动取消。
🔑 关键点 ③:suspend fun apiRequest()
- 挂起函数:用
suspend修饰,可在协程内暂停执行(如delay)而不阻塞线程。 - 线程切换:
apiRequest运行在Dispatchers.IO线程,避免主线程卡顿。
优点:
- 确保
fetchData()在scope结束时自动取消,避免内存泄漏。 - 更易管理协程生命周期。
**示例:使用 **CoroutineScope
- 管理协程的生命周期,确保协程不会泄漏(例如 Activity 销毁时自动取消协程)。
- Android 中常用
viewModelScope(ViewModel 内)或lifecycleScope(Activity/Fragment 内)。
class MyViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch {
val data = fetchData()
updateUI(data)
}
}
}Dispatchers(调度器)
协程可以指定运行在哪个线程:
Kotlin 提供了 3 种主要的 Dispatchers(调度器):
| Dispatcher | 作用 |
|---|---|
Dispatchers.Main | 主线程(UI 更新,Android 开发常用) |
Dispatchers.IO | IO 线程(适用于数据库、网络请求) |
Dispatchers.Default | CPU 密集型(适用于计算任务) |
Dispatchers.Unconfined | 不指定线程,继承调用方 |
示例:切换调度器
fun main() = runBlocking {
launch(Dispatchers.IO) {
val data = fetchData()
withContext(Dispatchers.Main) {
println(data) // 切换回主线程更新 UI
}
}
}withContext(Dispatchers.Main) {}切换线程,在 UI 线程更新数据。
协程取消
协程可以手动取消:
fun main() = runBlocking {
val job = launch {
repeat(1000) { i ->
println("执行任务 $i ...")
delay(500L)
}
}
delay(2000L) // 运行 2 秒后取消
job.cancel()
println("协程已取消")
}🔹 输出(只运行 4 次后取消):
执行任务 0 ...
执行任务 1 ...
执行任务 2 ...
执行任务 3 ...
协程已取消协程的取消是协作式的(Cooperative),即:
- 只有可挂起的函数(如
delay())才能响应取消。 - 如果协程一直在计算任务,不会自动取消,需主动检测
isActive:
fun main() = runBlocking {
val job = launch {
var i = 0
while (isActive) { // 监听协程是否活跃
println("执行任务 ${i++} ...")
}
}
delay(2000L)
job.cancel() // 取消协程
}结构化并发(CoroutineScope)
如果你在 launch 里启动多个子协程,当父协程取消时,子协程也会被自动取消:
fun main() = runBlocking {
val parentJob = launch {
launch {
repeat(10) {
println("子协程执行中 $it")
delay(500L)
}
}
}
delay(2000L)
parentJob.cancel() // 取消父协程,子协程也会终止
println("所有协程已取消")
}总结
✅ Kotlin 协程 提供了一种简单高效的方式来实现异步编程,它的优势:
- 轻量级,不会像线程那样消耗大量资源。
- 不会阻塞主线程,UI 线程流畅不卡顿。
- 结构化并发,避免内存泄漏,任务管理更方便。
- 支持挂起(suspend)函数,让异步代码写起来像同步代码。
问题
是不是任意一个代码块其实都可以相当于一个作用域在其中用协程?
是的,几乎所有代码块都可以作为一个作用域,在其中启动协程,但 不同作用域的生命周期、特性和适用场景不同。
1. 什么是“作用域”?
在 Kotlin 协程中,作用域(Scope) 指的是 协程运行的上下文,它决定了:
- 协程的生命周期(何时启动、何时取消)
- 协程在哪个线程执行(
Dispatchers.Main、Dispatchers.IO等) - 协程的层级结构(父协程取消时,子协程是否被取消)
Kotlin 提供了 多种作用域,可以在不同的代码块中使用协程。
2. Kotlin 常见的协程作用域
下面列出了 可以作为协程作用域的代码块,并比较它们的特点。
| 作用域 | 使用方式 | 特点 | 适用场景 |
|---|---|---|---|
GlobalScope.launch {} | 全局作用域 | 不推荐,生命周期长,可能导致内存泄漏 | 长时间运行的任务(不推荐) |
viewModelScope.launch {} | ViewModel 作用域 | 绑定 ViewModel,ViewModel 销毁时自动取消 | 网络请求、数据库查询 |
lifecycleScope.launch {} | Activity/Fragment 作用域 | 绑定 UI 生命周期,界面销毁时自动取消 | UI 相关任务(如动画、计时器) |
coroutineScope {} | 局部作用域 | 继承父协程的作用域,内部所有子协程都必须完成,才会继续执行外部代码 | 需要并行执行多个任务时 |
liveData {} | LiveData 作用域 | 绑定 LiveData,当没有观察者时自动取消 | 直接返回 LiveData 结果 |
runBlocking {} | 阻塞作用域 | 主线程调用时会阻塞整个线程,一般用于测试 | 不推荐在 UI 线程中使用 |
3. 示例:不同作用域的协程用法
(1)全局作用域(GlobalScope,不推荐)
GlobalScope.launch {
delay(1000)
println("GlobalScope 协程运行完毕")
}✅ 特点:
- 不受任何组件的生命周期影响
- 手动管理取消,否则可能导致内存泄漏❌ 不推荐在 Android 开发中使用
(2)ViewModel 作用域(推荐用于网络请求)
class MyViewModel : ViewModel() {
fun fetchData() {
viewModelScope.launch(Dispatchers.IO) { // 在 IO 线程运行
val result = getDataFromNetwork()
withContext(Dispatchers.Main) {
dataLiveData.value = result // 回到主线程更新 UI
}
}
}
}✅ 特点:
viewModelScope绑定 ViewModel 生命周期- ViewModel 销毁时,协程自动取消
- 适用于网络请求、数据库查询等任务
(3)Activity/Fragment 作用域(lifecycleScope)
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
delay(1000)
println("Activity 作用域中的协程")
}
}
}✅ 特点:
lifecycleScope** 绑定 Activity/Fragment 生命周期**- Activity/Fragment 关闭时,协程自动取消
- 适用于 UI 任务,如动画、计时器
(4)局部作用域 coroutineScope {}(适用于并行任务)
suspend fun fetchData() {
coroutineScope {
launch {
delay(1000)
println("子任务1完成")
}
launch {
delay(2000)
println("子任务2完成")
}
}
println("所有子任务完成,继续执行")
}✅ 特点:
- 继承父协程作用域
- 所有子协程完成后,才会执行
coroutineScope之后的代码 - 适用于需要并行执行多个任务的情况
(5)LiveData 作用域 liveData {}(适用于 ViewModel 返回 LiveData)
fun searchPlaces(query: String) = liveData(Dispatchers.IO) {
val result = try {
val placeResponse = networkRequest(query)
Result.success(placeResponse)
} catch (e: Exception) {
Result.failure<List<Place>>(e)
}
emit(result) // 发送数据
}✅ 特点:
- liveData 代码块本身是一个协程
- 当
LiveData没有观察者时,协程会自动取消 - 适用于 ViewModel 直接返回
LiveData的场景
(6)阻塞作用域 runBlocking {}(仅用于测试)
fun main() {
runBlocking {
launch {
delay(1000)
println("runBlocking 内部的协程")
}
println("主线程阻塞中")
}
}✅ 特点:
- 会阻塞当前线程,直到协程执行完毕
- 适用于测试,但不推荐在 Android UI 线程中使用
4. 总结
✔ Kotlin 任何代码块都可以作为协程作用域,但选择合适的作用域很重要:
- UI 任务 →
lifecycleScope - 网络/数据库 →
viewModelScope - 局部并发任务 →
coroutineScope - LiveData 绑定数据 →
liveData {}
如果你的代码块涉及协程,尽量选择合适的作用域,避免内存泄漏或意外取消任务!🚀
coroutineScope 和之前提到的 suspendCoroutine 很容易混淆
coroutineScope 和 suspendCoroutine 是 Kotlin 协程中两个容易混淆的概念,但它们的作用和用途是完全不同的。
1.coroutineScope
coroutineScope是一个挂起函数,用于创建一个新的协程作用域。它的主要特点是:
- 结构化并发:它会等待其内部的所有协程完成后再结束。
- 异常传播:如果
coroutineScope内部的某个协程失败,它会取消所有其他协程,并将异常传播到外层。 - 作用域管理:它不会启动新的协程,而是创建一个新的作用域,用于管理内部的协程。
使用场景:
- 当你需要在挂起函数中启动多个协程,并确保它们都完成后再继续执行时。
- 当你需要结构化并发,避免协程泄漏(即协程未正确取消)时。
示例:
suspend fun fetchData() = coroutineScope {
val deferred1 = async { fetchFromNetwork1() }
val deferred2 = async { fetchFromNetwork2() }
val result1 = deferred1.await()
val result2 = deferred2.await()
result1 + result2
}coroutineScope会等待deferred1和deferred2都完成后再返回结果。- 如果
deferred1或deferred2失败,coroutineScope会取消另一个协程,并抛出异常。
2.suspendCoroutine
suspendCoroutine是一个底层挂起函数,用于将回调风格的代码转换为挂起函数。它的主要特点是:
- 挂起协程:它会挂起当前协程,直到手动恢复。
- 回调转换:它通常用于将基于回调的 API(如 Retrofit 的
Call)转换为挂起函数。
使用场景:
- 当你需要将回调风格的代码(如网络请求、数据库查询)转换为挂起函数时。
- 当你需要手动控制协程的挂起和恢复时。
示例:
ksuspend fun fetchFromNetwork(): String = suspendCoroutine { continuation ->
someCallbackBasedApi.enqueue(object : Callback<String> {
override fun onResponse(call: Call<String>, response: Response<String>) {
val body = response.body()
if (body != null) {
continuation.resume(body) // 恢复协程并返回数据
} else {
continuation.resumeWithException(RuntimeException("Response body is null"))
}
}
override fun onFailure(call: Call<String>, t: Throwable) {
continuation.resumeWithException(t) // 恢复协程并抛出异常
}
})
}suspendCoroutine会挂起当前协程,直到调用continuation.resume或continuation.resumeWithException。- 它通常用于将回调风格的 API 转换为挂起函数。
3.coroutineScopevssuspendCoroutine
| 特性 | coroutineScope | suspendCoroutine |
|---|---|---|
| 用途 | 创建新的协程作用域,管理结构化并发 | 将回调风格的代码转换为挂起函数 |
| 是否挂起协程 | 是(等待内部协程完成) | 是(直到手动恢复) |
| 是否启动新协程 | 否(仅创建作用域) | 否(仅挂起当前协程) |
| 异常传播 | 是(内部协程失败会取消其他协程) | 是(通过resumeWithException抛出异常) |
| 典型使用场景 | 并发执行多个协程,并等待它们完成 | 将回调 API 转换为挂起函数 |
4.代码对比
使用coroutineScope:
suspend fun fetchData() = coroutineScope {
val deferred1 = async { fetchFromNetwork1() }
val deferred2 = async { fetchFromNetwork2() }
deferred1.await() + deferred2.await()
}使用suspendCoroutine:
suspend fun fetchFromNetwork(): String = suspendCoroutine { continuation ->
someCallbackBasedApi.enqueue(object : Callback<String> {
override fun onResponse(call: Call<String>, response: Response<String>) {
val body = response.body()
if (body != null) {
continuation.resume(body)
} else {
continuation.resumeWithException(RuntimeException("Response body is null"))
}
}
override fun onFailure(call: Call<String>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}5.总结
coroutineScope:用于创建新的协程作用域,管理结构化并发,适合在挂起函数中启动多个协程。suspendCoroutine:用于将回调风格的代码转换为挂起函数,适合与基于回调的 API 交互。
更新: 2026-02-27 19:44:17
原文: https://www.yuque.com/dongpozhouzi-mshe3/zhm85g/ufwor7spuuflk22r