StateFlow和SharedFlow 都是热流,它们可以在多个收集器之间共享数据。本文主要就以下个方面进行讲解:
一、基本使用
二、如何取舍
三、二者的关系
一、基本使用
1.0引入相关配置(可跳过)
libs.versions.toml
ini
[versions]
lifecycleRuntimeKtx = "2.6.1"
[libraries]
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" }
app build.gradle.kts
scss
implementation(libs.androidx.lifecycle.viewmodel.compose)
1.1 StateFlow的基本使用 StateFlow的基本使用方式和LiveData类似,甚至可以说StateFlow就是LiveData的平替。
ViewModel
kotlin
class FlowViewModel:ViewModel() {
private val _currentTime= MutableStateFlow<String>("")
val currentTime:StateFlow<String> = _currentTime.asStateFlow()
fun getCurrentTime(){
val format=SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val date= Date()
val time=format.format(date)
_currentTime.value = "当前时间:${time}"
}
}
MainActivity
kotlin
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
ShowCurrentTime()
}
}
}
@Composable
fun ShowCurrentTime(modifier: Modifier = Modifier) {
val viewModel: FlowViewModel = viewModel()
val timeState by viewModel.currentTime.collectAsState()
Column(modifier.padding(top = 30.dp, start = 10.dp)) {
Button(onClick = {
viewModel.getCurrentTime()
}) {
Text("点击获取时间")
}
Text(text = timeState)
}
}
上面的代码非常简单,首先在ViewModel中创建了一个私有的MutableStateFlow,然后再创建了一个可访问的StateFlow,这一点和LiveData是不是很相似,最后再创建了个getCurrentTime方法;在MainActivity中 通过ShowCurrentTime方法来显示当前UI,其中点击Button就可以调用viewModel中的getCurrentTime方法。 其中最终要的就是 viewModel.currentTime.collectAsState()。
1.2 SharedFlow的基本使用
kotlin
private val _currentTimeShareFlow=MutableSharedFlow<String>()
val currentTimeShareFlow:SharedFlow<String> = _currentTimeShareFlow.asSharedFlow()
fun getCurrentTimeShareFlow(){
val format=SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
val date= Date()
val time=format.format(date)
viewModelScope.launch {
_currentTimeShareFlow.emit("当前时间:${time}")
}
}
SharedFlow的发射端基本和StateFlow一致,但有两点不同1、它不需要初始值,2、发射数据需要手动调用,并且需要在协程作用域中调用;SharedFlow的接收端有2种方式接收。
方式一、通过LaunchedEffect启动协程作用域,不修改发射端的代码,因为sharedFlow通过emit方法发射出来,所以我们可以通过collect来收集,具体代码如下:
scss
@Composable
fun ShowCurrentTimeShareFlow(modifier: Modifier = Modifier) {
val viewModel: FlowViewModel = viewModel()
var currentTime by remember { mutableStateOf("") }
LaunchedEffect(Unit) {
viewModel.currentTimeShareFlow.collect{
currentTime=it
}
}
Column(modifier.padding(top = 30.dp, start = 10.dp)) {
Button(onClick = {
viewModel.getCurrentTimeShareFlow()
}) {
Text("点击获取时间")
}
Text(text = currentTime)
}
}
上面的代码新增了一行var currentTime by remember { mutableStateOf("") } 因为我们在collect方法中收集了数据,但是collect不能调用Compose方法,因此不能直接在collect方法中使用Text(it) 所以新增了一个成员变量。运行结果也是一模一样。
方式二、发射端和接收端都需要修改代码,具体如下:
arduino
private val _currentTimeShareFlow=MutableSharedFlow<String>(replay = 1)
val currentTimeShareFlow:SharedFlow<String> = _currentTimeShareFlow.asSharedFlow()
ini
@Composable
fun ShowCurrentTimeShareFlow2(modifier: Modifier = Modifier) {
val viewModel: FlowViewModel = viewModel()
val currentTime by viewModel.currentTimeShareFlow.collectAsState(initial = "2013-06-05")
Column(modifier.padding(top = 30.dp, start = 10.dp)) {
Button(onClick = {
viewModel.getCurrentTimeShareFlow()
}) {
Text("点击获取时间")
}
Text(text = currentTime)
}
}
上述代码中发射端新增了一个replay = 1 的参数,接收端也新增了一个initial = "2013-06-05" 的参数,接收端的这个很好理解,就是初始值,程序一运行上来,ShareFlow没有发射值的情况下,就是用这个initial的值;
那replay=1是什么意思呢?可以去掉吗?
先回答第二个问题,这里可以去掉,我们去掉replay = 1程序依然能正常运行,这里之所以加上,主要是提前引出第二大介绍内容(稍后再说)。
那replay=1究竟是什么意思呢?它的意思是缓存最近发射的1个值,新的订阅者会立即收到这个值。避免状态不一致的问题。
例如当屏幕旋转时UI会重建,但是我们设置了replay=1界面上显示的还是最新数据。
移除replay=1,我们会发现随着屏幕的旋转,界面上的最新数据在不断变动。
二、如何取舍
先说说什么是粘性,StateFlow 和 ShareFlow 的本质都是基于发送者观察者设计模式来设计的,如果观察者在未观察之前,发送者已经发送了数据,当观察者开始观察时,要不要收到刚刚发送者发送的数据。如果还能收到刚才的数据这种行为就叫做粘性,如果不能收到那么就叫非粘性。这两种行为需要到具体的业务中具体判断具体分析。
2.1 StateFlow是粘性的
举个例子viewModel中的代码不动,修改Actiivty中的代码
kotlin
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Column {
val viewModel: FlowViewModel = viewModel()
var isEnter by remember { mutableStateOf(false) }
ShowCurrentTime{
isEnter =true
}
if(isEnter){
SystemClock.sleep(100)
val time=Date().time
val currentTime by viewModel.currentTime.collectAsState()
Text(text ="$currentTime---${time}")
}
}
}
}
}
scss
@Composable
fun ShowCurrentTime(modifier: Modifier = Modifier,onClick:()->Unit) {
val viewModel: FlowViewModel = viewModel()
val timeState by viewModel.currentTime.collectAsState()
var number by remember { mutableStateOf(0) }
Column(modifier.padding(top = 30.dp, start = 10.dp)) {
Button(onClick = {
viewModel.getCurrentTime()
if(number==1){
onClick.invoke()
}
number++
}) {
Text("点击获取时间")
}
val time=Date().time
if(!timeState.isNullOrEmpty()){
Text(text = "${timeState}---${time}")
}
}
}
上面的代码很简单,在ShowCurrentTime方法中,一上来就开始观察currentTime,然后点击Button会调用viewModel中的getCurrentTime()方法,再然后会触发ShowCurrentTime方法进行重组,将时间显示在Text上。当第2次点击Button的时候,会执行onClick回调,接着setContent方法中触发重组,休眠100毫秒后才开始观察currentTime。
如果两个Text的数据一值,则说明StateFlow是粘性的,因为setContent中的Text是在先发送,并且休眠了100毫秒后再观察的。
可以看到两个Text前面的值是一模一样,后面的毫秒值也是第二个比第一个大了100毫秒。所以StateFlow 是粘性的。
2.2SharedFlow是非粘性的
举个例子: 修改viewModel中的代码:
arduino
private val _currentTimeShareFlow=MutableSharedFlow<String>()
val currentTimeShareFlow:SharedFlow<String> = _currentTimeShareFlow.asSharedFlow()
修改Activity中的代码
scss
@Composable
fun ShowCurrentTimeShareFlow2(modifier: Modifier = Modifier,onClick:(Int)->Unit) {
val viewModel: FlowViewModel = viewModel()
val currentTime by viewModel.currentTimeShareFlow.collectAsState(initial = "2013-06-05")
var number by remember { mutableStateOf(0) }
Column(modifier.padding(top = 30.dp, start = 10.dp)) {
Button(onClick = {
viewModel.getCurrentTimeShareFlow()
number++
if(number==1){
onClick.invoke(1)
}
}) {
Text("点击获取时间")
}
val time=Date().time
if(!currentTime.isNullOrEmpty()){
Text(text = "${currentTime}---${time}")
}
}
}
kotlin
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Column {
val viewModel: FlowViewModel = viewModel()
var isEnter by remember { mutableStateOf(false) }
ShowCurrentTimeShareFlow2{
isEnter =true
}
if(isEnter){
SystemClock.sleep(100)
val time=Date().time
val currentTime by viewModel.currentTimeShareFlow.collectAsState(initial = "2013-06-05")
Text(text ="$currentTime---${time}")
}
}
}
}
}
上面代码的逻辑和之前的StateFlow差不多,就不做过多的解释了,我们看同样的方式,第二个Text在延迟100毫秒后再观察数据源,就和第一个有差别了,他们的数据不统一,也就坐实了ShareFlow是非粘性的。

