RxJava VS kotlin flow

1.基础概念介绍

1.1 观察者模式

观察者模式,其实对于Android开发者而言,并不陌生,button的setOnClickListener,就是一个典型的观察者模式。控件button是被观察者,它产生一个事件(点击),观察者OnClickListener接收到,做出相应的处理,而setOnClickListener就是订阅者,它将两者连接起来

以上面为例,观察者模式需要具备的三个角色:被观察者,观察者,事件,一个动作:订阅

角色作用类别
被观察者(Observable)产生事件控件
观察者(Observer)接收事件,并给出响应动作OnClickListener
订阅(Subscribe)连接 被观察者 & 观察者setOnClickListener
事件(Event)被观察者 & 观察者 沟通的载体控件被点击

1.2 函数响应式编程

在第一次接触这些词的时候,我满脑子的问号,看官方文档也是云山雾里的。后来我翻看了各种文档,说一下我对这些词的理解:

  • 响应式编程(Reactive Programming RP):万物皆可视为数据流,比如:用户的操作,网络数据,某个代码状态变更

  • 函数式编程(Functional Programming FP):将逻辑抽象为旧数据如何映射为新数据。(比如rx或者kotlin里的各种操作符)

  • 函数响应式编程(Reactive Functional Programming RFP): 将数据流的各种操作(创建,结合,过滤等)抽象为函数(操作符),使得数据流可以自由的组合这些函数实现各种数据流的映射

推荐文章:

1.3 Rxjava

ReactiveX是Reactive Extensions的缩写,一般简写为Rx,Rx是一个编程模型,目标是提供一致的编程接口,帮助开发者更方便的处理异步数据流。Rx的大部分语言库由ReactiveX这个组织负责维护,比较流行的有RxJava/RxJS/Rx.NET,社区网站是 reactivex.io

ReactiveX.io给的定义是,Rx是基于观察者模式的实现了异步编程接口,ReactiveX结合了观察者模式、迭代器模式和函数式编程的精华。Rxjava则是Reactive Extensions的java实现

简单说,Rxjava是函数响应式编程思想的一种体现,基于观察者模式实现的一种异步编程接口

原理是: 被观察者 (Observable) 通过 订阅(Subscribe) 按顺序发送数据 给观察者 (Observer), 观察者(Observer) 按顺序接收事件 & 作出对应的响应动作。

image

特点:

  • 操作符丰富

  • 支持背压

  • 支持线程切换操作

  • 学习成本和上手难度较高

1.4 Flow

Flow是kotlin提供的一个工具( kotlinx 包下的组件),它也是函数响应式编程(RFP)思想的一种体现,也是使用的观察者模式,但拥有尽可能简单的设计, 对 Kotlin 以及协程友好且遵从结构化并发。

特点:

  • 支持线程(协程)

  • 跟协程绑定的比较多

  • 支持背压

  • 操作符和rx整体来说差不多

  • 学习成本低(前提是得会协程)

1.5 LiveData

LiveData 是 androidx 包下的组件,是 Android 生态中一个的简单的生命周期感知型容器。与常规的可观察类不同,LiveData 具有生命周期感知能力,它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。
这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

特点:

  • 可以感知生命周期

  • 只能在主线程更新数据

  • 不支持背压

  • 不支持防抖

  • 没有操作符

关于更多LiveData的使用和源码,可以看我之前的文章: JetPack框架组件2——liveData的使用和源码分析

2.Hello Word

同button的setOnClickListener一样,RxJava和kotlin flow 都是基于观察者模型实现的编程接口。所以和button的setOnClickListener使用类似,即:

  • 实现被观察者(button)

  • 实现观察者(OnClickListener)

  • 注册(setOnClickListener)

  • 被观察者发出事件,观察者接受到(点击)

2.1 Hello Rxjava

实现被观察者observable

// 1. 创建被观察者 Observable 对象
val observable = Observable.create( object : ObservableOnSubscribe<String> {
    // create() 是 RxJava 最基本的创造事件序列的方法

    //2.在复写的subscribe()里定义需要发送的事件
    override fun subscribe(emitter: ObservableEmitter<String>) {
        // 通过 ObservableEmitter类对象产生事件并通知观察者

        // ObservableEmitter类介绍
        // a. 定义:事件发射器
        // b. 作用:定义需要发送的事件 & 向观察者发送事件
        emitter.onNext("hello rxjava")//发送事件
        emitter.onComplete()//发送完成事件
    }
})

实现观察者observer

// 1. 创建观察者 (Observer )对象
val observer =  object : Observer<String> {
    // 2. 创建对象时通过对应复写对应事件方法 从而 响应对应事件

    override fun onSubscribe(d: Disposable) {
        Log.d(TAG, "开始采用subscribe连接")
    }

    override fun onNext(t: String) {
        Log.d(TAG, "对Next事件作出响应: $t")
    }

    override fun onError(e: Throwable) {
        Log.d(TAG, "对Error事件作出响应")
    }

    override fun onComplete() {
        Log.d(TAG, "对Complete事件作出响应")
    }
}

注册

observable.subscribe(observer)

执行结果

image

原理

这一块源码的实现,感兴趣可以看我之前的博客:Android之Rxjava2.X 9————Rxjava源码阅读1

