flow 核心 API:map/flatMapLatest/filter/debounce/sample/distinctUntilChanged...

51 阅读3分钟

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{};记得放对位置(在你想保护的算子之后)。