为什么引入 Flow?
Flow
是介于 LiveData
与 RxJava
之间的一个解决方案,它有以下特点:
-
Flow
支持线程切换、背压; -
简单的数据转换与操作符;
-
冷数据流,不消费则不生产数据,这一点与
LiveData
不同,LiveData
的发送端并不依赖于接收端; -
属于
kotlin
协程的一部分,可以很好的与协程基础设施结合。
Flow
是冷流,什么是冷流?
只有订阅者 订阅 时,才开始 发射数据流。并且 冷流 和 订阅者 只能是一对一的关系,当有多个不同的订阅者时,消息是重新完整发送的。也就是说对冷流而言,有多个订阅者的时候,他们各自的事件是独立的。
热流:无论有没有订阅者订阅,事件始终都会发生。当热流有多个订阅者时,热流与订阅者们的关系是一对多的关系,可以与多个订阅者共享信息。
SharedFlow
即共享的 Flow
,可以实现一对多关系,SharedFlow
是一种热流。
public fun <T> MutableSharedFlow(
replay: Int = 0,
extraBufferCapacity: Int = 0,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T>
参数解析:
replay
表示当新的订阅 者Collect 时,发送多少个已经发送过的数据给它。
extraBufferCapacity
表示减去 replay
,MutableSharedFlow
还能缓存多少数据,默认为0
onBufferOverflow
表示缓存策略,即缓冲区满了之后 Flow 如何处理,默认为挂起。
** 将冷流转化为 SharedFlow
?**
普通 flow
可使用 shareIn
扩展方法,转化成 SharedFlow
public fun <T> Flow<T>.shareIn(
scope: CoroutineScope,
started: SharingStarted,
replay: Int = 0
): SharedFlow<T>
val sharedFlow by lazy {
flow<Int> {
...
}.shareIn(viewModelScope, WhileSubscribed(5000), 0) // 等待5秒后仍然没有订阅者存在就终止协程
}
shareIn
参数解析:
-
scope
: 所在的协程作用域范围 -
started
控制共享的开始和结束的策略 -
replay
状态流的重播个数
started
接受以下的三个值:
1,Lazily
:当首个订阅者出现时开始,在 scope 指定的作用域结束时终止;
2,Eagerly
: 立即开始,在 scope 指定的作用域结束时终止;
3,WhileSubscribed
TODO
对于那些只执行一次的操作,您可以使用 Lazily
或者 Eagerly
。然而,如果您需要观察其他的流,就应该使用 WhileSubscribed
来实现细微但又重要的优化工作。
WhileSubscribed
策略会在没有收集器的情况下取消上游数据流,通过 shareIn
运算符创建的 SharedFlow
会把数据暴露给视图 (View),同时也会观察来自其他层级或者是上游应用的数据流。让这些流持续活跃可能会引起不必要的资源浪费,例如一直通过从数据库连接、硬件传感器中读取数据等等。当您的应用转而在后台运行时,您应当保持克制并中止这些协程。
public fun WhileSubscribed(
stopTimeoutMillis: Long = 0,
replayExpirationMillis: Long = Long.MAX_VALUE
)
stopTimeoutMillis
控制一个以毫秒为单位的延迟值,指的是最后一个订阅者结束订阅与停止上游流的时间差。默认值是 0 (立即停止).这个值非常有用,因为您可能并不想因为视图有几秒钟不再监听就结束上游流。这种情况非常常见——比如当用户旋转设备时,原来的视图会先被销毁,然后数秒钟内重建。
replayExpirationMillis
表示数据重播的过时时间,如果用户离开应用太久,此时您不想让用户看到陈旧的数据,你可以用到这个参数。
StateFlow
与 LiveData
是最接近的,StateFlow
继承于 SharedFlow
,是SharedFlow
的一个特殊变种。
与 SharedFlow
类似,我们也可以用 stateIn
将普通流转化成 StateFlow
。
val result: StateFlow<Result<UiState>> = someFlow
.stateIn(
scope = viewModelScope,
started = WhileSubscribed(5000),
initialValue = Result.Loading
)
上面的那段代码它可以做到以下几点:
1,当您的应用转至后台运行(状态不活跃),5 秒钟后所有的数据更新会停止,这样可以节省电量;
2,最新的数据仍然会被缓存,所以应用处于活跃状态时,订阅将被重启,新数据会填充进来,当数据可用时更新视图;
3,在屏幕旋转时,因为重新订阅的时间在5s内,因此上游流不会中止。
官方推荐 repeatOnLifecycle
来构建协程,在 lifecycleOwner
活跃时启动协程,在 lifecycleOwner
生命周期结束时停止协程。
当这个
Fragment
处于STARTED
状态时会开始收集流,并且在RESUMED
状态时保持收集,最终在Fragment
进入STOPPED
状态时结束收集过程。结合使用repeatOnLifecycle
API 和WhileSubscribed
,可以帮助您的应用妥善利用设备资源的同时,发挥最佳性能。
Note: These APIs are available in the androidx.lifecycle:lifecycle-runtime-ktx:2.4.0
library or later.
# 某 Fragment
onCreateView(...) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(STARTED) {
myViewModel.myUiState.collect { ... }
}
}
}
Use the Lifecycle.repeatOnLifecycle or Flow.flowWithLifecycle APIs to safely collect flows from the UI layer in Android.
class LocationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Listen to one flow in a lifecycle-aware manner using flowWithLifecycle
lifecycleScope.launch {
locationProvider.locationFlow()
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.collect {
// New location! Update the map
}
}
// Listen to multiple flows
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// As collect is a suspend function, if you want to collect
// multiple flows in parallel, you need to do so in
// different coroutines
launch {
flow1.collect { /* Do something */ }
}
launch {
flow2.collect { /* Do something */ }
}
}
}
}
}
总结:
简单往往意味着不够强大,而强大又常常意味着复杂,两者往往不能兼得,软件开发过程中常常面临这种取舍。LiveData
的简单并不是它的缺点,而是它的特点。StateFlow
与 SharedFlow
更加强大,但是学习成本也显著的更高。我们应该根据自己的需求合理选择组件的使用。
如果你的数据流比较简单,不需要进行线程切换与复杂的数据变换,LiveData
对你来说相信已经足够了。
如果你的数据流比较复杂,需要切换线程等操作,不需要发送重复值,需要获取 myFlow.value,StateFlow
对你来说是个好的选择。
如果你的数据流比较复杂,同时不需要获取 myFlow.value,需要配置新用户订阅重播无素的个数,或者需要发送重复的值,可以考虑使用 SharedFlow
。
网友评论