其实Rxjava上面部分源码的实现很简单,一句话概括

使用Observable.create创建时,需要传入的ObservableOnSubscribe接口中的subscribe方法参数,这个参数是用来发射数据.它其实是Observer的包装类

源码核心逻辑

public final class ObservableCreate<T> extends Observable<T> {
    @Override
    protected void subscribeActual(Observer<? super T> observer) {
        CreateEmitter<T> parent = new CreateEmitter<T>(observer);
        observer.onSubscribe(parent);

        try {
            //source即为ObservableOnSubscribe
            source.subscribe(parent);
        } catch (Throwable ex) {
            Exceptions.throwIfFatal(ex);
            parent.onError(ex);
        }    
    }    
}

通过源码也可以看出来,当完成observable.subscribe(observer)时,observable才可以发送数据

更简洁的写法

Observable
    .just("hello rxjava") //构造简单的Observable方法
    .subscribe { Log.d(TAG, "$it") } //可以只实现onNext方法
    
    //日志:hello rxjava

2.2 Hello Flow

实现被观察者flow

val flow: Flow<String> = flow { // 流构建器
    emit("hello flow") // 发送下一个值
}

实现观察者flowCollector

val flowCollector: FlowCollector<String> = FlowCollector {
    Log.d(TAG, "$it")
}

注册

注意,flow是存在suspend标识,所以必须运行在协程中

GlobalScope.launch{
    flow.collect(flowCollector)
}

执行结果

image

原理

看了flow的原理,其实和rxjava类似。flow创建,传入的FlowCollector接口,其实也是flowCollector进行包装后的对象

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

    public final override suspend fun collect(collector: FlowCollector<T>) {
        val safeCollector = SafeCollector(collector, coroutineContext)
        try {
            collectSafely(safeCollector)
        } finally {
            safeCollector.releaseIntercepted()
        }
    }
 }

一般写法

flow {
    emit("hello flow") // 发送下一个值
}.collect{
    Log.d(TAG, "$it")
}
//日志: MainActivity: hello flow

是否可以类似Rxjava那也,也可以发射开始,发射结束,error等状态的回调。这个是可以的

  • onStart:数据流开始发射

  • onEach:数据流中的每一个数据发射时的回调

  • onCompletion:数据流结束发射

  • onEmpty:当数据流中没有发射任何数据时

  • catch: 发生异常

flow {
    emit(1)
    emit(2)
    emit(3)
    emit(4)
}.catch { e ->
    // 发生了异常。显示异常信息
    Log.d(TAG, "加载错误: $e")
}.onEmpty {
    // 空白数据
    Log.d(TAG,"什么数据都没有")
}.onStart {
    Log.d(TAG,"正在加载中")
}.onEach {
    Log.d(TAG,"开始处理 $it")
}.onCompletion { e->
    if(e == null){
        Log.d(TAG,"加载结束 $it")
    }else{
        Log.d(TAG,"加载失败 $e")
    }

}.collect {
    Log.d(TAG,"加载成功 $it")
    println(it)
}

3.进阶

3.1 线程切换

3.1.1 Rxjava的线程切换

Rxjava的线程操作功能符号有两个:

  • subscribeOn() 指定被观察者的线程,有一点需要注意就是如果多次调用此方法,只有第一次有效。

  • observerOn() 指定观察者的线程,每指定一次就会生效一次。

subscribeOn

Observable
    .create<Int> {
        Log.e(TAG, "threadName:" + Thread.currentThread().getName());
        it.onNext(1);
    }.subscribeOn(Schedulers.newThread())
    .subscribe { Log.d(TAG, "$it") }

image

observerOn()

Observable.fromIterable(listOf(0, 1, 2, 3, 4)) //根据list快速创建一组Observable
    .map { //转化操作符
        Log.d(TAG, "subscribeOn threadName: ${Thread.currentThread().name} it = $it"  )
        return@map it * 10
    }.observeOn(AndroidSchedulers.mainThread())
    .subscribeOn(Schedulers.newThread())
    .subscribe {
        Log.d(TAG, "observerOn threadName: ${Thread.currentThread().name}")
        Log.d(TAG, "onNext: $it")
    }

image

3.1.2 flow的线程切换

flow的线程操作符只有flowOn

flowOn介绍:

  • 操作符对上游范围有效, 范围是指两个flowOn之间, 如果只有一个flowOn,则上游全部有效

  • 最后一个flowOn后的操作所在线程与当前整个flow所在的线程池相同

(0..4).asFlow()
    .onStart { Log.d(TAG, "flow start threadName: ${Thread.currentThread().name}"  )}
    .flowOn(Dispatchers.IO)
    .map {
        // 运行在 dispatcher2
        Log.d(TAG, "map threadName: ${Thread.currentThread().name} it = $it"  )
        it * 10
    }
    .flowOn(Dispatchers.Main)
    .collect {
        // 运行的协程取决于整个 flow 在哪个协程调用
        Log.d(TAG, "collect ${Thread.currentThread().name} it = $it"  )
        println(it)
    }

image

3.2 背压

3.2.1 背压介绍

针对的场景:

