记得在之前掘金上看到Google开发者的账号发了一篇《从 LiveData 迁移到 Kotlin 数据流》的文章。在之前接触ViewModel和LiveDta的时候就有在思考,ViewModel和Repository之间交互,通过什么来实现。后来翻了一下资料,发现官方推荐在ViewModel和Repository通过Flow来作为桥梁进行交互。
为了响应官方号召,我又一顿了解Flow。但在了解了Flow之后,当时心中就有大大的疑惑,Flow能够实现LiveData的功能,并且比LiveData功能更加强大,为什么不使用Flow来作为View和ViewModel之间的桥梁。
而现在官方确实推荐将Flow作为方案来替代LiveData。LiveData一脸懵逼:那我走?
LiveData
LiveData在2017年推出以来,作为Jetpack大家族的元老级人物,为安卓的MVVM架构作出了非凡的贡献,毕竟在当时的背景环境,大家都深陷RxJava的支配。而LiveData作为观察者模式的框架,能够以更平滑的学习曲线来实现变量的订阅,比起RxJava那一套更加轻量级,而且作为Google的亲儿子,在生命周期的管理上也有更出色的表现。
LiveData的缺点:
而LiveData它的缺点其实也非常明显,LiveData肩负着为UI提供数据订阅的能力,所以他的数据订阅只能在主线程,可能会有小伙伴说可以在子线程通过postValue去发布数据啊。但是其实这个postValue是有坑的,被坑过的小伙伴都应该知道短时间通过多次postValue,中间可能会存在数据的丢失。
而且在复杂的场景LiveData支持的能力确实有一些尴尬。
总结一下LiveDta有几个缺点:
-
在异步线程修改数据可能存在数据丢失的问题
-
在复杂的场景,LiveData的能力有一些捉襟见肘
LiveData你别走
但我们也不应该踩一捧一,确实LiveData整体上有更低的学习成本,在一些简单的场景LiveData已经完全能够满足我们的需要。
而且官方也说过并不会废弃LiveData,原因是:
- 用 Java 写 Android 的人还需要它,因为Flow是协程的东西,所以如果你是用 Java 的,是没有办法使用Flow的,所以LiveData还是有意义的。
- LiveData 的使用比较简单,而且功能上对于简单场景也是足够的,而 RxJava 和 Flow 这种东西学起来就没 LiveData 那么直观。
Flow
Flow是Google官方提供的一个类似于RxJava的响应式编程模型。它是基于Kotlin协程的。 它相对于Rxjava具有以下特点:
- 具有更友好的API,学习成本较低
- 跟Kotlin协程、LiveData结合更紧密,Flow能够转换成LiveData,在ViewModel中直接使用
- 结合协程的作用域,当协程被取消时,Flow也会被取消,避免内存泄漏
我们知道Flow的特点之一就是冷流。那么什么是冷流呢?
-
冷流:当数据被订阅的时候,发布者才开始执行发射数据流的代码。并且当有多个订阅者的时候,每一个订阅者何发布者都是一对一的关系,每个订阅者都会收到发布者完整的数据。
-
热流:无论有没有订阅者订阅,事件始终都会发生。当热流有多个订阅者时,发布者跟订阅者是一对多的关系,热流可以与多个订阅者共享信息。
StateFlow
因为Flow是冷流,这与LiveData的特点完全不一样,因此Flow提供了StateFlow来实现热流。
StateFlow 是 SharedFlow 的一个比较特殊的变种,而 SharedFlow 又是 Kotlin 数据流当中比较特殊的一种类型。StateFlow 与 LiveData 是最接近的,因为:
- 它始终是有值的。
- 它的值是唯一的。
- 它允许被多个观察者共用 (因此是共享的数据流)。
- 它永远只会把最新的值重现给订阅者,这与活跃观察者的数量是无关的。
官方推荐当暴露 UI 的状态给视图时,应该使用 StateFlow。这是一种安全和高效的观察者,专门用于容纳 UI 状态。
StateFlow使用
StateFlow替换掉LiveData是简单的。我们来看看StateFlow的构造函数:
/**
* Creates a [MutableStateFlow] with the given initial [value].
*/
@Suppress("FunctionName")
public fun <T> MutableStateFlow(value: T): MutableStateFlow<T> = StateFlowImpl(value ?: NULL)
我们在ViewModel
上游中不断的发送值,View
层通过collect
函数去获取到上游发送的数据。
StateFlow
只有在值发生改变时才会返回,如果发生更新但值没有变化时,StateFlow
不会回调collect
函数,但LiveData
会进行回调。
stateIn
StateIn 能够将普通的流转换为StateFlow,但转换之后还需要一些配置工作.
- scope 共享开始时所在的协程作用域范围
- started 控制共享的开始和结束的策略
Lazily
: 当首个订阅者出现时开始,在scope
指定的作用域被结束时终止。Eagerly
: 立即开始,而在scope
指定的作用域被结束时终止。WhileSubscribed
能够指定当前不有订阅者后,多少时间取消上游数据和能够指定多少时间后,缓存中的数据被丢失,回复称initialValue的值。
- initialValue 初始值
WhileSubscribed
WhileSubscribed 策略会在没有收集器的情况下取消上游数据流。通过 stateIn 运算符创建的 StateFlow 会把数据暴露给视图 (View),同时也会观察来自其他层级或者是上游应用的数据流。让这些流持续活跃可能会引起不必要的资源浪费,例如一直通过从数据库连接、硬件传感器中读取数据等等。当您的应用转而在后台运行时,您应当保持克制并中止这些协程。
@Suppress("FunctionName")
public fun WhileSubscribed(
stopTimeoutMillis: Long = 0,
replayExpirationMillis: Long = Long.MAX_VALUE
): SharingStarted =
StartedWhileSubscribed(stopTimeoutMillis, replayExpirationMillis)
WhileSubscribed
WhileSubscribed
支持传入stopTimeoutMillis
和replayExpirationMillis
参数。
其中stopTimeoutMillis
支持设置超时停止的效果,单位为ms。当最后一个订阅者不再订阅上游时,StateFlow
会停止上游数据的发送。
这样就可以提供APP的性能,当没有订阅者时或者应用被切到后台后会等待stopTimeoutMillis
设置的时间后上游会停止发送数据,并且会缓存停止前的缓存数据。
replayExpirationMillis
如果当上游如果停止发送太久,这时候StateFlow中缓存的数据是比较陈旧的数据,当这时候有订阅者时,我们不希望给订阅者陈旧的数据。我们可以设置replayExpirationMillis
参数,当停止共享携程超过设置的replayExpirationMillis
时间后,StateFlow中会将缓存重置为默认值。
在视图中观察数据
ViewModel中的StateFlow需要结合生命周期知道他们已经不在需要感知到何时不再需要被监听。我们在View
视图层提供了若干个协程构建器。
- Activity.lifecycleScope.launch : 启动协程,并且在本 Activity 销毁时结束协程。
- Fragment.lifecycleScope.launch : 启动协程,并且在本 Fragment 销毁时结束协程。
- Fragment.viewLifecycleOwner.lifecycleScope.launch : 启动协程,并且在本 Fragment 中的视图生命周期结束时取消协程。
- launchWhenX :启动协程,它会在 lifecycleOwner 进入 X 状态之前一直等待,又在离开 X 状态时挂起协程。
通过上面官方的这个图,我们可以看出当APP进入后台时,如果APP还在后台收集数据更新可能引发应用崩溃和资源的浪费。
repeatOnLifecycle
因此google官方提供了新的API接口repeatOnLifecycle
能够在某个特定的状态满足时启动协程,并且在生命周期所有者退出该状态时停止协程。
当视图处于 STARTED 状态时会开始收集流,并且在 RESUMED 状态时保持收集,最终在视图进入 STOPPED 状态时结束收集过程。
使用repeatOnLifecycle
和StateFlow能够帮助我们应用根据应用生命周期优化性能和设备资源。
通过repeatOnLifecycle
和StateFlow
能够帮助我们更好管理数据流。最后以官方的一句话结束本文。
当然,如果您并不需要使用到 Kotlin 数据流的强大功能,就用 LiveData 好了 :)