前面聊过协程和挂起函数,实际工程中真正高频出现的是 Flow。它把响应式编程带进了 Kotlin,但很多人用了一段时间还是分不清冷流和热流。
一、冷流:只有收集者才触发发射
fun coldFlow(): Flow<Int> = flow {
println("flow started")
for (i in 1..3) {
emit(i)
}
}
// 两个 collect,flow 会执行两次
coldFlow().collect { println(it) }
coldFlow().collect { println(it) }
flow { } 构建的是冷流。每次 collect,上游代码都会重新执行。这和 RxJava 的 Observable 不一样,更像是 RxJava 的 defer。
二、热流:状态常驻,多个订阅者共享
热流用 SharedFlow 和 StateFlow。
private val _events = MutableSharedFlow<String>()
val events: SharedFlow<String> = _events
private val _state = MutableStateFlow(0)
val state: StateFlow<Int> = _state.asStateFlow()
三、背压:生产快、消费慢怎么办
flow {
for (i in 1..10_000) emit(i)
}
.buffer() // 缓冲,发射不等待消费
.conflate() // 只保留最新值,跳过中间值
.collectLatest { } // 有新值时取消旧值处理
背压策略要按业务选:
实时性要求高 →
collectLatest不能丢数据 →
buffer或channelFlow允许采样 →
conflate或sample
四、Flow 与生命周期
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val users by viewModel.users.collectAsStateWithLifecycle()
}
collectAsStateWithLifecycle 会在 App 进入后台时自动停止收集,避免无效更新和内存泄漏。
五、常见反模式
把一次性事件存在 StateFlow 里:
StateFlow会重放最新值,事件重复消费容易出错。事件用SharedFlow。在 Flow 里做耗时操作不切换线程:Flow 默认跑在收集者所在线程,记得用
flowOn(Dispatchers.IO)。用
stateIn时没指定 SharingStarted:默认Eagerly会立即启动,后台也会跑。通常用WhileSubscribed(5000)。
val state = repository.loadData()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
小结
冷流按需启动,热流常驻共享
事件用 SharedFlow,状态用 StateFlow
背压策略根据实时性 vs 完整性选
用 lifecycle-aware 的收集方式,避免内存泄漏
下一篇,我们回到 UI 动效,看看 Android 属性动画和 MotionLayout 怎么做出顺滑的交互。
参考阅读
引用链接
[1]Kotlin Flow 官方文档: https://kotlinlang.org/docs/flow.html
[2]StateFlow and SharedFlow: https://developer.android.com/kotlin/flow/stateflow-and-sharedflow