美文网首页JetPack
Flow 了解一下

Flow 了解一下

作者: Drew_MyINTYRE | 来源:发表于2022-04-07 19:55 被阅读0次

    为什么引入 Flow?

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

    • 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 表示减去 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 参数解析:

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

    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

    相关文章

      网友评论

        本文标题:Flow 了解一下

        本文链接:https://www.haomeiwen.com/subject/waessrtx.html