一文带你全面了解Flow

目录

 

一、Flow介绍

二、简单使用

三、原理

四、两种热流 SateFlow & SharedFlow

五、Flow与LiveData

六、Flow与Channel

七、总结


一、Flow介绍

  • 引入Flow的目的:Flow解决了什么问题。
  1. LiveData不支持线程切换,所有数据转换都将在主线程上完成,有时需要频繁更改线程,面对复杂数据流时处理起来比较麻烦。
  2. 而RxJava又有些过于麻烦了,有许多让人傻傻分不清的操作符,入门门槛较高,同时需要自己处理生命周期,在生命周期结束时取消订阅。
  • 可以看出,Flow是介于LiveData与RxJava之间的一个解决方案,它有以下特点:
  1. Flow 支持线程切换(flowOn改变数据发射的线程,消费端的数据直接有观察者的协程自己控制)、背压。
  2. Flow 入门的门槛很低,没有那么多傻傻分不清楚的操作符。
  3. 简单的数据转换与操作符,如 map 等等。
  4. 支持冷数据流,不消费则不生产数据,这一点与LiveData不同:LiveData的发送端并不依赖于接收端。
  5. 属于kotlin协程的一部分,可以很好的与协程基础设施结合。
  6. 与RxJava类似,支持通过catch捕获异常,通过onCompletion 回调完成
  7. Flow没有提供取消方法,可以通过取消Flow所在协程的方式来取消

LiveData 比RxJava更纯粹,就是单一的消费生产模型(职能偏向于数据的产生、响应)

Flow更类似RxJava,是对流式数据进行操作整合(职能偏向于对数据进行操作、线程切换等)(这个是目前自己的理解,有待后续改进)

Flow其实是协程内部的一部分构成

Flow相似于RxJava

同样分为3个模块

  1. 上游
  2. 操作符
  3. 下游

collect和flow的回调函数本身属于suspend函数可以开启协程作用域

Flow有以下特征:

  • ​flow{ ... }​ 构建一个Flow类型
  • ​flow { ... }​内可以使用suspend函数.
  • foo()​不需要是suspend函数
  • emit方法用来发射数据
  • collect方法用来遍历结果

Flow的应用场景:

  • Flow 支持线程切换、背压
  • Flow 入门的门槛很低,没有那么多傻傻分不清楚的操作符
  • 简单的数据转换与操作符,如 map 等等
  • Flow 是对 Kotlin 协程的扩展,让我们可以像运行同步代码一样运行异步代码,使得代码更加简洁,提高了代码的可读性
  • 易于做单元测试

1.配合LiveData

LiveData 是一个生命周期感知组件,最好在 View 和 ViewModel 层中使用它,如果在 Repositories 或者 DataSource 中使用会有几个问题

  • 它不支持线程切换,其次不支持背压,也就是在一段时间内发送数据的速度 > 接受数据的速度,LiveData 无法正确的处理这些请求
  • 使用 LiveData 的最大问题是所有数据转换都将在主线程上完成

2.RxJava使用门槛太高,替代RxJava

二、简单使用

lifecycleScope.launch {
    flow {
        for (i in 1..10) {
            emit(i)
        }
    }.flowOn(Dispatchers.Main)
        .catch {
            //异常处理
        }
        .onCompletion {
            //完成回调
        }
        .collect { num ->
            // 具体的消费处理
            // 只有collect时才会生产数据
            // ...
        }
}

三、原理

Flow 就是 Kotlin 协程与响应式编程模型结合的产物