Observable.create(ObservableOnSubscribe<Int> {
    var i = 0
    while (true) {
        i++
        it.onNext(i)
    }
})
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.newThread())
.subscribe {
    Thread.sleep(5000);
    Log.d(TAG, "onNext: $it")
}

在异步订阅的(比如网络请求),被观察者发生事件的速度太快,观察者来不及接受所有的事件,从而缓存区中的事件越积越多,最终导致缓存区溢出,事件丢失并OOM

image

定义

Backpressure,也称为Reactive Pull,就是下游需要多少(具体是通过下游的request请求指定需要多少),上游就发送多少。

作用

在异步场景中,被观察者发送事件速度远快于观察者的处理速度的情况下,一种告诉上游的被观察者降低发送速度的策略

背压策略的原理

  • 对于观察者:响应式拉取,即观察者根据自己的实际需求接受事件

  • 对于被观察者:反馈控制,即被观察者根据观察者的接受能力,从而控制发送事件的速度

  • 对于缓存区:对超出缓存区大小的事件进行丢弃,保留,报错。

3.2.2 Rxjava中背压的使用

在Rxjava中,如果需要使用背压,需要使用Observable(被观察者)的另一种实现:Flowable。

Flowable的特点:

  • 对应的观察者变为Subscribe

  • 所有操作符强制支持背压

  • 默认的缓存区的大小为:128。缓存区使用队列存放事件

Flowable的能力

  • request()可以控制observer(观察者)接受事件的速度

  • requested()可以控制Observable(被观察者)发送事件的速度

  • 背压策略:处理缓存区的逻辑

下面以异步订阅为例,讲解这三个能力的使用,更详细的使用以及同步订阅的处理,参考我的博文

Android之Rxjava2.X 8————Rxjava 背压策略

request()

