1. 它们分别"属于谁"
-
冷流(Cold Flow)构建器 :flow { ... } / transform { ... } 的接收者是 FlowCollector
→ 只能用 emit(value) / emitAll(flow) (没有 tryEmit)。
-
热流(Hot Flow)容器:
-
MutableStateFlow:有 value、emit(value)、tryEmit(value)
-
MutableSharedFlow:有 emit(value)、tryEmit(value)
-
callbackFlow/channelFlow 里是 send/trySend(Channel 语义),不是 emit/tryEmit。
2.emit vs tryEmit
总对比(速查)
场景 / 类型 | emit(value) | tryEmit(value) |
---|---|---|
Cold:flow {} / transform {} | ✅(挂起函数)向下游发射;若下游慢会挂起,天然背压 | ❌ 没有 |
MutableStateFlow | ✅ 不会挂起(语义等同 value = ...) | ✅ 总是返回 true(StateFlow 为"合流/最新值",无容量限制) |
MutableSharedFlow | ✅ 默认可能挂起(看缓冲/溢出策略) | ✅ 快速返回:true=接收成功;false=当前无法接收(见 §4) |
口诀:需要背压/按需挂起 → 用 emit;需要"尽力而为、立刻返回" → 用 tryEmit。
对 StateFlow:直接 state.value = new 最高效;emit/tryEmit 只是 API 形态不同,行为等价(不挂起/永远成功)。
3. 冷流里的emit
scss
val numbers: Flow<Int> = flow {
// 只有 collect 才会执行
for (i in 1..3) {
delay(100) // 可挂起
emit(i) // 发射给下游;若下游慢,这里会挂起(背压)
}
}
- 背压:上游 emit 会等下游处理,除非你在中间加了 buffer() / conflate() 等运算符。
- 线程:上游用 flowOn(Dispatchers.IO) 切线程;下游在收集端线程。
4.
MutableSharedFlow的背压 & tryEmit是否成功
MutableSharedFlow 构造:
ini
val events = MutableSharedFlow<Event>(
replay = 0, // 历史重放条数
extraBufferCapacity = 0, // 额外缓冲容量
onBufferOverflow = BufferOverflow.SUSPEND // 满了时的策略
)
4.1
emit(挂起语义)
-
默认 (extraBufferCapacity=0 & SUSPEND):当没有订阅者、或下游/缓冲满 → 挂起 等待可用。
-
onBufferOverflow = DROP_OLDEST / DROP_LATEST:永不挂起,会按策略丢弃旧/新元素。
4.2
tryEmit(非挂起语义)
-
返回 true:本次发送被接受(直接发给订阅者,或进了缓冲/重放区)。
-
返回 false :当前无法 接受(典型:replay + extraBufferCapacity == 0 且 SUSPEND,且没有订阅者或缓冲满)。
-
若 onBufferOverflow = DROP_*:一般 总是 true(会按策略丢弃某个元素来腾位)。
特别注意:replay > 0 时,即使没有订阅者 ,tryEmit 也会把值放进重放缓存 → 多为 true。
5.
MutableStateFlow的emit/tryEmit/value
- value = x 是首选(同步、立即生效)。
- emit(x) :是 suspend 形态,不会挂起,等价于 value = x。
- tryEmit(x) :总是 true(StateFlow 只有"最新值",没有容量上限的失败场景)。
- 收集端是"合流(conflated)" :下游慢会跳过中间值,只拿到最新值。
6. 选用建议(实战)
6.1 UI 状态(StateFlow)
kotlin
private val _ui = MutableStateFlow(UiState())
val ui: StateFlow<UiState> = _ui
fun onAction() {
_ui.update { it.copy(loading = true) } // 推荐
// 或 _ui.value = it.copy(...)
// 或 viewModelScope.launch { _ui.emit(...) }(等价但多一层协程)
}
理由:状态只关心"最终一致",用合流语义最省心。
6.2 一次性事件(SharedFlow)
kotlin
private val _events = MutableSharedFlow<UiEvent>(
replay = 0, extraBufferCapacity = 64, onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val events: SharedFlow<UiEvent> = _events
fun toast(msg: String) {
// UI 线程里不想挂起 → tryEmit
if (!_events.tryEmit(UiEvent.Toast(msg))) {
// 极端兜底:必要时丢给后台
viewModelScope.launch { _events.emit(UiEvent.Toast(msg)) }
}
}
规则:
-
主线程事件优先 tryEmit(),避免意外挂起卡 UI;
-
需要"保证送达/愿意挂起"等背压 → 用 emit()。
6.3 冷流升温(避免多处收集重复执行)
scss
val resultCold: Flow<List<Item>> = flow { emit(api.search(q)) }.flowOn(Dispatchers.IO)
val resultState: StateFlow<List<Item>> =
resultCold.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
// 之后仅更新 StateFlow 的 value/emit;UI 多处收集不会重复请求
7. 常见误区 & 纠偏
-
在主线程用 emit 发送事件 → 可能挂起卡 UI。
✅ 改 tryEmit(或给 SharedFlow 配 extraBufferCapacity、DROP_*)。
-
把一次性事件放进 StateFlow → 新订阅者会立刻收到"旧事件"。
✅ 事件用 SharedFlow(replay=0);需要"上一次也要看到"才调大 replay。
-
冷流被多处收集重复打接口。
✅ 在 VM 里用 stateIn/shareIn 升温并缓存。
-
以为 StateFlow.emit 会挂起。
❌ 它不会挂起,和 value= 等价;避免无谓 launch { _state.emit(x) }。
8. 小示例:三连板(背压/丢/取最新)
scss
// 1) 背压缓冲
val f1 = MutableSharedFlow<Int>(replay = 0, extraBufferCapacity = 64)
f1.tryEmit(1) // true,入缓冲
// 2) 丢旧保新(UI 渲染常用)
val f2 = MutableSharedFlow<Int>(
replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST
)
// 3) 只取最新(下游处理耗时)
f2
.conflate() // 或 collectLatest { ... }
.onEach { render(it) }
.launchIn(viewModelScope)
总结
- emit 是挂起式发送:冷流里天然背压;SharedFlow/StateFlow 里依据缓冲策略。
- tryEmit 是非挂起式发送:立即返回能否接受(StateFlow 总能;SharedFlow 取决于 replay/extraBufferCapacity/onBufferOverflow)。
- UI 事件 → SharedFlow + tryEmit;UI 状态 → StateFlow + value/update;冷流多处收集 → stateIn/shareIn 升温。