官方推荐 Flow 取代 LiveData, 有必要吗?
其主要有 3 个参数
1.replay
表示当新的订阅者Collect
时,发送几个已经发送过的数据给它,默认为 0,即默认新订阅者不会获取以前的数据
2.extraBufferCapacity
表示减去replay
,MutableSharedFlow
还缓存多少数据,默认为 0
3.onBufferOverflow
表示缓存策略,即缓冲区满了之后Flow
如何处理,默认为挂起
简单使用如下:
//ViewModel
val sharedFlow=MutableSharedFlow<String>()
viewModelScope.launch{
sharedFlow.emit("Hello")
sharedFlow.emit("SharedFlow")
}
//Activity
lifecycleScope.launch{
viewMode.sharedFlow.collect {
print(it)
}
}
3.3 将冷流转化为SharedFlow
普通flow
可使用shareIn
扩展方法,转化成SharedFlow
val sharedFlow by lazy {
flow<Int> {
//...
}.shareIn(viewModelScope, WhileSubscribed(500), 0)
}
shareIn
主要也有三个参数:
@param
scope
共享开始时所在的协程作用域范围
@param
started
控制共享的开始和结束的策略
@param
replay
状态流的重播个数
started
接受以下的三个值:
1.Lazily
: 当首个订阅者出现时开始,在scope
指定的作用域被结束时终止。
2.Eagerly
: 立即开始,而在scope
指定的作用域被结束时终止。
3.WhileSubscribed
: 这种情况有些复杂,后面会详细讲解
对于那些只执行一次的操作,您可以使用Lazily
或者Eagerly
。然而,如果您需要观察其他的流,就应该使用WhileSubscribed
来实现细微但又重要的优化工作
3.4 Whilesubscribed
策略
WhileSubscribed
策略会在没有收集器的情况下取消上游数据流,通过shareIn
运算符创建的SharedFlow
会把数据暴露给视图 (View
),同时也会观察来自其他层级或者是上游应用的数据流。
让这些流持续活跃可能会引起不必要的资源浪费,例如一直通过从数据库连接、硬件传感器中读取数据等等。当您的应用转而在后台运行时,您应当保持克制并中止这些协程。
public fun WhileSubscribed(
stopTimeoutMillis: Long = 0,
replayExpirationMillis: Long = Long.MAX_VALUE
)
如上所示,它支持两个参数:
1.
stopTimeoutMillis
控制一个以毫秒为单位的延迟值,指的是最后一个订阅者结束订阅与停止上游流的时间差。默认值是 0 (立即停止).这个值非常有用,因为您可能并不想因为视图有几秒钟不再监听就结束上游流。这种情况非常常见——比如当用户旋转设备时,原来的视图会先被销毁,然后数秒钟内重建。2.
replayExpirationMillis
表示数据重播的过时时间,如果用户离开应用太久,此时您不想让用户看到陈旧的数据,你可以用到这个参数
4. StateFlow
介绍
4.1 为什么引入StateFlow
我们前面刚刚看了SharedFlow
,为什么又冒出个StateFlow
?
StateFlow
是 SharedFlow
的一个比较特殊的变种,StateFlow
与 LiveData
是最接近的,因为:
1.它始终是有值的。
2.它的值是唯一的。
3.它允许被多个观察者共用 (因此是共享的数据流)。
4.它永远只会把最新的值重现给订阅者,这与活跃观察者的数量是无关的。
可以看出,StateFlow
与LiveData
是比较接近的,可以获取当前的值,可以想像之所以引入StateFlow
就是为了替换LiveData
总结如下:
1.StateFlow
继承于SharedFlow
,是SharedFlow
的一个特殊变种
2.StateFlow
与LiveData
比较相近,相信之所以推出就是为了替换LiveData
4.2 StateFlow
的简单使用
我们先来看看构造函数:
public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)
1.StateFlow
构造函数较为简单,只需要传入一个默认值
2.StateFlow
本质上是一个replay
为 1,并且没有缓冲区的SharedFlow
,因此第一次订阅时会先获得默认值
3.StateFlow
仅在值已更新,并且值发生了变化时才会返回,即如果更新后的值没有变化,也没会回调Collect
方法,这点与LiveData
不同
与StateFlow
类似,我们也可以用stateIn
将普通流转化成SharedFlow
val result: StateFlow<Result<UiState>> = someFlow
.stateIn(
scope = viewModelScope,
started = WhileSubscribed(5000),
initialValue = Result.Loading
)
与shareIn
类似,唯一不同的时需要传入一个默认值
同时之所以WhileSubscribed
中传入了5000
,是为了实现等待5
秒后仍然没有订阅者存在就终止协程的功能,这个方法有以下功能
用户将您的应用转至后台运行,5 秒钟后所有来自其他层的数据更新会停止,这样可以节省电量。
最新的数据仍然会被缓存,所以当用户切换回应用时,视图立即就可以得到数据进行渲染。
订阅将被重启,新数据会填充进来,当数据可用时更新视图。
在屏幕旋转时,因为重新订阅的时间在 5s 内,因此上游流不会中止
4.3 在页面中观察StateFlow
与LiveData
类似,我们也需要经常在页面中观察StateFlow
观察StateFlow
需要在协程中,因此我们需要协程构建器,一般我们会使用下面几种
lifecycleScope.launch
: 立即启动协程,并且在本Activity
或Fragment
销毁时结束协程。LaunchWhenStarted
和LaunchWhenResumed
,它会在lifecycleOwner
进入X
状态之前一直等待,又在离开X
状态时挂起协程
如上图所示:
1.使用launch
是不安全的,在应用在后台时也会接收数据更新,可能会导致应用崩溃
2.使用launchWhenStarted
或launchWhenResumed
会好一些,在后台时不会接收数据更新,但是,上游数据流会在应用后台运行期间保持活跃,因此可能浪费一定的资源
这么说来,我们使用WhileSubscribed
进行的配置岂不是无效了吗?订阅者一直存在,只有页面关闭时才会取消订阅
官方推荐repeatOnLifecycle
来构建协程
在某个特定的状态满足时启动协程,并且在生命周期所有者退出该状态时停止协程,如下图所示。
比如在某个Fragment
的代码中:
onCreateView(...) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(STARTED) {
myViewModel.myUiState.collect { ... }
}
}
}
当这个Fragment
处于STARTED
状态时会开始收集流,并且在RESUMED
状态时保持收集,最终在Fragment
进入STOPPED
状态时结束收集过程。
结合使用repeatOnLifecycle API
和WhileSubscribed
,可以帮助您的应用妥善利用设备资源的同时,发挥最佳性能
4.4 页面中观察Flow
的最佳方式
通过ViewModel
暴露数据,并在页面中获取的最佳方式是:
最佳实践如上图所示,如果采用其他方式,上游数据流会被一直保持活跃,导致资源浪费
当然,如果您并不需要使用到Kotlin Flow
的强大功能,就用LiveData
好了 :)
5 StateFlow
与SharedFlow
有什么区别?
从上文其实可以看出,StateFlow
与SharedFlow
其实是挺像的,让人有些傻傻分不清,有时候也挺难选择该用哪个的
我们总结一下,它们的区别如下:
SharedFlow
配置更为灵活,支持配置replay
,缓冲区大小等,StateFlow
是SharedFlow
的特化版本,replay
固定为 1,缓冲区大小默认为 0StateFlow
与LiveData
类似,支持通过myFlow.value
获取当前状态,如果有这个需求,必须使用StateFlow
SharedFlow
支持发出和收集重复值,而StateFlow
当value
重复时,不会回调collect
对于新的订阅者,
StateFlow
只会重播当前最
新值,SharedFlow
可配置重播元素个数(默认为 0,即不重播)
可以看出,StateFlow
为我们做了一些默认的配置,在SharedFlow
上添加了一些默认约束,这些配置可能并不符合我们的要求
它忽略重复的值,并且是不可配置的。这会带来一些问题,比如当往
List
中添加元素并更新时,StateFlow
会认为是重复的值并忽略它需要一个初始值,并且在开始订阅时会回调初始值,这有可能不是我们想要的
它默认是粘性的,新用户订阅会获得当前的最新值,而且是不可配置的,而
SharedFlow
可以修改replay
StateFlow
施加在SharedFlow
上的约束可能不是最适合您,如果不需要访问myFlow.value
,并且享受SharedFlow
的灵活性,可以选择考虑使用SharedFlow
总结
--
简单往往意味着不够强大,而强大又常常意味着复杂,两者往往不能兼得,软件开发过程中常常面临这种取舍。
LiveData
的简单并不是它的缺点,而是它的特点。StateFlow
与SharedFlow
更加强大,但是学习成本也显著的更高.
我们应该根据自己的需求合理选择组件的使用
如果你的数据流比较简单,不需要进行线程切换与复杂的数据变换,
LiveData
对你来说相信已经足够了如果你的数据流比较复杂,需要切换线程等操作,不需要发送重复值,需要获取
myFlow.value
,StateFlow
对你来说是个好的选择如果你的数据流比较复杂,同时不需要获取
myFlow.value
,需要配置新用户订阅重播无素的个数,或者需要发送重复的值,可以考虑使用SharedFlow
参考资料
Google 推荐在 MVVM 架构中使用 Kotlin Flow
Migrate from LiveData to StateFlow and SharedFlow
关于kotlin中的Collections、Sequence、Channel和Flow (二)
其他资料
评论