一、协程官方4大类
从冷热属性划分:普通Flow(冷流)、StateFlow、SharedFlow、callbackFlow / channelFlow,后两者属于特殊构建方式。
- Cold‑Flow(冷流):
flow{}、flowOf、asFlow() - Hot‑Flow(热流):
StateFlow、SharedFlow - 特殊构建:
callbackFlow、channelFlow(适配Java回调转为Flow)
1. Cold Flow(普通Flow,冷流)
核心特点
- 只有调用collect收集时,flow{}内部代码才会执行;没人订阅不会运行。
- 每一次collect都会重新执行上游逻辑,多个收集者互相独立,上游会重复执行。
- 协程取消时流自动停止;完全遵循结构化并发。
- 没有初始值,没有缓存,不保存发射过的数据。
创建方式
kotlin
//方式1 flow构造器
fun getDataFlow(): Flow<String> = flow {
println("执行网络请求")
val result = api.request()
emit(result)
}
//方式2
flowOf(1,2,3)
listOf(1,2,3).asFlow()
Android适用场景
- 单次网络请求、Room数据库监听(Room返回Flow本质也是冷流);
- 分页加载、下拉刷新;每次订阅重新请求数据;
缺点:多个页面同时collect会多次发起网络请求。
常用中间操作符
map、filter、transform、onStart、onCompletion、catch、debounce、distinctUntilChanged。
三个flatMap重点(面试高频)
- flatMapConcat:串行执行,前一个flow执行完毕才执行下一个,适合顺序请求;
- flatMapMerge:并发执行,最多16个并发,适合并行请求;
- flatMapLatest:收到新值立刻取消上一个flow,搜索场景(输入新文字取消上一次网络请求)。
2. StateFlow(热流,继承自SharedFlow)
核心特性
- 热流:不管有没有collect,都一直在运行;多个收集者共享同一个数据源,上游只执行一次。
- 强制要有初始值,内部永久保存最新值;新的订阅者进来立刻收到最新值(粘性)。
- 如果新emit的值和旧值一样会自动过滤,不会重复分发。
- 只能通过
value赋值更新数据;只读暴露给外部。
kotlin
//ViewModel标准写法
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
//更新状态
_uiState.value = UiState.Success(data)
底层原理
MutableStateFlow本质就是 MutableSharedFlow(replay = 1, extraBufferCapacity = 0),默认只缓存最后一条数据。
使用场景(MVVM‑MVI必备)
- 页面UI状态:Loading、Success、Error;
- 全局登录状态、用户信息、配置信息;
- 替代LiveData,不受Android组件生命周期限制;
- 多个Fragment共享同一份数据。
注意:StateFlow适合状态,不适合一次性事件(弹窗、toast、页面跳转),会出现事件重复消费问题。
冷流转成StateFlow:stateIn()
把普通冷流转换成热流,避免多次collect重复请求网络:
kotlin
val hotFlow = flow { emit(loadData()) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = UiState.Loading
)
WhileSubscribed(5000):所有收集者取消之后再保留5秒,短时间内重新订阅不会再次请求网络。SharingStarted.Eagerly:立即启动;Lazily第一个订阅者到来才启动。
3. SharedFlow(通用热流)
核心特性
- 热流,支持多订阅者广播;
- 默认非粘性事件:新订阅者只能收到订阅之后emit的数据,收不到之前发送的值;
- 可以手动配置:
replay(重放条数)、extraBufferCapacity(缓冲区); - 不会自动过滤重复值,调用emit就发送;
- 适合一次性事件:弹窗、Toast、页面跳转、全局事件总线。
kotlin
//全局事件总线示例
private val _eventFlow = MutableSharedFlow<AppEvent>(
replay = 0, //默认不回放历史消息
extraBufferCapacity = 5 //缓冲区满时emit会挂起
)
val eventFlow = _eventFlow.asSharedFlow()
//发送事件
viewModelScope.launch {
_eventFlow.emit(AppEvent.Toast("登录成功"))
}
- replay = 1:新订阅者可以收到最近一条历史事件(变成粘性);
- 当缓冲区满,emit会suspend挂起;如果不想挂起可以用
tryEmit()。
shareIn(冷流转SharedFlow)
kotlin
val sharedFlow = flow {}.shareIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(),
replay = 0
)
StateFlow vs SharedFlow 选型(记住这个结论)
- StateFlow:存状态,UI数据,必须初始值,自动去重;replay固定为1;
- SharedFlow:一次性事件、事件总线;可自由配置回放条数,不会自动去重。
4. channelFlow & callbackFlow(适配Java回调转为Flow)
callbackFlow
专门用来把Java回调接口转换成协程Flow(RxJava、OkHttp回调、蓝牙、传感器、WebView回调)。
- 内部基于Channel实现,支持背压;
- 必须调用
close()或者channel.close()结束流; awaitClose等待回调销毁,防止内存泄漏。
示例:把传统Java回调转为Flow
kotlin
fun startSearch(): Flow<String> = callbackFlow {
val listener = object : SearchListener {
override fun onResult(result: String) {
trySend(result)
}
}
searchEngine.setListener(listener)
awaitClose {
//页面销毁时移除监听,避免内存泄漏
searchEngine.removeListener(listener)
}
}
channelFlow
和callbackFlow底层一致;适合多线程并发emit数据,支持在不同Dispatcher里面发送数据。
kotlin
fun produceData():Flow<Int> = channelFlow {
launch(Dispatchers.IO) { send(1) }
launch(Dispatchers.Default) { send(2) }
}
注意:普通flow{}内部不允许切换Dispatcher发送emit,channelFlow可以跨线程发送数据。
二、冷热流核心区别总结(面试必背)
| 类型 | 冷热属性 | 初始值 | 多收集者 | 新订阅者 | 典型场景 |
|---|---|---|---|---|---|
| flow{} | 冷流 | 无初始值 | 各自独立执行上游 | 重新执行上游逻辑 | 网络请求、数据库查询 |
| StateFlow | 热流 | 必须提供初始值 | 共享同一个上游 | 立刻收到最新值 | UI状态、用户信息 |
| SharedFlow | 热流 | 可选 | 共享同一个上游 | 默认收不到历史事件,可配置replay | 事件分发、弹窗、跳转 |
| callbackFlow | 冷流(按需启动) | 无初始值 | 共享 | 按需接收 | Java回调转Flow |
三、Flow高级操作符(Android项目高频)
1. 超时与异常处理
catch{}捕获上游异常,只能捕获flow内部异常;onCompletion:流结束(正常结束或异常都会回调);withTimeout(10000):协程超时取消。
2. 防抖、去重
debounce(300):300ms内多次发射只取最后一次(搜索输入框);distinctUntilChanged():前后值相同就丢弃。
3. 组合多个Flow
zip:两个flow一一配对,两个流都发出数据后才执行;combine:任意一个flow更新都会触发回调(全局多个状态合并);
kotlin
combine(loginState, configState) { login, config ->
//任意一个状态变化都会执行
}.collect{}
4. 生命周期感知(配合lifecycleScope)
kotlin
viewModelScope.launch {
uiState.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collect {
//页面STARTED状态才接收数据,页面停止自动取消收集,节省性能
}
}
四、Android项目落地最佳实践(严格遵守)
- 页面单次请求、数据库查询:使用普通Flow ;如果多页面同时观察,用
stateIn转成StateFlow避免重复请求。 - UI页面状态:MVI模式用StateFlow + sealed‑class UiState;
- 全局事件(Toast、页面跳转):SharedFlow,replay=0,防止事件重复消费;
- Java回调、蓝牙、传感器、第三方SDK回调:统一使用callbackFlow封装;
- 页面观察Flow全部使用
flowWithLifecycle,页面不可见时停止接收数据,减少耗电; - 禁止在ViewModel中使用GlobalScope;全部用viewModelScope、lifecycleScope;
- 全局状态管理:Hilt注入单例类,内部持有MutableStateFlow,用Mutex做并发修改保护。
五、协程Flow的异常规则
- flow{}内部抛出异常会终止整个流;
catch可以捕获异常并恢复流; - StateFlow、SharedFlow本身不会因为emit时异常被取消;
- collect的协程被取消,Flow停止接收数据;
- 注意:
flatMapLatest会自动取消旧协程,不会抛出CancellationException。