一、终端操作(Terminal operators)
一旦调用就启动收集,并决定这条 Flow 的“生命周期结束点”。
1) 收集类
- collect { ... }:顺序处理每个值(会对上游施加背压)。
- collectLatest { ... }:来新值就取消上一次处理块,只处理最新值。
- launchIn(scope):把 Flow 启动到 scope 中,返回 Job(常与 onEach 搭配)。相当于“边返回边运行”。
flow.onEach { render(it) }.launchIn(viewModelScope) // 启动并返回 Job
2) 取值/短路类(会在满足条件后取消上游)
-
first() / firstOrNull():取第一个值后结束。
-
single() / singleOrNull():要求仅有一个元素。
-
last():收集到末尾的最后一个值。
-
toList() / toSet() / toCollection(coll):收集成集合。
-
reduce { acc, v -> ... } / fold(init) { acc, v -> ... }
-
count() / any {} / all {} / none {}
take(n) 属于中间操作,但它会在拿够 n 个后主动取消上游。
3) “启动型共享”操作(本质是中间,但会启动上游)
-
stateIn(scope, started, initial):把冷流升为 StateFlow(有“当前值”)。
-
shareIn(scope, started, replay):把冷流升为 SharedFlow(广播式)。
它们返回新的 Flow(热流),但由于会在给定 scope 内启动收集,表现出“终端”的效果。
二、取消协作(Cooperative cancellation)
协程的取消是协作式的:通过抛出 CancellationException 让各层尽快结束。Flow/挂起函数都应对取消敏感。
1) 取消如何传播
- 下游取消会向上游传播:first() / take(n) / 手动 job.cancel()。
- collectLatest:新值到来会取消之前那一次收集块(不是上游),随后立即处理最新值。
- launchIn(scope):取消返回的 Job 或取消 scope,都会停止这条 Flow。
val job = flow.onEach { ... }.launchIn(scope)
// ...
job.cancel() // 立即停止收集与上游
2) 让代码“可被取消”的要点
- 只用可挂起的 API:例如 delay、withContext(Dispatchers.IO)、Retrofit/Room 的挂起函数等。
- CPU 密集循环:定期 yield() / ensureActive() / 检查 isActive。
- 阻塞 IO:放到 Dispatchers.IO;若库支持取消(如 OkHttp Call.cancel()),用 suspendCancellableCoroutine 绑定取消。
// CPU 密集:定期让出
while (...) {
ensureActive() // 或 yield()
heavyStep()
}
3) 常用取消相关算子与回调
- onCompletion { cause -> ... }:正常完成 cause==null;取消/异常时 cause 非空(注意区分)。
- cancellable():让基于 asFlow() 的迭代在每次发射前检查取消(对长链表/昂贵迭代很有用)。
- timeout(ms) / withTimeout(ms):超时自动取消并抛出 TimeoutCancellationException。
flowOf(1,2,3).onCompletion { cause ->
if (cause is CancellationException) log("cancelled")
}
4) 处理异常时
不要吞掉取消
flow.catch { e ->
if (e is CancellationException) throw e // 继续向上抛
emit(fallback())
}
5) 生命周期集成(Android)
- Fragment:repeatOnLifecycle(STARTED) { flow.collect { ... } } 自动在不可见时取消、可见时重启。
- Compose:collectAsStateWithLifecycle()。
- 共享上游但无订阅应停:WhileSubscribed(5_000)。
val ui: StateFlow<UiState> =
repo.dataFlow
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), UiState())
三、典型场景模板
1) 搜索:只取最新请求(老请求自动取消)
val query = MutableStateFlow("")
val resultFlow =
query.debounce(300)
.distinctUntilChanged()
.flatMapLatest { q -> flow { emit(api.search(q)) }.flowOn(Dispatchers.IO) }
lifecycleScope.launch {
resultFlow.collectLatest { render(it) } // 新查询到来,旧渲染取消
}
2) 启动到作用域 & 手动取消
val job = flow.onEach { render(it) }
.launchIn(viewLifecycleOwner.lifecycleScope)
// 需要时:
job.cancel()
3) “拿够就停”:take/first
val top5 = repo.itemsFlow
.take(5) // 拿够 5 个会主动取消上游
.toList()
4) 清理与不可取消区
try {
flow.collect { ... }
} finally {
withContext(NonCancellable) {
// 必须完成的清理(关闭文件/释放句柄)
}
}
四、容易踩的点(速记)
-
以为 collectLatest 不会背压上游****
- 如果上游没有缓冲,仍可能被挂起。→ 加 buffer() 或为 SharedFlow 配 extraBufferCapacity/DROP_*。
-
在 catch 里吞掉 CancellationException****
- 一定要 if (e is CancellationException) throw e。
-
把一次性事件放进 StateFlow****
- 新订阅立刻收到旧事件。→ 用 SharedFlow(replay=0)。
-
忘记停“共享上游”****
- stateIn/shareIn 要用 WhileSubscribed,或手动取消 scope。
小结
-
终端操作:collect/collectLatest/launchIn、各类取值/聚合(first/single/last/toList/reduce/fold),以及会启动上游的 stateIn/shareIn。
-
取消协作:让上游/处理块可取消(挂起、yield/ensureActive、绑定底层取消);正确使用 onCompletion、不吞取消异常;在 Android 用 repeatOnLifecycle/collectAsStateWithLifecycle 管理订阅生命周期。
需要我把你某段 Flow 代码“加上正确的终端操作与取消处理”(比如防抖、只取最新、WithSubscribed、异常兜底)的,贴出来我帮你改成最佳实践版本。