协程到底省了什么
协程不是开更多线程,而是在少量线程上挂起与恢复大量任务。挂起一个协程几乎不占线程,于是你能同时发起几十上百个网络请求,而不必为每个请求开线程。代码写成顺序样子,阻塞等待却不真的卡住线程。
协程的价值:用同步的代码结构,写出异步的执行效率,并且天然可取消、可组合。
这篇在"会用 launch"的基础上,把挂起、调度、取消与 Flow 讲透。
挂起点与 suspend
suspend 标记的函数只能在协程或另一个 suspend 里调用。它的特殊之处是可以在不阻塞线程的情况下暂停——执行到挂起点时,协程把线程让出去做别的事,等结果回来再恢复。
suspend fun fetchUser(): User {
delay(800) // 挂起,不占线程
return User("码农")
}
delay 不是 Thread.sleep:它挂起协程而不是阻塞线程。理解这一点,就理解了协程和线程池的本质区别。
调度器与作用域
调度器决定协程跑在哪类线程上:Dispatchers.Main 是主线程,适合更新界面;IO 适合网络与磁盘;Default 适合 CPU 密集计算。作用域(CoroutineScope)管理一批协程的生命周期。
- Main主线程,更新 UI。
- IO网络、数据库、文件读写。
- Default排序、解析等 CPU 密集活。
ViewModel 里用 viewModelScope,页面销毁时自动取消其中所有协程,避免内存泄漏。
结构化并发与取消
结构化并发指协程成树状组织:父协程等所有子协程,父取消则子一并取消。async/await 用来并发并拿结果;withTimeout 给任务加超时。
viewModelScope.launch {
val user = async(Dispatchers.IO) { fetchUser() }
val list = async(Dispatchers.IO) { fetchList() }
uiState = Result.Ready(user.await(), list.await())
}
两个请求并发执行,一起 await。页面销毁时 viewModelScope 取消,两个请求随之取消,不会在后台白跑。
Flow:冷流与操作符
一次返回单个值用 suspend 函数;一串随时间产出的值用 Flow。Flow 是冷流——只有被 collect 才开始执行,像一条按需打开的数据流。map、filter、flatMap 这些操作符把数据流一步步加工。
fun search(q: String): Flow<List<Item>> =
flow { emit(api.search(q)) }
.debounce(300)
.distinctUntilChanged()
.flowOn(Dispatchers.IO)
debounce 让连续输入只在停顿后才发请求,这正是搜索框的典型套路。
StateFlow 与 SharedFlow
普通 Flow 是冷的、一次性的;要给界面提供"当前状态"并让新订阅者立刻拿到最新值,用 StateFlow。它持有一个状态值,界面收集它即可。SharedFlow 则用于一对多的事件(如弹 toast、导航),每条事件只发一次。
- StateFlow:可观察的状态,ViewModel 暴露它,界面收集刷新。
- SharedFlow:一次性事件,避免旋转屏幕后重复弹 toast。
异常与超时
Flow 里上游抛出的异常会顺着流传到 collect,用 catch 操作符在流中间兜底。withTimeout 给一段挂起代码限时,超时抛 TimeoutCancellationException。结构化作用域下,一个子协程崩溃默认会连带取消整棵树,必要时用 SupervisorJob 隔离。
常见失误:在 collect 里裸吞异常,或忘了在 IO 调度器做网络,导致主线程被同步调用拖慢。
实战:用 Flow 串联加载状态
把"加载中、成功、失败"用 StateFlow 表达出来。
class UserViewModel : ViewModel() {
private val _ui = MutableStateFlow<UiState>(UiState.Loading)
val ui: StateFlow<UiState> = _ui
fun load() = viewModelScope.launch {
_ui.value = UiState.Loading
try {
val data = withContext(Dispatchers.IO) { repo.user() }
_ui.value = UiState.Ready(data)
} catch (e: Exception) {
_ui.value = UiState.Error(e.message ?: "未知错误")
}
}
}
界面收集 ui,按 Loading/Ready/Error 三态渲染。状态单向流动、可取消、超时可控,这就是协程加 Flow 在安卓里最成熟的写法。