为什么需要协程
主线程(UI 线程)必须保持流畅。网络、磁盘、解码等耗时操作必须离开主线程。回调地狱难维护,协程用同步风格写异步。
回调嵌套深、取消困难;RxJava 强大但学习曲线陡。Kotlin 协程用语言级 suspend 提供轻量、结构化、可取消的并发。
suspend 与基础
suspend 标记"可挂起"的函数,挂起时释放线程,恢复时从断点继续,不阻塞线程。
suspend fun fetchUser(id: String): User {
delay(1000) // 非阻塞挂起
return api.getUser(id)
}
// 调用必须在协程/另一 suspend 内
viewModelScope.launch {
val user = fetchUser("42")
textView.text = user.name
}CoroutineScope 与结构化并发
协程必须运行在 Scope 中。viewModelScope、lifecycleScope 在对应生命周期结束时自动取消所有子协程。
class MyViewModel : ViewModel() {
fun load() {
viewModelScope.launch { // 自动绑定 VM 生命周期
try { state.value = ApiState.Success(repo.load()) }
catch (e: Exception) { state.value = ApiState.Error(e.message) }
}
}
}Dispatcher 调度器
决定协程跑在哪个线程:Dispatchers.Main 主线程、IO 磁盘网络、Default CPU 计算。
| 调度器 | 用途 |
|---|---|
Main | UI 更新 |
IO | 网络/数据库阻塞调用 |
Default | CPU 密集计算 |
Unconfined | 不限定(谨慎) |
withContext(Dispatchers.IO) { db.userDao().getAll() } // 切到 IO,返回结果回原线程async / await
async 启动并发并返回 Deferred,await() 取结果。并行多个独立请求时用它。
coroutineScope {
val user = async { api.user() }
val orders = async { api.orders() }
show(user.await(), orders.await()) // 并行,总耗时≈最慢
}Flow 数据流
Flow 是冷异步数据流,适合连续发出多个值(如数据库观察、事件流)。StateFlow/SharedFlow 是热流,用于 UI 状态。
fun search(query: String): Flow<List<Item>> = flow {
emit(repo.search(query)) // 冷流:被收集才执行
}.flowOn(Dispatchers.IO) // 上游在 IO 执行
// UI 收集(Compose/生命周期感知)
viewLifecycleOwner.lifecycleScope.launch {
viewModel.searchFlow.collect { render(it) }
}用 MutableStateFlow 持有状态,UI 收集 stateIn 后的只读流,天然契合 MVVM 单向数据流。
Channel
Channel 是协程间的通信管道(热),适合生产者-消费者场景,produce/actor 模式基于此。
val channel = Channel<Int>()
launch { for (x in 1..5) channel.send(x) }
launch { for (y in channel) println(y) }常见陷阱
耗时操作没切到 IO,或收集 Flow 时没在主线程。用 withContext(Dispatchers.Main) 或 flowOn/lifecycleScope 保证 UI 更新在主线程。
不会。它绑定 ViewModel 生命周期,屏幕旋转(Activity 重建)时 VM 存活,协程继续;只有在 VM 真正清除时才取消。