前言
主要介绍Flow在实际项目中使用。
Flow生命周期
在介绍Flow实际应用场景之前,我们先看一个Flow的计时器例子,我们在ViewModel定义了一个timeFlow数据流:
class MainViewModel : ViewModel() {
val timeFlow = flow {
var time = 0
while (true) {
emit(time)
delay(1000)
time++
}
}
然后Activity里面,接收前面定义的数据流。
lifecycleOwner.lifecycleScope.launch {
viewModel.timeFlow.collect { time ->
times = time
Log.d("ddup", "update UI $times")
}
}
实际效果:
发现,App切换到后台时,日志还在打印,这不是对资源的浪费,我们修改一下接收的地方代码:
lifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.timeFlow.collect { time ->
times = time
Log.d("ddup", "update UI $times")
}
}
协程开启的方法,从launch改成launchWhenStarted,再运行看下效果:
我们可以看到,当点击HOME键,退回到后台的时候,日志不再打印了,由此可见,改动生效了,但是流取消接收了吗,我们切回到前台看下:
切换到前台,我们可以看到,计数器并没有从0开始,所以其实它并没有取消接收,只是在后台暂停接收数据了,Flow管道还保留之前的数据,事实上这个launchWhenStarted API已经废弃了,Google更推荐repeatOnLifecycle来代替它,并且它不会存在管道中保留旧数据问题。
我们尝试改造一下对应代码:
lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.timeFlow.collect { time ->
times = time
Log.d("ddup", "update UI $times")
}
}
}
重新运行看下效果:
我们可以看到,从后台切回到前台数据又从0开始了,说明切换到后台,Flow取消工作了,原来的数据全部清空了。
我们在使用Flow,通过repeatOnLifecycle,更能保证我们程序的安全性。
SharedFlow vs StateFlow 区别
SharedFlow
和 StateFlow
是两个处理数据流的利器,它们基于协程,提供了一种响应式的编程方式。
原理分析
SharedFlow
和 StateFlow
基于协程构建,它们利用协程的轻量级特性,在异步操作中更加高效。
SharedFlow
使用了一种基于事件溯源的机制,当有新的事件产生时,将事件添加到共享的事件序列中,然后通知所有订阅者。
而 StateFlow
则维护了一个可变的状态,并在状态发生变化时通知所有观察者。
热流与冷流
热流和冷流是关于数据流的两个基本概念,它们描述了数据流何时开始以及如何传递事件的方式。
- 热流是一种主动的数据流。它在创建时就开始发射事件,无论是否有观察者订阅。即使没有观察者,热流也会持续产生事件。当观察者订阅时,它只是加入了已经运行的数据流,开始接收当前已经产生的事件。
- 冷流是一种被动的数据流。它在有观察者订阅时才开始发射事件。每个观察者都会获得相同的事件序列,而不会受到其他观察者的影响。
SharedFlow
和 StateFlow
都是热流。即没有观察者,数据会持续更新,与LiveData
类似。其中MutableSharedFlow
与MutableStateFlow
是它们的可变类型。
SharedFlow、StateFlow与LiveData的区别
StateFlow
就是SharedFlow
的一种特殊类型,特点有三:
- 它的replay容量为 1;即可缓存最近的一次粘性事件,如果想避免粘性事件问题,使用
SharedFlow
,replay默认值0。 - 初始化时必须给它设置一个初始值
- 每次发送数据都会与上次缓存的数据作比较,只有不一样才会发送。它还可直接访问它自己的value参数获取当前结果值,在使用上与
LiveData
相似。
与LiveData
的不同点
StateFlow
必须在构建的时候传入初始值,LiveData
不需要;StateFlow
默认是防抖的,即相同值不更新,LiveData
默认不防抖;StateFlow
默认没有和生命周期绑定
StateFlow 经常被用来替代 LiveData 充当架构组件使用,所以大家相对熟悉。其实 StateFlow 只是 SharedFlow 的一种特化形式,
StateFlow 替代LiveData
前面介绍的都是Flow冷流例子,接下来将会介绍一些热流常见的应用场景。
还是前面的计时器的例子,假如横竖屏切换后,又会出现什么情况呢?
我们可以看到,横竖屏切换后,,Activity重新创建,重新创建后,timeFlow会重新collect,冷流被重新collect后重新执行,然后计时器又从0开始计时了,很多时候,我们希望横竖屏切换时,希望页面的状态是保持不变的,至少在一定时间内不被改变的,这里我们冷流修改成热流试下:
======================================================================
在协程中,通过调用操作符shareIn与stateIn,可以将一个冷流转换成一个热流,这两个方法的区别如下:
shareIn:将一个冷流转换成一个标准的热流——SharedFlow类型的对象。
stateIn:将一个冷流转换成一个单数据更新的热流——StateFlow类型的对象。
======================================================================
val hotFlow = timeFlow.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000),0)
```
lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.hotFlow.collect { time ->
times = time
Log.d("ddup", "update UI $times")
}
}
}
```
stateIn里面的三个参数,第一个是协程的作用域,第二个是flow保持工作状态最大有效时间,超过flow就会停止工作,最后一个参数是初始值。
重新运行看下效果:
这里我们可以看到横竖屏切换后,打印的日志,计时器不会从0开始了。
我们上面介绍了一个冷流如何修改变成热流的,这里还没有介绍stateFlow如何代替LiveData,下面介绍一下,stateFlow替代LiveData用法:
private val _stateFlow = MutableStateFlow(0)
val stateFlow = _stateFlow.asStateFlow()
fun startTimer() {
val timer = Timer()
timer.scheduleAtFixedRate(object :TimerTask() {
override fun run() {
_stateFlow.value += 1
}
},0,1000)
}
```
viewModel.startTimer()
lifecycleOwner.lifecycleScope.launch {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.stateFlow.collect { time ->
times = time
Log.d("ddup", "update UI $times")
}
}
}
```
我们定义了一个StateFlow热流,然后通过一个startTimer()方法改变stateFlow值类似LiveData setData,点击按钮时,开始改变StateFlow值并收集对应流的值类似LiveData Observe方法监听数据变化。
下面看下实际运行效果:
到这里,我们介绍完了StateFlow基本用法,下面来介绍SharedFlow。
SharedFlow
SharedFlow 是继承于 Flow ,同时它是 StateFlow 的父类,它们都是是热流。
SharedFlow的特点
- SharedFlow没有默认值
- SharedFlow可以保存旧的数据,根据配置可以将旧的数据回播给新的订阅者
- SharedFlow使用emit/tryEmit发射数据,StateFlow内部其实都是调用的setValue。
- SharedFlow会挂起直到所有的订阅者处理完成。