想系统掌握 Kotlin 协程,要层层递进学习,既覆盖新手必备的基础,也包含高级开发中常用的进阶知识点。
一、基础入门:从零开始理解协程
1. 协程的本质(再强化认知)
协程是 可暂停、可恢复的计算过程 ,由 Kotlin 协程框架(kotlinx-coroutines)管理,而非操作系统内核:
- 线程:内核态,切换成本高(MB 级栈空间);
- 协程:用户态,切换成本低(KB 级栈空间),一个线程可承载上万协程。
协程的核心:同步代码的写法,异步非阻塞的执行。
2. 环境准备(必做)
Gradle 依赖(最新稳定版)
gradle
// 核心库(JVM/桌面端)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
// 安卓适配(安卓项目)
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0")
// 测试支持
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0")
核心语法:第一个可运行的协程
kotlin
import kotlinx.coroutines.*
fun main() {
// 1. runBlocking:阻塞当前线程的协程作用域(仅用于测试/主函数)
runBlocking {
println("主线程开始:${Thread.currentThread().name}")
// 2. launch:启动无返回值的协程,返回 Job 控制生命周期
val job = launch(Dispatchers.Default) {
repeat(3) { i ->
println("协程执行中 $i:${Thread.currentThread().name}")
delay(500) // 挂起函数:暂停协程,不阻塞线程
}
}
delay(800) // 主线程挂起800ms
println("主线程取消协程")
job.cancel() // 取消协程
job.join() // 等待协程终止
println("主线程结束")
}
}
输出:
plaintext
主线程开始:main @coroutine#1
协程执行中 0:DefaultDispatcher-worker-1 @coroutine#2
协程执行中 1:DefaultDispatcher-worker-1 @coroutine#2
主线程取消协程
主线程结束
3. 核心基础概念(入门必记)
| 概念 | 作用 |
|---|---|
CoroutineScope |
协程作用域,管理协程生命周期(核心:结构化并发) |
Job |
协程的句柄,可 cancel()/join()/isActive 控制协程 |
suspend |
挂起函数标记,只能在协程 / 其他挂起函数中调用,可暂停 / 恢复 |
Dispatchers |
调度器,指定协程运行线程(Main/IO/Default/Unconfined) |
delay() |
挂起函数,暂停协程(非阻塞),仅用于协程内 |
runBlocking |
阻塞当前线程的协程作用域(仅测试用,禁止业务代码使用) |
二、核心进阶:掌握协程的关键能力
1. 结构化并发(协程的核心设计原则)
核心思想:协程的生命周期绑定到作用域,父协程取消 → 所有子协程自动取消,杜绝协程泄漏。
错误示例(GlobalScope 导致泄漏)
kotlin
// 禁止使用!GlobalScope 是全局作用域,协程不受生命周期管理
GlobalScope.launch {
delay(10000) // 长时间任务
println("协程执行(即使页面已销毁)")
}
正确示例(自定义作用域)
kotlin
// 1. 自定义作用域(推荐方式:CoroutineScope + 上下文)
val myScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
// 2. 启动子协程
val parentJob = myScope.launch {
val childJob1 = launch { fetchData() }
val childJob2 = launch { processData() }
}
// 3. 销毁时取消作用域(如Activity onDestroy)
myScope.cancel() // 父协程取消 → 所有子协程自动取消
2. 挂起函数(suspend)深度理解
- 挂起函数不是线程切换:只是标记 "这个函数可能暂停协程";
- 挂起 ≠ 阻塞:挂起时线程可执行其他协程,阻塞时线程空闲;
- 挂起函数的协作式取消 :挂起函数(如 delay/withContext)会检查协程是否活跃,非活跃则抛出
CancellationException。
自定义挂起函数(CPU 密集型任务的取消兼容)
kotlin
suspend fun longRunningTask() = withContext(Dispatchers.Default) {
for (i in 0 until 10000) {
// 手动检查协程是否活跃(CPU密集型任务必须加,否则无法取消)
ensureActive() // 等价于 if (!isActive) throw CancellationException()
Thread.sleep(1) // 模拟CPU计算(非挂起函数,需手动检查)
}
}
3. 带返回值的协程:async/await
async 启动协程并返回 Deferred<T>,通过 await() 获取结果(会挂起直到结果返回)。
并行执行多个任务(核心场景)
kotlin
suspend fun loadData(): Pair<String, Int> = coroutineScope {
// 并行启动两个协程
val userDeferred = async { fetchUser() }
val scoreDeferred = async { fetchScore() }
// 等待结果(总耗时≈最长的那个任务,而非累加)
val user = userDeferred.await()
val score = scoreDeferred.await()
return@coroutineScope Pair(user, score)
}
// 模拟耗时任务
suspend fun fetchUser(): String {
delay(1000)
return "张三"
}
suspend fun fetchScore(): Int {
delay(1500)
return 95
}
4. 协程的异常处理
协程的异常处理分两种场景:
场景 1:单个协程的异常捕获(try-catch)
kotlin
coroutineScope.launch {
try {
val data = withContext(Dispatchers.IO) {
throw RuntimeException("网络请求失败")
fetchData()
}
} catch (e: Exception) {
println("捕获异常:${e.message}")
}
}
场景 2:作用域全局异常处理(CoroutineExceptionHandler)
kotlin
// 1. 定义异常处理器
val exceptionHandler = CoroutineExceptionHandler { context, throwable ->
println("全局捕获异常:${throwable.message},协程:${context[CoroutineName]}")
}
// 2. 作用域绑定异常处理器
val scope = CoroutineScope(SupervisorJob() + exceptionHandler + CoroutineName("MyScope"))
// 3. 启动协程(异常会被全局处理器捕获)
scope.launch {
throw RuntimeException("协程异常")
}
关键:SupervisorJob vs Job
Job:父协程会因子协程异常而取消,且取消所有其他子协程(默认);SupervisorJob:子协程异常不影响父协程和其他子协程(适合多任务并行,如列表多 item 加载)。
5. 协程的取消与超时
(1)协程取消(协作式)
kotlin
val job = scope.launch {
while (isActive) { // 检查协程是否活跃
println("协程运行中")
delay(100)
}
}
// 取消协程
job.cancel()
job.join() // 等待取消完成(可简写为 job.cancelAndJoin())
(2)超时处理(withTimeout/withTimeoutOrNull)
kotlin
suspend fun fetchDataWithTimeout(): String? {
// withTimeout:超时抛出 TimeoutCancellationException
// withTimeoutOrNull:超时返回 null(更安全)
return withTimeoutOrNull(1000) {
delay(1500) // 模拟超时
"数据"
}
}
// 使用
val data = fetchDataWithTimeout() ?: "默认数据(超时)"
6. 协程通道(Channel):协程间通信
通道是协程间的 "管道",用于协程间传递数据(类似阻塞队列,但非阻塞)。
基础用法
kotlin
suspend fun channelDemo() = coroutineScope {
// 创建通道,指定容量(无缓冲则发送方挂起直到接收方接收)
val channel = Channel<Int>(capacity = 2)
// 发送方协程
launch {
repeat(3) {
println("发送数据 $it")
channel.send(it) // 发送数据,满了则挂起
delay(500)
}
channel.close() // 关闭通道
}
// 接收方协程
launch {
// 遍历通道(通道关闭后退出循环)
for (data in channel) {
println("接收数据 $data")
}
}
}
7. 协程流(Flow):响应式数据流(核心进阶)
Flow 是协程的响应式流,用于处理异步序列数据(如分页加载、实时数据推送),替代 RxJava。
基础 Flow 示例
kotlin
// 1. 定义Flow(冷流:订阅时才执行)
fun fetchNumbers(): Flow<Int> = flow {
println("Flow开始执行")
repeat(3) {
delay(500)
emit(it) // 发射数据
}
}
// 2. 收集Flow(必须在协程内)
suspend fun collectFlow() {
fetchNumbers()
.flowOn(Dispatchers.IO) // 指定发射数据的线程
.collect { // 收集数据(在当前协程线程执行)
println("收集到:$it,线程:${Thread.currentThread().name}")
}
}
Flow 常用操作符(实战高频)
kotlin
fetchNumbers()
.map { it * 2 } // 数据转换
.filter { it > 2 } // 数据过滤
.debounce(300) // 防抖(如搜索框输入)
.distinctUntilChanged() // 去重(仅当数据变化时发射)
.catch { e -> println("捕获异常:${e.message}") } // 异常处理
.onCompletion { println("Flow完成") } // 完成回调
.collect { println("最终结果:$it") }
三、实战精通:协程在项目中的最佳实践
1. 安卓项目中的协程用法(ViewModel + CoroutineScope)
kotlin
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class MyViewModel : ViewModel() {
// 1. ViewModel 内置作用域(自动绑定生命周期,页面销毁自动取消)
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState
// 2. 业务逻辑:加载数据
fun loadData() {
viewModelScope.launch(Dispatchers.IO) {
try {
val data = fetchDataFromApi()
_uiState.value = UiState.Success(data)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message ?: "未知错误")
}
}
}
private suspend fun fetchDataFromApi(): String {
delay(1000)
return "服务器返回数据"
}
}
// UI状态密封类
sealed class UiState {
object Loading : UiState()
data class Success(val data: String) : UiState()
data class Error(val message: String) : UiState()
}
2. 并发任务的最佳实践
(1)并行执行 + 结果合并
kotlin
suspend fun loadAllData(): Result<Data> = coroutineScope {
val userDeferred = async { fetchUser() }
val orderDeferred = async { fetchOrder() }
val goodsDeferred = async { fetchGoods() }
return@coroutineScope try {
val user = userDeferred.await()
val order = orderDeferred.await()
val goods = goodsDeferred.await()
Result.success(Data(user, order, goods))
} catch (e: Exception) {
// 取消所有未完成的协程
listOf(userDeferred, orderDeferred, goodsDeferred).forEach { it.cancel() }
Result.failure(e)
}
}
data class Data(val user: String, val order: String, val goods: String)
(2)限流执行(如批量请求)
kotlin
suspend fun batchRequest(ids: List<Int>) = coroutineScope {
// 限制并发数为3
val semaphore = kotlinx.coroutines.sync.Semaphore(3)
ids.forEach { id ->
launch {
semaphore.acquire() // 获取许可,满了则挂起
try {
fetchItem(id)
} finally {
semaphore.release() // 释放许可
}
}
}
}
3. 协程测试(单元测试)
kotlin
import kotlinx.coroutines.test.runTest
import org.junit.Test
class CoroutineTest {
@Test
fun testFetchData() = runTest { // 协程测试专用作用域
val data = fetchData()
assert(data == "模拟从服务器获取的数据")
}
}
四、避坑指南:新手常犯的错误
- 滥用 runBlocking:仅用于测试 / 主函数,业务代码中使用会阻塞线程(如安卓主线程);
- 使用 GlobalScope:全局作用域无生命周期管理,必然导致协程泄漏;
- CPU 密集型任务未检查 isActive:协程无法取消,导致资源浪费;
- 忽略协程异常:未捕获的异常会导致协程崩溃,甚至崩溃进程;
- Flow 未指定 flowOn:发射数据的线程错误(如在主线程执行耗时操作)。
五、精通进阶:协程底层原理(可选)
1. 协程的实现原理
- 协程基于 状态机 实现:挂起函数编译后会被拆分为多个状态,暂停时保存状态,恢复时恢复状态;
- 协程调度基于 线程池 + 任务队列:Dispatchers.IO/Default 底层是线程池,协程任务提交到队列中执行;
- 挂起函数的本质:通过
Continuation(续体)实现暂停 / 恢复,suspend是语法糖。
2. 核心源码关键点
CoroutineScope:本质是CoroutineContext的封装;CoroutineContext:协程上下文,包含 Job、Dispatchers、异常处理器等;Continuation:续体,保存协程的执行状态和恢复逻辑。
总结
核心要点回顾
- 基础核心 :协程是轻量级线程,通过
CoroutineScope实现结构化并发,launch/async启动协程,Dispatchers指定线程; - 进阶能力:掌握挂起函数的协作式取消、异常处理、Channel 通信、Flow 响应式数据流;
- 实战原则 :协程必须绑定生命周期(如 ViewModelScope),杜绝 GlobalScope,CPU 密集型任务手动检查
isActive; - 避坑关键:合理处理异常、正确使用调度器、避免阻塞主线程。
从入门到精通的路径:先掌握基础用法 → 结合项目实战(如安卓 ViewModel)→ 理解核心进阶特性(Flow/Channel)→ 最后了解底层原理。建议先动手写代码,再逐步深入原理,这样更容易掌握。