Kotlin Flow 是 Kotlin 协程生态中的响应式数据流(Reactive Stream)实现 ,用于异步、顺序、可组合地发射多个值。你可以把它理解为"基于协程的 RxJava 轻量替代",但设计理念更贴合 Kotlin 的挂起函数和结构化并发。
一、核心概念:Flow 到底是什么
1. 定义
kotlin
// Flow 的接口定义极其简单
public interface Flow<out T> {
public abstract suspend fun collect(collector: FlowCollector<T>)
}
本质 : Flow 就是一个挂起函数 collect,接收一个 FlowCollector 回调。每次调用 collect,内部的代码块顺序执行, emit 一个值就回调一次。
2. 冷流(Cold Stream)
Flow 是冷流:不消费就不生产,每次消费都从头生产。
kotlin
val flow = flow {
println("开始生产")
emit(1)
emit(2)
}
// 第一次收集
flow.collect { println("收到: $it") } // 输出:开始生产 / 收到: 1 / 收到: 2
// 第二次收集
flow.collect { println("收到: $it") } // 再次输出:开始生产 / 收到: 1 / 收到: 2
对比热流(Hot Stream,如 StateFlow):不管有没有观察者,都在生产数据。
3. 与 RxJava 的核心区别
| 特性 | Kotlin Flow | RxJava |
|---|---|---|
| 基础 | 基于 Kotlin 协程/挂起函数 | 基于 JVM 线程/回调 |
| 线程切换 | flowOn(Dispatchers.IO),简单直观 |
subscribeOn/observeOn 组合复杂 |
| 背压 | 默认挂起(背压天然友好) | 多种策略(BUFFER、DROP、LATEST 等) |
| 异常处理 | catch 操作符 + try/catch |
onError 回调 |
| 生命周期 | 天然支持结构化并发(自动取消) | 需手动管理 Disposable |
| 学习成本 | 低(挂起函数直觉) | 高(大量操作符和线程概念) |
| 包体积 | 小(协程核心 + Flow) | 大(RxJava 全家桶) |
4. 核心操作符
kotlin
flowOf(1, 2, 3, 4, 5)
.filter { it > 2 } // 过滤
.map { it * 2 } // 变换
.flowOn(Dispatchers.IO) // 上游在 IO 线程执行
.catch { emit(-1) } // 异常捕获
.onEach { println(it) } // 副作用
.collect() // 终端操作符(触发执行)
二、常见问题
Q1:Flow 是什么?为什么要用 Flow 而不是 LiveData/RxJava?
- Flow 是 Kotlin 协程提供的冷流(Cold Stream) API,用于处理异步顺序发射多个值的场景。
- 相比 LiveData:
- Flow 支持丰富操作符(map、filter、debounce 等),LiveData 的 Transformations 能力有限。
- Flow 支持线程切换(flowOn),LiveData 只能在主线程消费。
- Flow 是 Kotlin 多平台通用,LiveData 是 Android 专属。
- Flow 支持背压,LiveData 没有背压概念(setValue 太快会丢值)。
- 相比 RxJava:
- Flow 基于挂起函数,代码更线性、可读性更高,避免回调地狱。
- Flow 天然支持结构化并发(viewModelScope.launch 取消时自动取消 Flow 收集)。
- 包体积更小,无需引入 RxJava 全家桶。
Q2:什么是冷流(Cold Flow)和热流(Hot Flow)?Flow 属于哪种?
- 冷流: 不收集就不生产数据,每次收集都独立从头执行。Flow 是冷流。
- 热流: 生产者独立存在,不管有没有收集者都在生产数据。StateFlow、SharedFlow 是热流。
kotlin
// 冷流:每次 collect 都重新执行 flow 块
val coldFlow = flow { emit(1); emit(2) }
// 热流:有独立的生产者,多个收集者共享同一数据
val hotFlow = MutableStateFlow(0)
冷流转热流: 使用 stateIn 或 shareIn 操作符。
Q3:StateFlow、SharedFlow 和 LiveData 的区别?
| 特性 | StateFlow |
SharedFlow |
LiveData |
|---|---|---|---|
| 类型 | 热流(有初始值) | 热流(无初始值) | 可观察数据持有者 |
| 初始值 | 必须有 | 可以没有 | 可以没有 |
| 重播 | 始终重播最新值(replay=1) |
可配置 replay 数量 |
始终重播最新值 |
| 背压 | onBufferOverflow 策略 |
onBufferOverflow 策略 |
无,直接覆盖 |
| 观察者 | 需要 LifecycleScope/repeatOnLifecycle |
同上 | 自动感知生命周期 |
| 线程 | 任意线程收集,通常 flowOn 切换 |
同上 | 主线程观察 |
| 适用 | UI 状态(如 Loading/Success/Error) | 事件(如 Toast、Navigation) | 简单 UI 状态 |
关键区分:
- StateFlow 适合状态(State):有初始值,新观察者总能拿到最新状态。
- SharedFlow 适合事件(Event):如 Channel 的替代,配置 replay=0 时新观察者收不到历史事件。
- LiveData 在纯 Kotlin 项目中被 StateFlow 替代趋势明显,但在 Java 项目中仍广泛使用。
Q4:flowOn 的作用是什么?它切换的是上游还是下游的线程?
- flowOn 改变的是上游操作符的执行线程(上下文)。
- 下游操作符和终端操作符(如 collect)运行在调用者(收集者)的协程上下文中。
kotlin
flow {
// 运行在 Dispatchers.IO
emit(fetchData())
}.map {
// 运行在 Dispatchers.IO(上游)
it.process()
}.flowOn(Dispatchers.IO)
.filter {
// 运行在 collect 所在的线程(默认主线程)
it.isValid()
}.collect {
// 运行在 collect 所在的线程(默认主线程)
updateUI(it)
}
原理 : flowOn 会在上游和下游之间插入一个通道 (Channel),上游在指定调度器生产,下游在收集者调度器消费,实现生产者-消费者解耦。
Q5:Flow 的背压(Backpressure)是如何处理的?
- Flow 基于挂起函数的特性,天然对背压友好:当消费者处理慢时,生产者(emit)会挂起等待,不会丢数据。
- 对比 RxJava 需要显式选择策略(BUFFER、DROP、LATEST),Flow 默认行为就是挂起缓冲。
- 如果希望不挂起,可以使用 buffer()、conflate()、collectLatest() 等操作符主动控制:
kotlin
flow {
emit(1); emit(2); emit(3)
}.buffer(10) // 缓冲 10 个元素,emit 不挂起
.conflate() // 只保留最新值,中间值丢弃
.collectLatest { // 有新值时取消上一个值的收集
delay(100) // 处理慢
}
Q6:Flow 的异常处理机制有哪些?catch 能捕获哪些异常?
Flow 异常处理有三种方式:
1. try/catch 包裹 collect:
kotlin
try {
flow.collect { ... }
} catch (e: Exception) { ... }
可以捕获上游和下游(collect 块内)的异常。
2. catch 操作符:
kotlin
flow { ... }
.map { ... }
.catch { e ->
emit(defaultValue) // 可以发射默认值
}
.collect()
- catch 只捕获上游异常(catch 之前的操作符)。
- 不能捕获 collect 块内的异常(下游)。
- catch 内可以再次 emit,实现异常恢复。
3. onCompletion:
kotlin
.onCompletion { cause ->
// cause 为 null 表示正常完成,非 null 表示异常取消
}
类似 finally,无论成功、失败还是取消都会执行。
重要: catch 不会捕获 CancellationException(协程取消是正常流程,不是异常)。
Q7:collect、collectLatest、launchIn 有什么区别?
| 终端操作符 | 行为 | 适用场景 |
|---|---|---|
collect |
挂起式顺序收集,处理完一个才收下一个 | 需要处理每一个值 |
collectLatest |
收到新值时,取消上一个值的收集协程 | 搜索输入防抖、位置更新(只关心最新) |
launchIn(scope) |
在指定 Scope 内启动收集,非阻塞 | 需要同时启动多个 Flow,或绑定生命周期 |
first() / single() |
取第一个/唯一一个值,然后取消 | 一次性查询 |
kotlin
// collectLatest:用户快速输入 "abc",只处理 "c" 的搜索
searchFlow
.debounce(300)
.flatMapLatest { query -> search(query) }
.collectLatest { result -> updateUI(result) }
// launchIn:在 viewModelScope 中启动收集,返回 Job 可取消
flow.launchIn(viewModelScope)
Q8:如何在 Android 中安全地收集 Flow?repeatOnLifecycle 的作用?
在 Activity/Fragment 中直接 lifecycleScope.launch { flow.collect() } 会导致后台应用仍收集数据,浪费资源甚至崩溃。
正确做法:
kotlin
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
updateUI(state)
}
}
}
- repeatOnLifecycle 在生命周期进入 STARTED 时启动收集,低于 STARTED 时自动取消并重新订阅。
- 保证 UI 不可见时不收集,避免内存泄漏和无效更新。
- 适用于 StateFlow 等热流。
替代方案:
- flowWithLifecycle(flow, Lifecycle.State.STARTED):将 Flow 转换为生命周期感知的 Flow。
- LifecycleOwner.addRepeatingJob(Kotlin 1.6+ 扩展)。
Q9:flatMapConcat、flatMapMerge、flatMapLatest 的区别?
| 操作符 | 行为 | 类比 RxJava |
|---|---|---|
flatMapConcat |
顺序处理:上一个内部 Flow 完成后才启动下一个 | concatMap |
flatMapMerge |
并发处理:同时启动多个内部 Flow,结果按完成顺序合并 | flatMap |
flatMapLatest |
收到新值时,取消上一个未完成的内部 Flow | switchMap |
kotlin
flowOf(1, 2, 3)
.flatMapLatest { value ->
flow {
delay(100)
emit(value)
}
}
.collect { println(it) } // 快速发射时,只保留最新值
Q10:Flow 在 MVVM 架构中的典型使用方式?
ViewModel 层:
kotlin
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
private val _events = MutableSharedFlow<Event>()
val events: SharedFlow<Event> = _events.asSharedFlow()
fun loadData() {
viewModelScope.launch {
repository.fetchData()
.onStart { _uiState.value = UiState.Loading }
.catch { e -> _uiState.value = UiState.Error(e.message) }
.collect { data ->
_uiState.value = UiState.Success(data)
}
}
}
}
UI 层(Fragment):
kotlin
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch { viewModel.uiState.collect { updateUI(it) } }
launch { viewModel.events.collect { handleEvent(it) } }
}
}
分层原则:
- StateFlow 暴露状态(UI 的完整数据快照)。
- SharedFlow 暴露事件(一次性通知,如导航、Toast)。
- 业务逻辑中的数据流用冷 Flow,在 ViewModel 中通过 stateIn 转为热流供 UI 订阅。
三、总结
| 问题 | 一句话答案 |
|---|---|
| Flow 是什么 | 基于协程的冷流,顺序异步发射多个值 |
| 冷流 vs 热流 | 冷流按需生产(Flow),热流持续生产(StateFlow/SharedFlow) |
| StateFlow vs SharedFlow | StateFlow 有初始值重播最新状态;SharedFlow 配置灵活,适合事件 |
| flowOn | 切换上游操作符的调度器,下游运行在 collect 所在线程 |
| 背压 | 默认挂起等待;可用 buffer/conflate/collectLatest 主动控制 |
| 异常处理 | catch 捕获上游;try/catch 包裹 collect 捕获全部;onCompletion 类似 finally |
| 生命周期收集 | 用 repeatOnLifecycle(Lifecycle.State.STARTED) 避免后台收集 |
| 终端操作符 | collect 顺序处理;collectLatest 取消旧值;launchIn 非阻塞启动 |