@SuppressLint("CheckResult")
fun backpressure(){
    Flowable.create(FlowableOnSubscribe<Int> {
        Log.d(TAG, "发送事件 1");
        it.onNext(1);
        Log.d(TAG, "发送事件 2");
        it.onNext(2);
        Log.d(TAG, "发送事件 3");
        it.onNext(3);
        Log.d(TAG, "发送事件 4");
        it.onNext(4);
        Log.d(TAG, "发送完成");
        it.onComplete();
    },BackpressureStrategy.ERROR)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(object : Subscriber<Int> {
            override fun onSubscribe(s: Subscription?) {
                s?.request(3)
            }

            override fun onNext(t: Int?) {
                Log.d(TAG, "接收到了事件 $t");
            }

            override fun onError(t: Throwable?) {
                Log.d(TAG, "onError $t");
            }

            override fun onComplete() {
                Log.d(TAG, "onComplete ");
            }

        })

image

注意点:

  1. 如果未设置request,则默认未接收

  2. 观察者不接收事件的情况下,被观察者继续发送事件 & 存放到缓存区,再按需求取出

  3. 超出缓存区的数据,会按照策略进行处理

requested()

异步订阅下,**requested()**反向控制Observable

image

Flowable.create(
    FlowableOnSubscribe<Int> {
        // 被观察者一共需要发送500个事件
        for (i in 0..500) {
            var flag = false;
            // 若requested() == 0则不发送
            while (it.requested() == 0L) {
                if (!flag) {
                    Log.d(TAG, "不再发送");
                    flag = true;
                }
            }

            // requested() ≠ 0 才发送
            Log.d(TAG, "发送了事件" + i + ",观察者可接收事件数量 = " + it.requested());
            it.onNext(i);
        }
    },
    BackpressureStrategy.ERROR)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(object : Subscriber<Int> {
        override fun onSubscribe(s: Subscription?) {
            subscription = s
        }

        override fun onNext(t: Int?) {
            Log.d(TAG, "接收到了事件 $t");
        }

        override fun onError(t: Throwable?) {
            Log.d(TAG, "onError $t");
        }

        override fun onComplete() {
            Log.d(TAG, "onComplete ");
        }

    })

image

image

image

背压策略

目前rxjava有5种策略

  • BackpressureStrategy.ERROR 超出缓存区,抛出异常

  • BackpressureStrategy.MISSING 提示缓存区满了

  • BackpressureStrategy.BUFFER 缓存区大小设置成无限大

  • BackpressureStrategy.DROP 超过缓存区大小(128)的事件丢弃

  • BackpressureStrategy.LATEST 只保存最新(最后)事件,超过缓存区大小(128)的事件丢弃(即如果发送了150个事件,缓存区里会保存129个事件(第1-第128 + 第150事件))

3.2.3 flow中背压的使用

flow的操作符很简单,使用一个操作符解决:

buffer(capity, onBufferOver)

  • capity 缓存的大小

  • onBufferOverflow 缓存超出的策略

同时给予buffer衍生了一系列背压相关的操作符

  • conflate(): 只取最新的数据,等价 buffer(0, DROP_OLDEST),即不缓存数据,直接取最新数据的处理

  • collectLatest():类似conflate,但是不会直接用新数据覆盖老数据,而是每一个都会被处理,只不过如果前一个还没被处理完后一个就来了的话,处理前一个数据的逻辑就会被取消。

  • mapLatest:同理 collectLatest

  • flatMapLatest:同理 collectLatest

flow {
    (1..500).forEach {
        delay(100)
        println("emit: $it, ${System.currentTimeMillis()}, ${Thread.currentThread().name}")
        emit(it)
    }
}
    .buffer(capacity = 128, onBufferOverflow = BufferOverflow.DROP_OLDEST)
    .collect {
        delay(500)
        Log.d(TAG, "接收到了事件 $it");
    }

采用了DROP_OLDEST策略,此时当缓冲区超出处理速度时,就会丢弃老数据

image

3.3 flow拓展

冷流:即下游无消费行为时,上游不会产生数据,只有下游开始消费,上游才从开始产生数据。

热流:即无论下游是否有消费行为,上游都会自己产生数据

3.3.1一般flow

一般的flow,仅有一个观察者。冷流

//构建
val testFlow = flow<String>{
    emit("hello")
    emit("flow")
}

//接收
coroutineScope.launch{
    testFlow.collect{ value->
        println(value)
    }
}

//打印
hello
flow

3.3.2 stateFlow

  • 有状态的FLow 可以有多个观察者,热流

  • 需要时传入初始值, initialState

  • 常用作与UI相关的数据观察,类比LiveData

//创建
val uiState=MutableStateFlow(Result.Loading)

//监听
coroutineScope.launch{
    uiState.collect{ value->
        println(value)
    }
}

//赋值
uiState.value=Result.Sucess

//打印结果
Result.Loading
Result.Sucess

3.3.3 sharedFlow

  • 升级版StateFloe,有多个观察者,热流,无需初始值

有三个参数值

  • replay - 重播给新订阅者的值的数量(不能为负,默认为零)

  • extraBufferCapacity - 除了replay之外缓冲的值的数量。 当有剩余缓冲区空间时, emit不会挂起(可选,不能为负,默认为零)

  • onBufferOverflow - 配置缓冲区溢出的操作(可选,默认为暂停尝试发出值)

//创建
val signEvent=MutableSharedFlow <String> ()

//监听
coroutineScope.launch{
    signEvent.collect{ value->
        println(value)
    }
}
//赋值
signEvent.tryEmit("hello")
signEvent.tryEmit("shared flow")

//打印结果
hello
shared flo

3.4 更多操作符

3.4.1 RxJava操作符系列

Rxjava最大的优势就是种类繁多的操作符。常用的具体如下,这里只进行列举功能,详细的可以参考后面的文档:

  • 创建操作 Android之Rxjava2.X 2————Rxjava 创建操作符

    • create:Create操作符创建一个完整的Observable,可以传递onNext,onError和onCompleted等事件
    • just: 根据传入的参数,快速创建一个Observable
    • fromArray :根据传入的数组,快速创建一个Observable
    • fromIterable: 根据传入的集合,快速创建一个Observable
    • empty:仅发送Complete事件,直接通知完成。error:仅发送Error事件,直接通知异常。never:不发送任何事件
    • defer: 发生订阅时,才创建Observable
    • timer:延迟给定时间后,创建Observable
    • Interval: 创建一个按固定的时间间隔发射一个无限递增的整数序列的Observable
    • intervalRange: 创建一个按固定的时间间隔发射一个给定的事件序列范围的Observable
    • range /rangeLong: 创建一个发射给定的事件序列范围的Observable
  • 变换操作 Android之Rxjava2.X 3————Rxjava 变换操作符

    • map: 根据传入的函数,对Observable发射的每一项数据处理转化
    • flatMap:将Observable发射的每一个数据转化成一个新的Observable。注意这个过程可能是无序的
    • concatMap:类似于flatMap,但它是可以保证有序
    • Buffe: 被观察者(Obervable)需要发送的事件中, 获取一定数量的事件放到缓存区中,最终发送
  • 组合操作 Android之Rxjava2.X 4————Rxjava 组合操作符

    • concat/concatArray: 组合多个被观察者一起发送数据,合并后 按发送顺序串行执行
    • merge/mergeArray :组合多个被观察者一起发送数据,合并后 按时间线并行执行
    • zip:合并多Observable,并根据BiFunction函数将多个Observable的值生成一个新的值发射出去。注意zip是按照个数合并
    • combineLatest: 合并多Observable,并根据BiFunction函数将多个Observable的值生成一个新的值发射出去。combineLatest是按照时间线合并
    • concatDelayError / mergeDelayError/combineLatestDelayError: 将onError()事件延迟到所有Observable都发送完事件后再执行
    • reduce:将Observable的所有事件聚合为1个事件
    • collect:将Observable的所有事件聚合到一个数据结构中(list)
    • startWith / startWithArray:在一个被观察者发送事件前,追加发送一些数据 / 一个新的被观察者
    • count:统计被观察者发送事件的数量
  • 过滤操作 Android之Rxjava2.X 5————Rxjava 过滤操作符

    • filter:筛选符合要求的事件。返回true则发送事件,否则不会发送
    • ofType:类似filter,它可以让Observable只返回指定类型的数据。
    • skip/skipLast: 只返回前n/后n个数据
    • distinct/ distinctUntilChanged:过滤事件序列中重复的事件 / 连续重复的事件
    • take/takeLast:只发射前面/后面的N项数据
    • throttleFirst:/ throttleLast(sample): 在某段时间内,只发送该段时间内第1次事件 / 最后1次事件
    • sample:在某段时间内,只发送该段时间内最新(最后)1次事件
    • throttleWithTimeout /debounce: 发送数据事件时,若2次发送事件的间隔<指定时间,就会丢弃前一次的数据
    • firstElement / lastElement/elementAt: 仅选取第1个元素 / 最后一个元素/指定位置
  • 功能 Android之Rxjava2.X 6————Rxjava 功能操作符

    • subscribeOn/observerOn 线程切换
    • delay:延迟发送
    • doxx:在事件的某个生命周期中调用(doOnEach/doOnNext/…)
    • onErrorReturn/onErrorResumeNext /onExceptionResumeNext:遇到错误时,发送1个新的Observable/中止发送
    • retry/retryUntil/retryWhen:重试,即当出现错误时,让被观察者(Observable)重新发射数据
    • repeat/repeatWhen:重新发送
  • 条件操作符:Android之Rxjava2.X 7————Rxjava 条件操作符

    • all:判定是否Observable发射的所有数据都满足某个条件
    • takeWhile:发射Observable发射的数据,直到一个指定的条件不成立
    • skipWhile:丢弃Observable发射的数据,直到一个指定的条件不成立
    • takeUntil:执行到某个条件时,停止发送事件
    • skipUntil:等到 skipUntil() 传入的Observable开始发送数据,(原始)第1个Observable的数据才开始发送数
    • SequenceEqual:判定两个Observables是否发射相同的数据序列。
    • contains:判断发送的数据中是否包含指定数据
    • isEmpty:判断发送的数据是否为空
    • amb:当需要发送多个 Observable时,只发送 先发送数据的Observable的数据,而其余 Observable则被丢弃。
    • defaultIfEmpty:在不发送任何有效事件( Next事件)、仅发送了 Complete 事件的前提下,发送一个默认值

3.4.2 flow操作符系列

我发现flow的操作符,基本上可以替代RxJava,也提供了诸多操作符来处理数据

具体使用和示例参考博客:https://juejin.cn/post/6989536876096913439

  • 创建flow

    • flow:创建Flow
    • flowOf:快速创建 flow
    • asFlow:将其他数据转换成 普通的flow ,一般是集合向Flow的转换
    • callbackFlow:将回调方法改造成flow
    • emptyFlow:返回一个空流
    • channelFlow:创建一个允许在构造代码块中切换线程的flow
  • 末端操作,在flow最后调用,此时返回的不是一个flow了

    • collect:触发flow的运行 。 通常的监听方式
    • collectIndexed:带下标的collect
    • collectLatest:与 collect的区别是 ,有新值发出时,如果此时上个收集尚未完成,则会取消掉上个值的收集操作(之后xxxlatest区别都是这个)
    • toCollection/toList/toSet:将结果添加到集合/list/set
    • launchIn:直接触发流的执行
    • last/lastOrNull /first/firstOrNull/single/singleOrNull:返回流 发出 的最后/第一个值
    • count:返回流发送值的个数
    • fold/reduce:从初始值开始(reduce无)执行遍历,并将结果作为下个执行的 参数。
  • 变换操作

    • map/mapLatest/mapNotNull:将发出的值 进行变换
    • transform/transformLatest/transformWhile:对发出的值 进行变换 ,区别于map, transform的接收者是FlowCollector
    • asStateFlow/asSharedFlow: 将 MutableSharedFlow 转换为 StateFlow/SharedFlow
    • receiveAsFlow/consumeAsFlow:将Channel 转换为Flow
    • withIndex:将结果包装成IndexedValue 类型
    • scan/runningFold/runningReduce:和 fold 相似,区别是fold 返回的是最终结果,scan返回的是个flow ,会把初始值和每一步的操作结果发送出去。
    • shareIn/stateIn:将普通flow 转化为 SharedFlow/StateFlow
  • 组合操作符

    • zip:对两个流进行组合,分别从二者取值,一旦一个流结束了,那整个过程就结束了。
    • combine:组合每个流最新发出的值。
    • merge:合并多个流为 一个流
    • flattenConcat/flattenMerge:以顺序方式将给定的流展开为单个流 ,flattenMerge可以设置并发收集流的数量。
    • flatMapContact /flatMapLatest:这是一个组合操作符,相当于 map + flattenConcat , 通过 map 转成一个流,在通过 flattenConcat
    • flatMapMerge:也是组合操作符,简化使用。 map + flattenMerge
  • 过滤操作符

    • filter/filterNot/filterNotNull:筛选出符合条件的值
    • filterInstance:筛选对应类型的值
    • drop:作用是 丢弃掉前 n 个的值
    • dropWhile:找到第一个不满足条件的,返回其和其之后的值。
    • take:返回前 n个 元素
    • takeWhile:也是找第一个不满足条件的项,但是取其之前的值
    • debounce:防抖节流
    • sample:给定一个时间周期,仅获取周期内最新发出的值
    • distinctUntilChangedBy/distinctUntilChanged:去重操作符,判断连续的两个值是否重复
  • 功能操作符

    • cancellable :接收的的时候判断 协程是否被取消 ,如果已取消,则抛出异常
    • catch:对上游异常进行捕获 ,对下游无影响
    • retryWhen:有条件的进行重试
    • retry:流发生异常时可以重新执行
    • buffer:处理背压
    • conflate:仅保留最新值
    • flowOn:指定上游操作的执行线程

4.牛刀小试

4.1联合判断&点击防抖

模拟登陆场景描述:

  • 两个EditText,输入账号和密码。一个Button,点击发起登录

  • 只有两个EditText有输入,此时button从灰色变成蓝色,并且可以点击

  • button点击要具有防抖功能(1s内只有第一次算有效点击)

需求分析

联合判断: 只有当账号和密码都有输入时,才可以点击

  • 将账号和密码的输入视为两个数据流,当两个数据流都符合要求时,产生一个新的数据流表示是否可点

  • 可以考虑使用组合操作符:combineLatest(combine)。将两个EditText的产生的字符流按照时间线,合并为一个表示是否可以点击的数据流

button的防抖功能

  • 将button的点击视为数据流规定时间周期之内,只接受第一个事件

  • 可以考虑过滤操作符: throttleFirst(sample)在1s内只接受第一个事件,或者直接使用sample(debounce)防抖操作符

同时,这里引入了一个新的问题,如何将Android UI操作转化为数据流

4.1.1 Rxjava实现

如果将View的操作转化为Observable,可以直接使用现有的第三方库(但tt好像没有引入)

compile ‘com.jakewharton.rxbinding:rxbinding:0.4.0’

自己实现一个View操作转化为Observable,参考ObservableCreate

将button点击转化为Observable(其他逻辑的需要转化成Observable可以参考)

class ViewClickObservale(private val view: View) : Observable<View>() {

    override fun subscribeActual(observer: Observer<in View>) {
        val listener = Listener(view, observer)
        observer.onSubscribe(listener)
        view.setOnClickListener(listener)
    }

    internal class Listener(private val view: View, private val observer: Observer<in View>) :
        MainThreadDisposable(), View.OnClickListener {

        override fun onClick(v: View) {
            if (!isDisposed) {
                observer.onNext(v)
            }
        }
        override fun onDispose() {
            view.setOnClickListener(null)
        }
    }
}

完成需求:

@SuppressLint("CheckResult")
private fun logOnRxjava() {

    val account: EditText = findViewById (R.id.account)
    val password: EditText = findViewById (R.id.password)
    val logOn: Button = findViewById (R.id.log_on)
    logOn.isEnabled = false

    //使用RxBinding将EditText将textChanges转化为Observable
    //skip跳过 一开始EditText无任何输入时的空值
    val accountObservable =  RxTextView.textChanges(account).skip(1)
    val passwordObservable =  RxTextView.textChanges(password).skip(1)

    //使用combineLatest将事件合并 两个EditText的输入-->button是否可点
    Observable.combineLatest(accountObservable,passwordObservable) { _, _ ->
       account.text.isNotEmpty() && password.text.isNotEmpty()
    }.subscribe{
        logOn.isEnabled = it
    }

    //使用自定义的Observale,将view的点击转化为Observale
    ViewClickObservale(logOn) 
        .throttleFirst(1, TimeUnit.SECONDS)//根据时间,进行防抖
        .subscribe{
            Toast.makeText(this,"正在登录...",Toast.LENGTH_LONG).show()
        }
}

实现效果:

此处应该有视频,但掘金不支持视频

4.1.2 flow实现

将ui流数据转化为flow,因为ui在主线程,而flow的collect必须在协程中调用,所以使用channelFlow创建flow。更多可以参考文章:使用更为安全的方式收集 Android UI 数据流

private fun viewClickFlow(view: View): Flow<View?> = channelFlow {
    view.setOnClickListener {
        trySend(view)
    }
}

private fun viewTextChangeFlow(view: TextView):Flow<CharSequence?> = channelFlow {
    val callback = object : TextWatcher{
        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
        override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
            trySend(p0)
        }
        override fun afterTextChanged(p0: Editable?) {}
    }

    view.addTextChangedListener(callback)

    awaitClose {
        view.removeTextChangedListener(callback)
    }
}

