1. LiveData
有什么不足?
1.1 为什么引入LiveData
?
要了解LiveData
的不足,我们先了解下LiveData
为什么被引入
LiveData
的历史要追溯到 2017 年。彼时,观察者模式有效简化了开发,但诸如RxJava
一类的库对新手而言有些太过复杂。为此,架构组件团队打造了LiveData
: 一个专用于Android
的具备自主生命周期感知能力的可观察的数据存储器类。LiveData
被有意简化设计,这使得开发者很容易上手;而对于较为复杂的交互数据流场景,建议您使用RxJava
,这样两者结合的优势就发挥出来了
可以看出,LiveData
就是一个简单易用的,具备感知生命周期能力的观察者模式
它使用起来非常简单,这是它的优点,也是它的不足,因为它面对比较复杂的交互数据流场景时,处理起来比较麻烦
1.2 LiveData
的不足
我们上文说过LiveData
结构简单,但是不够强大,它有以下不足
1.LiveData
只能在主线程更新数据
2.LiveData
的操作符不够强大,在处理复杂数据流时有些捉襟见肘
关于LiveData
只能在主线程更新数据,有的同学可能要问,不是有postValue
吗?其实postValue
也是需要切换到到主线程的,如下图所示:
这意味着当我们想要更新LiveData
对象时,我们会经常更改线程(工作线程→主线程),如果在修改LiveData
后又要切换回到工作线程那就更麻烦了,同时postValue
可能会有丢数据的问题。
2. Flow
介绍
Flow
就是 Kotlin
协程与响应式编程模型结合的产物,你会发现它与 RxJava
非常像,二者之间也有相互转换的 API
,使用起来非常方便。
2.1 为什么引入Flow
为什么引入Flow
,我们可以从Flow
解决了什么问题的角度切入
**1. LiveData
不支持线程切换,所有数据转换都将在主线程上完成,有时需要频繁更改线程,面对复杂数据流时处理起来比较麻烦
- 而
RxJava
又有些过于麻烦了,有许多让人傻傻分不清的操作符,入门门槛较高,同时需要自己处理生命周期,在生命周期结束时取消订阅**
可以看出,Flow
是介于LiveData
与RxJava
之间的一个解决方案,它有以下特点
-
Flow
支持线程切换、背压 -
Flow
入门的门槛很低,没有那么多傻傻分不清楚的操作符 - 简单的数据转换与操作符,如
map
等等 - 冷数据流,不消费则不生产数据,这一点与
LiveData
不同:LiveData
的发送端并不依赖于接收端。 - 属于
kotlin
协程的一部分,可以很好的与协程基础设施结合
关于Flow
的使用,比较简单,有兴趣的同学可参阅文档:Flow文档
3. SharedFlow
介绍
我们上面介绍过,Flow 是冷流
,什么是冷流?
冷流 : 只有
订阅者
订阅时,才开始执行发射数据流的代码。并且冷流
和订阅者
只能是一对一的关系,
当有多个不同的订阅者
时,消息是重新完整发送的
。
也就是说对冷流
而言,有多个订阅者
的时候,他们各自的事件是独立的。热流:无论有没有
订阅者
订阅,事件始终都会发生。当热流
有多个订阅者
时,热流
与订阅者
们的关系是一对多的关系,可以与多个订阅者共享信息。
3.1 为什么引入SharedFlow
上面其实已经说得很清楚了,冷流
和订阅者
只能是一对一的关系,当我们要实现一个流,多个订阅者
的需求时(这在开发中是很常见的),就需要热流
了
从命名上也很容易理解,SharedFlow
即共享的Flow
,可以实现一对多关系,SharedFlow
是一种热流
3.2 SharedFlow
的使用
我们来看看SharedFlow
的构造函数
public fun <T> MutableSharedFlow(
replay: Int = 0,
extraBufferCapacity: Int = 0,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T>
其主要有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
共享开始时所在的协程作用域范围
@paramstarted
控制共享的开始和结束的策略
@paramreplay
状态流的重播个数
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
不同
与SharedFlow
类似,我们也可以用stateIn
将普通流转化成StateFlow
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
其实是挺像的,让人有些傻傻分不清,有时候也挺难选择该用哪个的
我们总结一下,它们的区别如下:
1. SharedFlow
配置更为灵活,支持配置replay
,缓冲区大小等,StateFlow
是SharedFlow
的特化版本,replay
固定为1,缓冲区大小默认为0
2. StateFlow
与LiveData
类似,支持通过myFlow.value
获取当前状态,如果有这个需求,必须使用StateFlow
3. SharedFlow
支持发出和收集重复值,而StateFlow
当value
重复时,不会回调collect
4. 对于新的订阅者,StateFlow
只会重播当前最新值,SharedFlow
可配置重播元素个数(默认为0,即不重播)
可以看出,StateFlow
为我们做了一些默认的配置,在SharedFlow
上添加了一些默认约束,这些配置可能并不符合我们的要求
1. 它忽略重复的值,并且是不可配置的。这会带来一些问题,比如当往List
中添加元素并更新时,StateFlow
会认为是重复的值并忽略
2. 它需要一个初始值,并且在开始订阅时会回调初始值,这有可能不是我们想要的
3. 它默认是粘性的,新用户订阅会获得当前的最新值,而且是不可配置的,而SharedFlow
可以修改replay
StateFlow
施加在SharedFlow
上的约束可能不是最适合您,如果不需要访问myFlow.value
,并且享受SharedFlow
的灵活性,可以选择考虑使用SharedFlow
总结
简单往往意味着不够强大,而强大又常常意味着复杂,两者往往不能兼得,软件开发过程中常常面临这种取舍。
LiveData
的简单并不是它的缺点,而是它的特点。StateFlow
与SharedFlow
更加强大,但是学习成本也显著的更高.
我们应该根据自己的需求合理选择组件的使用
1. 如果你的数据流比较简单,不需要进行线程切换与复杂的数据变换,LiveData
对你来说相信已经足够了
2. 如果你的数据流比较复杂,需要切换线程等操作,不需要发送重复值,需要获取myFlow.value
,StateFlow
对你来说是个好的选择
3. 如果你的数据流比较复杂,同时不需要获取myFlow.value
,需要配置新用户订阅重播无素的个数,或者需要发送重复的值,可以考虑使用SharedFlow