Kotlin Flow 的 emit 和 tryEmit 有什么区别 ?

在 Kotlin Flow 中,emit() 和 tryEmit() 都用于向 Flow 发送值,但它们的关键区别在于背压处理 和协程上下文。

一、主要区别

1. emit()

  • 挂起函数:会挂起直到有空间接收值(处理背压)
  • 协程安全:只能在协程作用域中调用
  • 推荐使用 :在 collect 或 callbackFlow 中优先使用
kotlin 复制代码
flow {
    // 会挂起直到接收方准备好
    emit(1)
    emit(2)
}

2. tryEmit()

  • 非挂起函数 :尝试立即发射值,如果接收方未准备好则返回 false
  • 可能丢数据 :返回 false 时值会被丢弃
  • 有限场景:主要在 MutableStateFlow 和 MutableSharedFlow 中使用
kotlin 复制代码
val _state = MutableStateFlow(0)

fun updateValue(newValue: Int) {
    // 尝试发射,如果失败则丢弃
    val success = _state.tryEmit(newValue)
    if (!success) {
        // 处理发射失败的情况
    }
}

二、使用场景对比

1. 使用 emit() 的场景

kotlin 复制代码
// 1. 在 flow {} 构建器中
flow {
    for (i in 1..10) {
        delay(100)
        emit(i)  // ✅ 正确
    }
}

// 2. 在 callbackFlow 中
callbackFlow {
    callback.setOnData { data ->
        trySend(data)  // callbackFlow 特殊方法
    }
    awaitClose { callback.removeListener() }
}

2. 使用 tryEmit() 的场景

kotlin 复制代码
// 主要在 MutableStateFlow/MutableSharedFlow 中
class ViewModel {
    private val _uiState = MutableStateFlow<UiState>(Loading)
    val uiState: StateFlow<UiState> = _uiState
    
    fun update() {
        // 非挂起函数中尝试更新状态
        if (!_uiState.tryEmit(Success(data))) {
            // 处理背压(通常日志记录)
        }
    }
}

3. 下面是一个小表格,可以帮助快速了解何时使用 emit 和 tryEmit:

场景 使用 emit 使用 tryEmit
在挂起函数内部 是 避免使用(除非非常紧急)
UI 点击或生命周期事件 是 可能(如果需要快速更新)
回调、监听器或后台线程 避免使用(不能挂起) 是
需要保证数据送达 是 否(需自行处理失败情况)
发送即忘(fire-and-forget)场景 否 是

两句话概括就是:

  • 当你需要可靠的、对协程友好的发送方式时,使用 emit。
  • 当你希望实现无阻塞、快速触发且不暂停的操作时,使用 tryEmit。

三、重要注意事项

  1. callbackFlow 的特殊情况

    kotlin 复制代码
    callbackFlow {
        callback { data ->
            // 这里必须用 trySend,因为 callback 不是挂起上下文
            trySend(data)
        }
    }
  2. MutableSharedFlow 的配置影响

    kotlin 复制代码
    val flow = MutableSharedFlow<Int>(
        replay = 0,
        extraBufferCapacity = 10  // 缓冲区大小影响 tryEmit 成功率
    )
  3. 性能考虑

    • emit():更安全,保证数据不丢失
    • tryEmit():性能更好,但可能丢失数据

总结一下二者的区别:

特性 emit() tryEmit()
挂起函数 是 否
等待收集者 是(如果缓冲区已满) 否
返回类型 Unit Boolean
是否立即发射? 否(可能会挂起) 是(如果缓冲区允许)
是否可在协程中使用? 必须 不需要

四、最佳实践建议

  • 优先使用 emit() ,除非有特殊性能需求
  • 在 flow {} 构建器中总是使用 emit()
  • 在回调接口中(如 callbackFlow)使用 trySend()/tryEmit()
  • 对于状态更新,如果无法确保在协程上下文中,使用 tryEmit() 但要有失败处理
  • 明确处理 tryEmit() 返回的 false 情况

简单总结:emit() 是安全的阻塞版本,tryEmit() 是非阻塞的尝试版本,可能失败丢数据。

相关推荐
拥小抱8 小时前
Compose 知识点
android
文章永久免费只为良心8 小时前
Web应用安全与防护_第三章_日志文件包含_从一道 CTFshow 题目讲透「日志投毒型 LFI → RCE」:原理、时序陷阱与全变种攻防手册
android·网络安全·渗透测试·漏洞危害·漏洞描述·日志投毒·红队指南
AIsoft_86899 小时前
学术讲座录音如何整理?录音APP可行吗
android·iphone
Goway_Hui11 小时前
【KMP for OpenHarmony】-- 在鸿蒙工程中使用 Apollo-Kotlin
华为·kotlin·harmonyos
Patrick在香港11 小时前
一个斜杠,把毫秒变成十几秒
android·数据库·python·性能优化·正则表达式·回溯·redos
海雅达手持终端PDA12 小时前
工业手持终端的“去手机化”演进:看海雅达如何以“同轴扫摄”终结工程妥协,重塑工业PDA新范式
android·物联网·智能手机·硬件工程·能源·制造·iot
释厄62312 小时前
AI 军备竞赛悖论——为 AI 卖游戏但 AI 需要游戏母场景
人工智能·游戏·kotlin
booboosui13 小时前
删掉一个住在本机的 agent:8 处要清,顺序不能反
android
hcgeng14 小时前
Android获取崩溃日志
android