总结

  • Flow是冷流:flow{ //TODO1} .collect { //TODO2}TODO1的代码会在 //TODO2 中被调到,才去执行
  • Flow怎么切线程:
    • Flow切换线程的方式与协程切换线程是类似的 都是
  1. 通过启动一个子协程,然后通过CoroutineContext中的Dispatchers切换线程
  2. 不同的地方在于Flow切换过程中利用了Channel来传递数据

Flow是冷流

public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> 
= SafeFlow(block)

// Named anonymous object
private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : AbstractFlow<T>() {
    override suspend fun collectSafely(collector: FlowCollector<T>) {
        collector.block()
    }
}

可以看出,flow{}中做的事也很简单,主要就是创建了一个继承自AbstractFlow的SafeFlow

再来看下AbstractFlow中的内容

public abstract class AbstractFlow<T> : Flow<T> {

    @InternalCoroutinesApi
    public final override suspend fun collect(collector: FlowCollector<T>) {
    // 1. collector 做一层包装
        val safeCollector = SafeCollector(collector, coroutineContext)
        try {
     // 2. 处理数据接收者
            collectSafely(safeCollector)
        } finally {
           // 3. 释放协程相关的参数
            safeCollector.releaseIntercepted()
        }
    }

    // collectSafely 方法应当遵循以下的约束
    // 1. 不应当在collectSafely方法里面切换线程,比如 withContext(Dispatchers.IO)
    // 2. collectSafely 默认不是线程安全的
    public abstract suspend fun collectSafely(collector: FlowCollector<T>)
}

private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : AbstractFlow<T>() {
    override suspend fun collectSafely(collector: FlowCollector<T>) {
        collector.block()
    }
}

发现主要做了三件事:

对数据接收方FlowCollector 做了一层包装,也就是这个SafeCollector

调用它里面的抽象方法AbstractFlow#collectSafely 方法。

释放协程的一些信息。

结合以下之前看的SafeFlow,它实现了AbstractFlow#collectSafely方法,调用了collector.block(),也就是运行了flow{}块中的代码。

现在就很清晰了,为什么Flow是冷流? 因为它会在每一次collect的时候才会去触发发送数据的动作

lifecycleScope.launch {
    flow {
        for (i in 1..10) {
            emit(i)
        }
    }.flowOn(Dispatchers.Main)
.collect { num ->
            // 具体的消费处理
            // 只有collect时才会生产数据
       
        }

也就是flow{ //TODO1} .collect { //TODO2}

TODO1的代码会在 //TODO2 中被调到,才去执行

切线程

Flow切换线程的方式与协程切换线程是类似的 都是

  1. 通过启动一个子协程,然后通过CoroutineContext中的Dispatchers切换线程
  2. 不同的地方在于Flow切换过程中利用了Channel来传递数据

四、两种热流 SateFlow & SharedFlow

     

共通点:

  • 都是热流
  • 都有两个版本:SharedFlow 与 MutableSharedFlow

不同点:

  • SateFlow只保留最新值,SharedFlow都保留
    • SateFlow 新的订阅者只会获得最新的和之后的数据。
    • SharedFlow 根据配置可以保留历史数据,新的订阅者可以获取之前发射过的一系列数据。
  • 初始值
    • SateFlow有初始值,永远有值
    • SharedFlow无初始值
  • 粘性事件
    • 使用 SharedFlow 不会有「粘性事件」的问题
    • StateFlow会有粘性事件
  • 定位 StateFlow处理状态,SharedFlow处理事件
    • StateFlow 与 LiveData 定位相似,处理的是状态(只关心最新状态,不管之前变动了多少)
    • SharedFlow处理事件,每一次发射的行为都能记录下来

Flow中使用SharedFlow处理粘性事件的问题

五、Flow与LiveData

默认是冷流,也支持热流

有一种特殊的 Flow,如 StateFlow/SharedFlow ,它们是热流。这些流可以在没有活跃消费者的情况下存活,换句话说,数据在流之外生成然后传递到流。

StateFlow 与 LiveData 十分像,或者说它们的定位类似。

StateFlow 与 LiveData 有一些相同点:

  • 提供「可读可写」和「仅可读」两个版本(StateFlow,MutableStateFlow)
  • 它的值是唯一的
  • 它允许被多个观察者共用 (因此是共享的数据流)
  • 它永远只会把最新的值重现给订阅者,这与活跃观察者的数量是无关的
  • 支持 DataBinding

它们也有些不同点:

  • 必须配置初始值
  • value 空安全
  • 防抖:StateFlow 默认是防抖的,在更新数据时,会判断当前值与新值是否相同,如果相同则不更新数据。
  • StateFlow 与 LiveData 定位相似,前者必须配置初始值,value 空安全并且默认防抖
  • StateFlow 与 SharedFlow 的使用场景不同,前者适用于「状态」,后者适用于「事件」

六、Flow与Channel

Flow:冷数据流,不消费则不生产,这一点与Channel正相反:Channel的发送端并不依赖于接收端。

七、总结

FLow 与channel区别:

f操作符多,冷热流可以相互转化

F是后出的,旨在解决channel不太方便的非阻塞式需求,一般只有在f太智能导致某个复杂场景不好实现时再考虑channel

LiveData 算是热的(它不受有无订阅影响)

Flow默认是冷流,它会在每一次collect的时候才会去触发发送数据的动作,也可以是热流

Rx有冷流也有 热流(从需要有背压就可以看出)

场景的选择:

1.如果简单的生产消费模型,直接使用LiveData

2.如果需要比较复杂的变化,使用Flow,LiveData支持转成Flow

3.F是后出的,旨在解决channel不太方便的非阻塞式需求,一般只有在f太智能导致某个复杂场景不好实现时再考虑channel

4.Rx没想到需要使用的场景,除非需要使用复杂的转换且不能使用kotlin

5.想要传递事件:

  1. 同一个事件会被多次消费(Fragment与Activity传递事件):
    1. SharedFlow支持被多个订阅者订阅,导致同一个事件会被多次消费
      1. 类似BroadcastChannel,支持多个订阅者,一次发送多处消费
      2. 配置灵活,如默认配置 capacity = 0, replay = 0,意味着新订阅者不会收到类似LiveData的回放。无订阅者时会直接丢弃,正符合上述时效性事件的特点
  2. 一次性事件:Channel可以,但是使用比较麻烦,可以使用Flow,也可以使用对LiveData进行封装成SingleLiveDate
    1. Flow receiveAsFlow 转成ChannelFlow
    2. SingleLiveDate:判断只有setValue的时候响应事件,做到事件的一次性

LiveData特点:

  • 观察者的回调永远发生在主线程
  • 仅持有单个且最新的数据:改成bus使用,有可能会漏掉一些中间的消息
  • 自动取消订阅
  • 提供「可读可写」和「仅可读」两个版本收缩权限
  • 配合 DataBinding 实现「双向绑定」

缺点(或者说特点)

  • value 是 nullable 的
  • 在 fragment 订阅时需要传入正确的 lifecycleOwner
  • 当 LiveData 持有的数据是「事件」时,可能会遇到「粘性事件」:有可能已经被消费过的事件,在lifecycleOwner 重建时,新的观察者会再次调用 Livedata#observe(),再次触发消费这个事件,使用不当会有bug
  • LiveData 是不防抖的:setValue()/postValue() 传入相同的值多次调用,观察者的 onChanged() 会被多次调用。
  • LiveData 的 transformation 工作在主线程

LiveData 很轻,功能十分克制,克制到需要配合 ViewModel 使用才能显示其价值,才能数据与UI彻底解耦

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

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LVS(Linux Virtual Server)是一种基于 Linux 系统的负载均衡集群技术,它主要用于将网络流量分发到多个服务器上,以提高系统的可靠性、可扩展性和性能。 LVS 集群一般包括四个组件:调度器(LVS 调度器)、前端服务器(负载均衡器)、后端服务器(真实服务器)和存储服务器(用于共享数据)。首先,调度器接收来自客户端的请求,然后根据配置的调度算法(如轮询、加权轮询、最小连接数等)将请求分发到多个前端服务器。前端服务器接收到请求后,通过相应的负载均衡算法将请求转发到后端的真实服务器上进行处理。在整个过程中,存储服务器用于存放共享的数据,以确保所有的真实服务器都能获取到相同的数据,并提供一致的服务。 LVS 集群的优点是能够提高网站的稳定性和可靠性,当某一台服务器出现故障时,调度器会自动将请求分发到其他可用的服务器上,从而保证服务的连续性。同时,LVS 集群还能够通过增加前端服务器和后端服务器的数量来提高系统的性能和吞吐量,以满足不断增长的用户需求。 在实际应用中,LVS 集群需要合理配置,包括选择合适的调度算法、调整每台服务器的权重、选择适当的硬件设备等。此外,还需要及时监控集群的运行状态,及时发现和解决故障,以确保整个系统的正常运行。 总的来说,LVS 负载均衡集群是一种强大而高效的集群技术,能够帮助企业提高系统的可靠性和性能,是现代互联网应用中不可或缺的重要组成部分。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值