Android 开发手册ANDROID DEV

CHAPTER 02并发模型 · CONCURRENCY

协程与并发

从回调地狱到 suspend/await 的轻量并发。理解结构化并发、Dispatcher、Flow 数据流与 Channel,把耗时操作移出主线程。

开始阅读难度进阶约3小时
02.1

为什么需要协程

主线程(UI 线程)必须保持流畅。网络、磁盘、解码等耗时操作必须离开主线程。回调地狱难维护,协程用同步风格写异步。

老办法:回调与 RxJava

回调嵌套深、取消困难;RxJava 强大但学习曲线陡。Kotlin 协程用语言级 suspend 提供轻量、结构化、可取消的并发。

02.2

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
}
02.3

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) }
        }
    }
}
02.4

Dispatcher 调度器

决定协程跑在哪个线程:Dispatchers.Main 主线程、IO 磁盘网络、Default CPU 计算。

调度器用途
MainUI 更新
IO网络/数据库阻塞调用
DefaultCPU 密集计算
Unconfined不限定(谨慎)
withContext(Dispatchers.IO) { db.userDao().getAll() }  // 切到 IO,返回结果回原线程
02.5

async / await

async 启动并发并返回 Deferred,await() 取结果。并行多个独立请求时用它。

coroutineScope {
    val user = async { api.user() }
    val orders = async { api.orders() }
    show(user.await(), orders.await())   // 并行,总耗时≈最慢
}
02.6

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) }
}
StateFlow 作 UI 状态

用 MutableStateFlow 持有状态,UI 收集 stateIn 后的只读流,天然契合 MVVM 单向数据流。

02.7

Channel

Channel 是协程间的通信管道(热),适合生产者-消费者场景,produce/actor 模式基于此。

val channel = Channel<Int>()
launch { for (x in 1..5) channel.send(x) }
launch { for (y in channel) println(y) }
02.8

常见陷阱

为什么 UI 更新还是在主线程外崩溃?

耗时操作没切到 IO,或收集 Flow 时没在主线程。用 withContext(Dispatchers.Main) 或 flowOn/lifecycleScope 保证 UI 更新在主线程。

viewModelScope 会在配置变更时取消吗?

不会。它绑定 ViewModel 生命周期,屏幕旋转(Activity 重建)时 VM 存活,协程继续;只有在 VM 真正清除时才取消。

02.9

实战练习

延伸资源