首先出一道题目开始
kotlin
class TestActivity : FragmentActivity() {
val viewModel: TestVM by lazy {
ViewModelProvider(this).get(TestVM::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.test)
setupObservers(1)
}
override fun onStop() {
super.onStop()
viewModel.liveData.value = true
lifecycleScope.launch {
viewModel.stateFlow.emit(true)
viewModel.sharedFlow.emit(true)
delay(1000L)
setupObservers(2)
}
}
fun setupObservers(i: Int) {
viewModel.liveData.observe(this, { it ->
Log.i("TestModel", "$i liveData observer: $it")
})
lifecycleScope.launch {
viewModel.stateFlow.collect { value ->
Log.i("TestModel", "$i stateFlow: $value")
}
}
lifecycleScope.launch {
viewModel.sharedFlow.collect { value ->
Log.i("TestModel", "$i sharedFlow: $value")
}
}
}
}
class TestVM : ViewModel() {
val stateFlow = MutableStateFlow(false)
val sharedFlow =
MutableSharedFlow<Boolean>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
val liveData = MutableLiveData<Boolean>()
}
如上代码所示,进入TestActivity→ 退到后台 → 从后台返回至前台。 你猜日志打印什么?
进入TestActivity

退至后台

后台返回前台

从上面的问题可以得出几个官点:
- StateFlow初始化必须要有一个初值,LiveData可有可没有
- LiveData只有在可见的时候(onStart→onStop(不包含)),订阅者才能收到值,非可见的时候发生值的变更,只是保存(保存一次),等到可见的时候,才分发给订阅者
- 不可见的时候触发值的更新,StateFlow和SharedFlow都会立马回调给观察者,但是LiveData需要变成可见的时候才回调
- MutableSharedFlow 可以配置缓存size
LiveData、SharedFlow、StateFlow 的区别
特性 | LiveData | SharedFlow | StateFlow |
---|---|---|---|
生命周期感知 | ✅ 自动 | ❌ 需手动管理 | ❌ 需手动管理 |
必须有初始值 | ❌ 可选 | ❌ 不需要 | ✅ 必须提供 |
订阅后立即上到上一次值 | ✅ 会 | ❌ 否 (可配置 (replay )) |
✅ 会 |
多个订阅者 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
背压处理 | ❌ 无 | ✅ 可配置缓冲区 | ✅ 固定缓冲区(size=1) |
线程安全 | ✅ 主线程安全 | ✅ 协程上下文安全 | ✅ 协程上下文安全 |
数据去重 | ❌ 不自动去重 | ❌ 不自动去重 | ✅ 自动去重 |
数据发射方式 | setValue() , postValue() |
emit() , tryEmit() |
emit() , tryEmit() |
是否保存状态 | ✅ 是 | ❌ 否(可配置replay) | ✅ 是 |
观察方式 | liveData+lifecycle | 协程收集 | 协程收集 |
推荐使用场景 | 简单UI状态更新 | 一次性事件+使用流式转换 | UI状态+使用流式转换 |
总结
SharedFlow:适用了一次性的事件并且可以使用流式转换
StateFlow: 也是Kotlin官方用来替代LiveData的,因为他支持更复杂的操作,但是Kotlin没有耦合进Android特有的生命周期,无生命周期的感知能力并且需要搭配协程使用。
kotlin
//最佳实践
lifecycleScope.launch{
repeatOnLifeCycle(Lifecycle.State.STARTED){
stateFlow.collect{ value ->
textView.text=value
}
}
}
使用repeatOnLifeCycle 这样就让stateFlow具备了生命周期感知能力
LiveData: 简单的UI状态更新
大部分情况下使用LiveData就行了,三者使用
思考
为什么MVI 架构中我们看到的使用StateFlow那一套比较多?
1. MVI 想的是「状态是整个界面的唯一真相源」
- View 不自己存状态。
- 状态只存在于 ViewModel 的单个对象中。
- 当你知道这个对象的当前值,你就能100%还原界面。
LiveData 在生命周期变化时,可能自动暂停、延迟派发、自动 re-dispatch,这些"智能操作"会让状态的可预测性下降。 而 StateFlow 不管这些,你自己控制收集时机,所以状态更确定。
2.MVI 强调「所有副作用可控、不会重复」
- LiveData 的"粘性"+"重新变 active 就重新回调"机制,会让你重复执行一次性事件(比如弹两次 Toast)。
- StateFlow + SharedFlow 明确区分:
- StateFlow:保存界面状态
- SharedFlow:发射一次性事件(不会自动重放)
这正符合 MVI 的理念:"状态可重放,副作用不可重放"。
3. MVI 追求「跨平台/可测试的纯逻辑层」
LiveData 是 Android 平台的特产(依赖 LifecycleOwner
、Looper
)。 而 StateFlow 纯 Kotlin 协程实现,既能跑在 Android,也能跑在 JVM/Compose/多平台项目里。
所以在 MVI + Kotlin 协程 体系下,LiveData 显得"多余又限制多"。