需求实现:

    private fun logOnFLow(){

        val account: EditText = findViewById(R.id.account)
        val password: EditText = findViewById(R.id.password)
        val logOn: Button = findViewById(R.id.log_on)
        logOn.isEnabled = false

//        将EditText的文本变化转化为flow流
        val accountFlow = viewTextChangeFlow(account)
        val passwordFlow = viewTextChangeFlow(password)

//        开启协程
        lifecycleScope.launchWhenStarted {
//        使用combine将事件合并 两个EditText的输入-->button是否可点
            accountFlow.combine(passwordFlow) { _, _ ->
                account.text.isNotEmpty() && password.text.isNotEmpty()
            }.collect {
                logOn.isEnabled = it
            }

            viewClickFlow(logOn)
                .sample(1000L)
                .collect {
                    Log.e(TAG,"正在登录...")
                }
        }
    }

4.2网络联合请求&本地缓存

模拟联合请求场景

  • 有一段文本的数据来源于两个接口数据的混合处理,并且要分别进行本地缓存

  • 数据优先从本地获取,如果获取不到,在进行网络请求,请求完成后,在进行本地缓存

  • 两个接口的数据要同时展示

需求分析

多级缓存,请求时先判断本地缓存是否有数据,如果没有数据,则进行网络请求

  • 将本地缓存的数据和网络请求的数据视为数据流,并按顺序发送数据流。并将第一个有效的数据返回

  • 使用组合操作符concat(merge) 将本地缓存数据流,网络请求的数据流合并为一个流,并串行依次执行

  • 使用过滤操作符take(1)获取合并流的第一个有效元素

