1) 变换类
map
- 做什么:一进一出,同步/挂起变换。
- 何时用:纯计算或轻量异步。
- 坑:map{…}里做重活会阻塞上游 → 放 flowOn(Dispatchers.IO)。
val uiFlow = repo.userFlow
.map { user -> user.toUi() } // 挂起安全
.flowOn(Dispatchers.Default)
flatMapLatest
- 做什么:把每个值映射成一个 Flow,并且只保留最新那个流(旧流自动取消)。
- 何时用:搜索框/筛选条件变化时,只取最新请求。
- 对比:flatMapMerge(并发合并)、flatMapConcat(顺序排队)。
val result = query
.debounce(300)
.distinctUntilChanged()
.flatMapLatest { q -> flow { emit(api.search(q)) }.flowOn(Dispatchers.IO) }
2) 过滤/节流类
filter
- 做什么:条件过滤。
val adults = users.filter { it.age >= 18 }
debounce
- 做什么:等待“静默窗口”结束才发最后一个值(防抖)。
- 何时用:输入框、滚动停止后再请求。
val stableQuery = query
.debounce(300)
.distinctUntilChanged()
sample
- 做什么:每隔固定周期发最近一次值(采样)。
- 何时用:高频传感器/进度条,限制刷新频率。
val fps = frameTimestamps.sample(16) // ~60Hz
distinctUntilChanged/distinctUntilChangedBy
- 做什么:相邻相等就不发;By 可按 key 去重。
- 何时用:去 UI 抖动、按主键/内容相等去重。
state.distinctUntilChangedBy { it.listHash } // 只在列表内容变时刷新
3) 背压/缓冲类
buffer(capacity)
- 做什么:在该点引入队列,让上游继续跑,下游慢时先排队。
- 何时用:上游昂贵/IO,多消费者;与 flowOn 结合形成线程边界。
- 坑:无脑 UNLIMITED 可能占内存。
source
.buffer(capacity = Channel.BUFFERED)
.collect { render(it) }
conflate()
- 做什么:下游慢时丢中间,只保留最新(不排队)。
- 何时用:UI 渲染/绘制,只关心最新状态。
- 对比:collectLatest{} 会取消正在处理的块;conflate()不取消,只跳过中间值。
state
.conflate()
.collect { render(it) } // 渲染慢也只会拿到最新
4) 错误/完成回调
catch { }
- 做什么:捕获上游异常(到 catch 之前的链条)。
- 何时用:网络兜底/重试。
- 坑:不要吞掉取消:
flow { ... }
.retry(3) { it is IOException }
.catch { e ->
if (e is CancellationException) throw e
emit(emptyList()) // 兜底
}
onCompletion { cause }
- 做什么:无论正常结束还是异常/取消都会调用;cause==null 表示正常完成。
flow { ... }
.onCompletion { cause -> log("done, cause=$cause") }
.collect()
5) 线程切换
flowOn(dispatcher)(只切上游)
- 做什么:把它之前的上游运算放到指定调度器;它与下游之间形成缓冲边界。
- 常见用法:IO/CPU 重活放上游线程;下游在 Main 收集。
- 验证:打印线程可见“只切上游”的效果。
val data = flow { emit(db.load()) } // 运行在 IO
.map { heavyCompute(it) } // 也在 IO
.flowOn(Dispatchers.IO)
.onEach { updateUi(it) } // 仍在 Main(收集端)
6) 组合模板(可直接用)
A) 搜索:防抖 + 只取最新 + 兜底
val result: StateFlow<List<Item>> =
query
.debounce(300)
.distinctUntilChanged()
.flatMapLatest { q -> flow { emit(api.search(q)) }.flowOn(Dispatchers.IO) }
.catch { emit(emptyList()) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
B) 大量事件 → 采样 + 合流渲染
events
.sample(50) // 至多 20fps
.conflate() // 下游慢时只保留最新
.collect { render(it) }
C) 重计算在后台、收集在主线程
repo.itemsFlow
.map { it.sortedBy { x -> x.score } }
.flowOn(Dispatchers.Default)
.collect { adapter.submitList(it) } // Main
7) 常见配对心法
-
只要最新:flatMapLatest(请求) / collectLatest(渲染) / conflate(状态)。
-
限频:输入用 debounce,高频流用 sample。
-
去抖:distinctUntilChanged[By] 放在尽量靠前的位置。
-
不卡 UI:重活放 flowOn(IO/Default);SharedFlow 配 extraBufferCapacity 或在链上 buffer()。
-
稳定结束:catch{} + onCompletion{};记得放对位置(在你想保护的算子之后)。