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 { … }、callbackFlow | StateFlow、SharedFlow、ChannelFlow |
| 权限场景匹配 | 一次性请求 → 采集结果后结束 | 持续监听 → 多界面共享最新权限状态 |
3.2 StateFlow vs SharedFlow:设计差异
StateFlow 是热流的一个变体,始终维护一个当前值(必须初始值非空),且只在值实际改变时发射。它适合表示“状态” —— 如权限是否已授予。
SharedFlow 是更通用的热流,支持配置 replay(重放数量)、extraBufferCapacity(额外缓冲区)、onBufferOverflow(溢出策略)。它既可以像事件总线一样工作(replay=0),也可以提供最新值(replay=1)。
| 特性 | StateFlow | SharedFlow |
|---|---|---|
| 初始值 | 必须提供 | 可选(replay=0 时无需) |
| 值去重 | 自动(distinctUntilChanged) | 不内置去重 |
| 重放数量 | 固定为 replay=1 | 可配置 0..Int.MAX_VALUE |
| 溢出策略 | 始终保留最新值(DROP_OLDEST) | 可配置 SUSPEND / DROP_OLDEST / DROP_LATEST |
| 性能 | 极轻量,适合高频状态 | 稍重,但更灵活 |
| 典型用途 | UI 状态持有(ViewModel 中的权限状态) | 事件广播、多collector同步、一次性确认结果 |
3.3 权限场景下的选择逻辑
- 持续观察权限状态(如相机权限是否已授予)→
StateFlow是最佳选择:天然带初始值、去重、最新值缓存。 - 一次性权限请求结果(如用户点击按钮后发起请求,只关心当前弹窗结果)→ 推荐使用
SharedFlow(replay=0)或Channel,避免重复消费历史结果。 - 多界面共享同一权限回调(如服务层监听权限变更后通知多个 fragment)→
SharedFlow(replay=1)保证新订阅者能立刻获得最新状态。 - 权限状态与 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 在权限场景下的定位关系:
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 能立即获得当前有效状态,避免弹窗重复或状态闪白。