联合请求:将多个网络请求的数据源,同时进行展示

  • 将两个数据内容视为数据流,并一对一进行合并为一个新的数据流

  • 使用组合操作符zip,可以满足需求

4.2.1 Rxjava实现

将网络请求和本地获取数据包装为Observable

object DataControl {
    const val BAI_DU = "https://www.baidu.com/"
    const val SOU_GOU = "https://www.sogou.com/"
    private const val SP_NAME = "sp_name"
    private val retrofit: RetrofitApi by lazy {
        Retrofit.Builder()
            .addConverterFactory(ScalarsConverterFactory.create())
            .client(OkHttpClient())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .baseUrl(BAI_DU)
            .build()
            .create(RetrofitApi::class.java)
    }

    //使用retrofit 可以直接转化为Observable
    fun getNetworkBaiduString() = retrofit.baidu(BAI_DU)
    fun getNetworkSougouString() = retrofit.souGou(SOU_GOU)
    //获取本地数据,并转化为Observable
    fun getLocalString(cxt: Context, channel: String): Observable<String?> {
        val baiduString = cxt.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
                .getString(channel, "")
        //如果本地没有数据,直接使用empty通知完成
        return if (baiduString.isNullOrEmpty()) Observable.empty() else Observable.just(baiduString)
    }
    //将数据保存到本地
    fun setLocalString(cxt: Context, channel: String, data: String) {
        val editor = cxt.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE).edit()
        editor.putString(channel, data)
        editor.apply()
    }

    interface RetrofitApi {
        @GET
        fun baidu(@Url url: String?): Observable<String?>
        @GET
        fun souGou(@Url url: String?): Observable<String?>
    }
}

