核心概念速览
- 协程(Coroutine):轻量级线程,可在挂起点暂停与恢复。
- 挂起函数(suspend):可在不阻塞线程的情况下挂起协程。
- 作用域(CoroutineScope):定义协程的生命周期与上下文。
- 调度器(Dispatcher):决定协程运行的线程。
suspend fun fetchUser(): User {
return apiService.getUser()
}
GlobalScope.launch {
val user = fetchUser()
println(user)
}
实际 Android 开发不推荐使用
GlobalScope,请使用生命周期感知的作用域。
协程作用域
| 作用域 | 用途 |
|---|---|
GlobalScope | 全局生命周期,避免使用 |
lifecycleScope | LifecycleOwner(Activity/Fragment) |
viewModelScope | ViewModel 生命周期 |
CoroutineScope 自定义 | 业务层自定义生命周期 |
class MainViewModel : ViewModel() {
fun load() = viewModelScope.launch {
val data = repository.fetch()
_uiState.value = data
}
}
调度器与线程切换
| 调度器 | 描述 | 场景 |
|---|---|---|
Dispatchers.Main | 主线程,UI 更新 | 更新 UI、Compose 状态 |
Dispatchers.IO | IO 密集任务 | 网络请求、磁盘操作 |
Dispatchers.Default | CPU 密集任务 | 解析 JSON、加密运算 |
Dispatchers.Unconfined | 不限制线程,谨慎使用 | 调试、测试 |
lifecycleScope.launch(Dispatchers.IO) {
val result = api.getArticles()
withContext(Dispatchers.Main) {
render(result)
}
}
协程构建器
launch:返回Job,不关心返回值。async:返回Deferred<T>,用于并发计算。withContext:切换调度器,返回结果。
val user = async { repository.fetchUser() }
val posts = async { repository.fetchPosts() }
val homeData = awaitAll(user, posts)
异常处理
val handler = CoroutineExceptionHandler { _, throwable ->
Timber.e(throwable, "coroutine error")
}
viewModelScope.launch(handler) {
try {
repository.fetch()
} catch (e: IOException) {
_uiState.value = UiState.Error(e)
}
}
- 结构化并发确保子协程失败会取消父协程。
- 在
async中抛出的异常会延迟到await()调用时抛出。
结构化并发
suspend fun refreshData() = coroutineScope {
val userDeferred = async { repository.fetchUser() }
val feedDeferred = async { repository.fetchFeed() }
val user = userDeferred.await()
val feed = feedDeferred.await()
user to feed
}
- 使用
coroutineScope确保子任务完成或取消后才返回。
协程与传统回调的对比
| 特点 | 回调方式 | 协程方式 |
|---|---|---|
| 可读性 | 回调地狱 | 线性代码 |
| 错误处理 | 需逐层捕获 | 结构化并发统一处理 |
| 资源释放 | 容易泄漏 | 作用域取消自动释放 |
常见误区
- 在主线程执行 CPU 密集任务:请使用
Dispatchers.Default。 - 使用
runBlocking阻塞主线程:仅适合单元测试。 - 忘记取消协程:确保在
onCleared或相应生命周期中取消。
实战案例:从 Dart 视角看 Dispatcher
我是 Flutter 开发者,第一次写 Kotlin 协程时,最大的思维转换就是 Dispatcher——Dart 里根本没有这个概念。
Dart 是单线程事件循环:async/await 写完就完事,IO 操作自动丢给系统线程池,compute() 才需要手动开 Isolate。而 Kotlin 里,你必须显式决定协程跑在哪:Dispatchers.Main 更新 UI,Dispatchers.IO 做网络,Dispatchers.Default 做计算。选错了的后果很直接——我在看官方文档的 dispatcher 章节时注意到,Dispatchers.IO 和 Default 共享线程池但上限不同(IO 默认 64 线程,Default 是 CPU 核数),把 JSON 解析这种 CPU 活丢到 IO 线程池,等于在跟网络请求抢线程。
另一个坑是 GlobalScope。Dart 里没有”作用域”概念,Future 扔出去就不管了;Kotlin 里 GlobalScope.launch 是官方文档明确反对的写法——协程泄漏不会像 Dart 那样随 Isolate 结束而消失,它会一直占着。Android 里永远用 lifecycleScope / viewModelScope,让生命周期替你取消。
一句话总结:Dart 的并发是”省心但粗”,Kotlin 协程是”精细但要操心”。理解了 Dispatcher 这层,协程就入门了。
参考:Kotlin 官方文档 Coroutine dispatchers 章节(kotlinlang.org)
总结
- 协程提供轻量级并发机制,通过作用域与调度器管理生命周期和线程。
- 结构化并发有助于统一错误处理并避免资源泄漏。
- 掌握
launch、async、withContext等常用构建器,是进阶 Flow 与 Compose 的基础。
下一篇将把协程应用到 Android 实战,涵盖网络请求、Room 与 UI 协同。
