3. Flow基础回顾:冷流与热流、StateFlow与SharedFlow的区别、在权限场景下的选择

Kotlin Flow 是响应式编程在 Kotlin 协程中的核心抽象。理解冷流(Cold Flow)与热流(Hot Flow)的本质区别,以及 StateFlow 与 SharedFlow 的设计差异,是正确选择权限状态管理方案的前提。本章从概念、API 特性到权限场景匹配,建立流式状态管线的选型基础。

3.1 冷流与热流:核心区别

维度冷流(Cold Flow)热流(Hot Flow)
数据生产者每个 collector 触发独立的生产逻辑生产者在 collect 之前已启动,多个 collector 共享数据流
生命周期随 collector 开始和结束独立于 collector,持续活跃
数据重放不保留历史值,每次 collect 从头执行可配置 replay 缓存(如 1 表示始终保留最新值)
典型实现flow { … }、callbackFlowStateFlow、SharedFlow、ChannelFlow
权限场景匹配一次性请求 → 采集结果后结束持续监听 → 多界面共享最新权限状态

3.2 StateFlow vs SharedFlow:设计差异

StateFlow 是热流的一个变体,始终维护一个当前值(必须初始值非空),且只在值实际改变时发射。它适合表示“状态” —— 如权限是否已授予。

SharedFlow 是更通用的热流,支持配置 replay(重放数量)、extraBufferCapacity(额外缓冲区)、onBufferOverflow(溢出策略)。它既可以像事件总线一样工作(replay=0),也可以提供最新值(replay=1)。

特性StateFlowSharedFlow
初始值必须提供可选(replay=0 时无需)
值去重自动(distinctUntilChanged)不内置去重
重放数量固定为 replay=1可配置 0..Int.MAX_VALUE
溢出策略始终保留最新值(DROP_OLDEST)可配置 SUSPEND / DROP_OLDEST / DROP_LATEST
性能极轻量,适合高频状态稍重,但更灵活
典型用途UI 状态持有(ViewModel 中的权限状态)事件广播、多collector同步、一次性确认结果

3.3 权限场景下的选择逻辑

  1. 持续观察权限状态(如相机权限是否已授予)→ StateFlow 是最佳选择:天然带初始值、去重、最新值缓存。
  2. 一次性权限请求结果(如用户点击按钮后发起请求,只关心当前弹窗结果)→ 推荐使用 SharedFlow(replay=0) 或 Channel,避免重复消费历史结果。
  3. 多界面共享同一权限回调(如服务层监听权限变更后通知多个 fragment)→ SharedFlow(replay=1) 保证新订阅者能立刻获得最新状态。
  4. 权限状态与 UI 状态分离 → 使用 StateFlow 持有不可变的权限组合状态,SharedFlow 发送副作用(如权限拒绝提示事件)。

3.4 代码实现:冷流与热流对比

以下示例展示一个模拟权限检查的冷流与热流差异:

// 冷流:每次 collect 都独立执行权限检查
fun checkPermissionCold(
    permissionName: String
): Flow<PermissionResult> = flow {
    emit(PermissionResult.Checking)
    delay(100) // 模拟异步检查
    val granted = Random.nextBoolean()
    emit(if (granted) PermissionResult.Granted else PermissionResult.Denied)
}

// 热流:使用 StateFlow 持有权限状态,多个 collector 共享
class PermissionStateHolder {
    private val _state = MutableStateFlow<PermissionResult>(PermissionResult.Unknown)
    val state: StateFlow<PermissionResult> = _state.asStateFlow()

    fun update(result: PermissionResult) {
        _state.value = result
    }
}

// 事件流:使用 SharedFlow 广播一次性请求结果
private val _requestEvent = MutableSharedFlow<PermissionResult>(replay = 0)
val requestEvent: SharedFlow<PermissionResult> = _requestEvent.asSharedFlow()

suspend fun requestPermission() {
    _requestEvent.emit(PermissionResult.Granted)
}

// 使用示例(在 ViewModel 中)
fun observePermission() {
    viewModelScope.launch {
        // 冷流:每次 collect 重新执行
        checkPermissionCold(Manifest.permission.CAMERA)
            .collect { result ->
                // 处理结果
            }
    }
    // 热流:订阅已有状态
    viewModelScope.launch {
        permissionStateHolder.state.collect { result ->
            // 状态改变时触发
        }
    }
}
实践建议: 在 Android ViewModel 中,权限状态通常使用 StateFlow 暴露给 UI 层,而权限请求的临时结果(如用户点按拒绝)使用 SharedFlow 作为一次性事件,避免 UI 重建后重复消费。

3.5 Flow 类型关系图

下图展示了冷流、热流以及 StateFlow/SharedFlow 在权限场景下的定位关系:

Flow 类型与权限状态管理选型 Flow (协程数据流) 冷流 (Cold Flow) 热流 (Hot Flow) channelFlow / callbackFlow SharedFlow StateFlow 权限场景推荐 状态持续持有 → StateFlow (replay=1, 自带最新值) | 一次性事件 → SharedFlow (replay=0) | 复杂监听 → SharedFlow (replay=1)

3.6 性能与稳定性注意点

常见陷阱:
  • 将 SharedFlow(replay=0) 用于状态持有会导致新 collector 无法获得最新状态,引发权限状态丢失。
  • 冷流用于持续监听权限变更时,每次 collect 都会重新创建资源(如传感器监听),造成资源泄漏。
  • StateFlow 的初始值必须是合理默认值(如 PermissionResult.Unknown),不要用 null 表示未知,否则 UI 层难以区分“尚未检查”与“检查失败”。
  • 在 ViewModel 中暴露 StateFlow 时,始终使用 .asStateFlow() 转为只读引用,避免外部篡改状态。

3.7 调试要点

// Flow 调试工具:添加日志观察发射频率
fun <T> Flow<T>.debugLog(tag: String): Flow<T> = this
    .onStart { android.util.Log.d(tag, "collect started") }
    .onEach { android.util.Log.d(tag, "value = $it") }
    .onCompletion { android.util.Log.d(tag, "collect completed") }

// 在 ViewModel 中使用
permissionStateHolder.state
    .debugLog("PermissionState")
    .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), PermissionResult.Unknown)

部署标准: 在应用生产环境中,权限状态流必须使用热流(StateFlow 或 SharedFlow)而非冷流,以确保绑定的生命周期(如 Fragment 重建)后,新 collector 能立即获得当前有效状态,避免弹窗重复或状态闪白。