多级缓存的实现

private fun getBaiduData():Observable<String?> {
    val baiduLocalObservable = DataControl
        .getLocalString(this, DataControl.BAI_DU)
        .map {
            //进行额外处理
            "数据来源:本地 百度->$it"
        }

    val baiduNetworkObservable = DataControl
        .getNetworkBaiduString()
        .subscribeOn(Schedulers.io()) // 切换到IO线程进行网络请求
        .observeOn(AndroidSchedulers.mainThread()) // 切换回到主线程 处理请求结果
        .map {
            //保存数据
            DataControl.setLocalString(this, DataControl.BAI_DU, it)
            //进行额外处理
            "数据来源:网络 百度->$it"
        }

    //获取百度数据
    //concat 串行执行本地。网络的两个数据流
    //只返回第一个数据
   return Observable.concat(baiduLocalObservable, baiduNetworkObservable)
        .take(1)
}

private fun getSouGouData():Observable<String?> {
    val souGouLocalObservable = DataControl
        .getLocalString(this, DataControl.SOU_GOU)
        .map {
            "数据来源:本地 搜狗->$it"
        }

    val souGouNetworkObservable = DataControl
        .getNetworkSougouString()
        .subscribeOn(Schedulers.io()) // 切换到IO线程进行网络请求
        .observeOn(AndroidSchedulers.mainThread()) // 切换回到主线程 处理请求结果
        .map {
            DataControl.setLocalString(this, DataControl.SOU_GOU, it)
            "数据来源:网络 搜狗->$it"
        }

    //获取百度数据
    //concat 串行执行本地。网络的两个数据流
    //只返回第一个数据
    return Observable.concat(souGouLocalObservable, souGouNetworkObservable)
        .take(1)
}

联合请求的实现

val baidu: TextView = findViewById(R.id.baidu)
val souGou: TextView = findViewById(R.id.souGou)
val button :Button = findViewById(R.id.button)
button.setOnClickListener {
    //zip,将百度和搜狗的数据一一对应进行组合。然后发出去
    Observable.zip(getBaiduData(),getSouGouData()) { s1, s2 ->
        Pair(s1, s2)
    }.subscribe {
        baidu.text = "时间: ${System.currentTimeMillis()} ${it.first}"
        souGou.text ="时间: ${System.currentTimeMillis()} ${it.second}"
    }
}

实现效果:

此处应该有视频,但掘金不支持视频

4.2.2 flow实现

将网络请求和本地获取数据包装为flow

object DataFlowControl {
    const val BAI_DU = "https://www.baidu.com/"
    const val SOU_GOU = "https://www.sogou.com/"
    private const val SP_NAME = "sp_name"
    private val retrofit: RetrofitApi by lazy {
        Retrofit.Builder()
            .addConverterFactory(ScalarsConverterFactory.create())
            .client(OkHttpClient())
            .addCallAdapterFactory(FlowCallAdapterFactory.create())
            .baseUrl(BAI_DU)
            .build()
            .create(RetrofitApi::class.java)
    }