细心的同学已经发现了,我们将viewModel中的shareFlow 中的 replay = 1去掉了,如果加上会是什么效果? 修改代码
arduino
private val _currentTimeShareFlow=MutableSharedFlow<String>(replay = 1)
val currentTimeShareFlow:SharedFlow<String> = _currentTimeShareFlow.asSharedFlow()

WTF 居然实现了StateFlow一模一样的效果,居然也可以是粘性的。
先抛出问题,这一点我们后面再说。
粘性是选择使用StateFlow 和使用SharedFlow的一个参考,另外一个重要的参考是 StateFlow表示共享1个单一的,随时间演变的,当前最新状态;SharedFlow是广播给0个或者多个接受者,事假是离散的,一次性的。
所以我们表示界面状态的时候就用StateFlow,如果有事件处理就选SharedFlow。
例如 StateFlow的核心是"始终持有可被观察的当下",就像手机电量显示------你任何时候看屏幕都必须看到当前真实电量;而SharedFlow更像是微信消息,新消息来了就推送,但不会总把三个月前的聊天记录塞给你。
三、二者的关系
前面我们通过修改SharedFlow中的replay=1,就达成了和StateFlow一样的粘性效果,隐隐预约感觉二者之间有一定的联系,先看看源码.
kotlin
public interface MutableStateFlow<T> : StateFlow<T>, MutableSharedFlow<T> {
/**
* The current value of this state flow.
*
* Setting a value that is [equal][Any.equals] to the previous one does nothing.
*
* This property is **thread-safe** and can be safely updated from concurrent coroutines without
* external synchronization.
*/
public override var value: T
/**
* Atomically compares the current [value] with [expect] and sets it to [update] if it is equal to [expect].
* The result is `true` if the [value] was set to [update] and `false` otherwise.
*
* This function use a regular comparison using [Any.equals]. If both [expect] and [update] are equal to the
* current [value], this function returns `true`, but it does not actually change the reference that is
* stored in the [value].
*
* This method is **thread-safe** and can be safely invoked from concurrent coroutines without
* external synchronization.
*/
public fun compareAndSet(expect: T, update: T): Boolean
}
原来StateFlow 是SharedFlow的一个子类啊!
既然SharedFlow能实现StateFlow的效果那为什么还需要单独设计StateFlow呢?
主要原因在于StateFlow不是为了能做SharedFlow能做的事情,而是为了在状态管理领域,提供一种更优化、更简洁、更符合特定语义的专业工具。
举个不太恰当的例子:螺丝刀能不能当锤子用,偶尔用上两次问题不大,但是不能一直把螺丝刀当锤子用啊。
对比StateFlow和sharedFlow不难发现,StateFlow强制要求初始值,保证任何时候都有状态可查,SharedFlow则没有;另外StateFlow.update方法提供了一种原子化的、基于旧值计算新值的便捷方式,非常适合状态更新逻辑。SharedFlow 没有这个。
总结:SharedFlow 是强大的事件广播工具。StateFlow 是专门为状态管理优化的工具