一、核心区别
| 特性 | emit | tryEmit |
|---|---|---|
| 挂起函数 | ✅ 是 suspend 函数 | ❌ 不是挂起函数 |
| 缓冲区满时行为 | 挂起等待直到有空间 | 立即返回 false,丢弃数据 |
| 返回值 | 无(Unit) | Boolean(true=成功,false=失败) |
| 使用场景 | 协程中 | 普通函数/回调中 |
| 线程安全 | 是 | 是 |
二、emit 详细说明
kotlin
// emit 是 suspend 函数,只能在协程或挂起函数中调用
public suspend fun emit(value: T)
工作原理:
- 当缓冲区有空间时,立即发射值并返回
- 当缓冲区满时,挂起调用者,等待缓冲区有可用空间
- 保证了数据的不丢失(背压支持)
kotlin
val sharedFlow = MutableSharedFlow<Int>(
replay = 0, // 新订阅者不会收到历史值
extraBufferCapacity = 2, // 额外缓冲容量为2
onBufferOverflow = BufferOverflow.SUSPEND // 默认:缓冲满时挂起
)
// ✅ 正确:在协程中使用 emit
viewModelScope.launch {
sharedFlow.emit(1) // 缓冲区有空间,立即返回
sharedFlow.emit(2) // 缓冲区有空间,立即返回
sharedFlow.emit(3) // 缓冲区满,挂起等待消费者读取后才能继续
}
三、tryEmit 详细说明
kotlin
// tryEmit 不是 suspend 函数,可在任何地方调用
public fun tryEmit(value: T): Boolean
工作原理:
- 尝试立即发射值,不挂起
- 如果缓冲区有空间 → 发射成功,返回 true
- 如果缓冲区满 → 丢弃数据,返回 false
- 不保证数据一定被发送
kotlin
val sharedFlow = MutableSharedFlow<Int>(
replay = 0,
extraBufferCapacity = 2,
onBufferOverflow = BufferOverflow.SUSPEND
)
// ✅ 在普通函数/回调中使用 tryEmit
fun onClick() {
val success = sharedFlow.tryEmit(1) // true,缓冲区有空间
val failed = sharedFlow.tryEmit(99) // 可能返回 false(缓冲区满时)
}
四、tryEmit 成功条件
tryEmit 在以下情况返回 true:
- replay != 0 时(有重放缓存)
- extraBufferCapacity > 0 且缓冲区未满
- 有活跃订阅者且其缓冲区未满
关键陷阱: 默认配置下 tryEmit 极易失败!
kotlin
// ⚠️ 默认配置:replay=0, extraBufferCapacity=0
val defaultFlow = MutableSharedFlow<String>()
// 这种情况下 tryEmit 几乎总是返回 false!
// 因为没有缓冲区,且没有活跃订阅者时无处存放数据
defaultFlow.tryEmit("hello") // 大概率返回 false,数据丢失!
五、完整代码示例
kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
class EventViewModel : ViewModel() {
// ============ 方案1:使用 emit(推荐,数据不丢失)============
private val _events = MutableSharedFlow<String>(
replay = 0,
extraBufferCapacity = 10,
onBufferOverflow = BufferOverflow.SUSPEND
)
val events = _events.asSharedFlow()
// 在协程中使用 emit ------ 数据安全
fun sendDataWithEmit(data: String) {
viewModelScope.launch {
_events.emit(data) // 挂起函数,保证数据发送
}
}
// ============ 方案2:使用 tryEmit(适合回调场景)============
private val _uiEvents = MutableSharedFlow<UiEvent>(
replay = 0,
extraBufferCapacity = 1, // ⚠️ 必须设置缓冲区!
onBufferOverflow = BufferOverflow.DROP_OLDEST // 满时丢弃最旧的
)
val uiEvents = _uiEvents.asSharedFlow()
// 在非协程回调中使用 tryEmit
fun onButtonClick() {
// 不需要协程,适合在普通函数/回调中调用
val success = _uiEvents.tryEmit(UiEvent.ShowToast("Clicked!"))
if (!success) {
Log.w(TAG, "Event dropped!")
}
}
// ============ 方案3:ChannelFlow 替代(1对1场景)============
private val _navigation = Channel<NavEvent>()
val navigation = _navigation.receiveAsFlow()
fun navigate(event: NavEvent) {
_navigation.trySend(event) // Channel 的 trySend
}
}
// ============ 使用示例 ============
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
viewModel.events.collect { event ->
// 处理事件
println("Received: $event")
}
}
}
}
六、理解误区
1、如果没有collect执行,emit发射数据时,会不会挂起等待?直到collect收集。
在 Kotlin 协程的 MutableSharedFlow中,emit不会因为"没有 Collector(收集者)"而挂起 。它只会因为"有 Collector 但处理不过来(背压)"而挂起。
只有当存在活跃的 Collector ,且数据生产速度 > 消费速度 时,emit才会挂起。
打个比方:
没 Collector:就像对着空气喊话。喊完就完事了,不需要等谁回应。
有 Collector 但没挂起:就像对着一群人演讲,手里拿了个麦克风(缓冲区),还能装得下。
挂起等待:就像你拿着麦克风(缓冲区满了),台下的人还在记笔记没听完,你只能暂停演讲,等他们记完了你再继续。
你感觉到的"挂起",通常是用了默认配置 (replay=0, extraBufferCapacity=0) 且有慢速 Collector 时发生的。
六、最佳实践总结
- 优先使用 emit:在协程环境中始终优先使用 emit,它保证数据不丢失
- tryEmit 必须配合缓冲区:如果使用 tryEmit,务必设置 extraBufferCapacity > 0 或 onBufferOverflow = DROP_OLDEST
- 默认配置是陷阱:MutableSharedFlow() 默认 extraBufferCapacity=0,此时 tryEmit 在无订阅者时必定失败
- 回调场景用 tryEmit:当无法使用协程时(如第三方库回调),tryEmit 是唯一选择
- 1对1事件用 Channel:如果事件只需一个消费者处理,考虑使用 Channel + receiveAsFlow()