    //使用retrofit 可以直接转化为Observable
    fun getNetworkBaiduString() = retrofit.baidu(BAI_DU)
    fun getNetworkSouGouString() = retrofit.souGou(SOU_GOU)

    //获取本地数据,并转化为Observable
    fun getLocalString(cxt: Context, channel: String): Flow<String?> {
        val baiduString = cxt.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
                .getString(channel, "")
        //如果本地没有数据,直接使用empty通知完成
        return if (baiduString.isNullOrEmpty()) emptyFlow() else flowOf(baiduString)
    }
    //将数据保存到本地
    fun setLocalString(cxt: Context, channel: String, data: String) {
        val editor = cxt.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE).edit()
        editor.putString(channel, data)
        editor.apply()
    }

    interface RetrofitApi {
        @GET
        fun baidu(@Url url: String?): Flow<String?>
        @GET
        fun souGou(@Url url: String?): Flow<String?>
    }
}

多级缓存的实现

private fun getBaiduFlowData():Flow<String?>{
    val baiduLocalFlow = DataFlowControl
        .getLocalString(this, DataControl.BAI_DU)
        .map {
            //进行额外处理
            "数据来源:本地 百度->$it"
        }

    val baiduNetWorkFlow = DataFlowControl
        .getNetworkBaiduString()
        .flowOn(Dispatchers.IO)
        .map {
            //保存数据
            it?.let { DataControl.setLocalString(this, DataControl.BAI_DU, it) }
            //进行额外处理
            "数据来源:网络 百度->$it"
        }.flowOn(Dispatchers.Main)

    //获取百度数据
    //merge 串行执行本地。网络的两个数据流
    //take(1)只返回第一个数据
    return listOf(baiduLocalFlow, baiduNetWorkFlow)
        .merge()
        .take(1)

}


private fun getSouGouFlowData():Flow<String?>{
    val souGouLocalFlow = DataFlowControl
        .getLocalString(this, DataControl.SOU_GOU)
        .map {
            //进行额外处理
            "数据来源:本地 搜狗->$it"
        }

    val souGouNetWorkFlow = DataFlowControl
        .getNetworkSouGouString()
        .flowOn(Dispatchers.IO)
        .map {
            //保存数据
            it?.let {DataControl.setLocalString(this, DataControl.SOU_GOU, it)}
            //进行额外处理
            "数据来源:网络 搜狗->$it"
        }.flowOn(Dispatchers.Main)
    //获取搜狗数据
    //merge 串行执行本地。网络的两个数据流
    //take(1)只返回第一个数据
    return listOf(souGouLocalFlow, souGouNetWorkFlow)
        .merge()
        .take(1)
}

联合请求的实现

private fun requestFlow() {
    val baidu: TextView = findViewById(R.id.baidu)
    val souGou: TextView = findViewById(R.id.souGou)
    val button: Button = findViewById(R.id.button)
    button.setOnClickListener {
        lifecycleScope.launch {
            //zip,将百度和搜狗的数据一一对应进行组合。然后发出去
            getBaiduFlowData().zip(getSouGouFlowData()) { s1, s2 ->
                Pair(s1, s2)
            }.collect {
                baidu.text = "时间: ${System.currentTimeMillis()} ${it.first}"
                souGou.text = "时间: ${System.currentTimeMillis()} ${it.second}"
            }
        }
    }
}

实现效果如上

5.参考文档:

Kotlin Flow 介绍
异步流 kotlin语言中文站

对比 RxJava 入门 Kotlin-flow - 掘金

【Kotlin Flow】 一眼看全——Flow操作符大全 - 掘金

Carson带你学Android:这是一篇清晰易懂的Rxjava入门教程

Kotlin Flow是一种用于异步数据流处理的库,它提供了一种类似于RxJava的响应式编程模型。Flow的实现原理主要涉及以下几个方面: 1. Flow的基本概念:Flow是一种冷流(Cold Stream),它是一种异步的、可取消的序列。Flow中的数据是按需产生的,只有当有收集器订阅时,才会开始产生数据。 2. Flow的操作符:Flow提供了一系列的操作符,用于对数据流进行转换、过滤、合并等操作。这些操作符是惰性的,只有当有收集器订阅时,才会触发数据的处理。 3. Flow的调度器:Flow可以通过调度器指定数据流的执行线程。调度器可以将数据流的处理切换到指定的线程池或协程上下文中,以实现并发执行或避免阻塞主线程。 4. Flow的背压支持:Flow提供了背压支持,可以通过调整缓冲区大小或使用缓存策略来处理生产者和消费者之间的速度不匹配问题。 5. Flow的异常处理:Flow可以通过catch操作符捕获异常,并在异常发生时执行特定的逻辑。此外,Flow还提供了retry和retryWhen操作符,用于处理异常重试。 6. Flow的取消支持:Flow可以通过取消协程来终止数据流的产生和处理。当取消流时,Flow会自动取消相关的协程,以释放资源并停止数据的产生。 总结起来,Kotlin Flow的实现原理主要涉及冷流、操作符、调度器、背压支持、异常处理和取消支持等方面。通过理解这些原理,可以更好地使用和理解Kotlin Flow库。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值