JetPack

Flow 了解一下

2022-04-07  本文已影响0人  Drew_MyINTYRE

为什么引入 Flow?

Flow 是介于 LiveDataRxJava 之间的一个解决方案,它有以下特点:

Flow 是冷流,什么是冷流?

只有订阅者 订阅 时,才开始 发射数据流。并且 冷流 和 订阅者 只能是一对一的关系,当有多个不同的订阅者时,消息是重新完整发送的。也就是说对冷流而言,有多个订阅者的时候,他们各自的事件是独立的。

热流:无论有没有订阅者订阅,事件始终都会发生。当热流有多个订阅者时,热流与订阅者们的关系是一对多的关系,可以与多个订阅者共享信息。

SharedFlow 即共享的 Flow,可以实现一对多关系,SharedFlow 是一种热流。

public fun <T> MutableSharedFlow(
    replay: Int = 0,
    extraBufferCapacity: Int = 0,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND
): MutableSharedFlow<T>

参数解析:

replay 表示当新的订阅 者Collect 时,发送多少个已经发送过的数据给它。

extraBufferCapacity 表示减去 replayMutableSharedFlow 还能缓存多少数据,默认为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 参数解析:

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 表示数据重播的过时时间,如果用户离开应用太久,此时您不想让用户看到陈旧的数据,你可以用到这个参数。

StateFlowLiveData 是最接近的,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 的简单并不是它的缺点,而是它的特点。StateFlowSharedFlow 更加强大,但是学习成本也显著的更高。我们应该根据自己的需求合理选择组件的使用。

如果你的数据流比较简单,不需要进行线程切换与复杂的数据变换,LiveData 对你来说相信已经足够了。

如果你的数据流比较复杂,需要切换线程等操作,不需要发送重复值,需要获取 myFlow.value,StateFlow 对你来说是个好的选择。

如果你的数据流比较复杂,同时不需要获取 myFlow.value,需要配置新用户订阅重播无素的个数,或者需要发送重复的值,可以考虑使用 SharedFlow

上一篇下一篇

猜你喜欢

热点阅读