【Kotlin学习】协程的基本概念(3)——Flow

416 阅读6分钟

kotlin flow介绍

它是kotlin协程与响应式编程模型结合的产物

flow的使用

flow的基本使用方式

flow能够返回多个异步计算的值

image.png
如果熟悉RxJava,可以吧collect()对应subscribe(),而emit()对应onNext()

另外的创建flow方式
1.flowOf()

image.png

2.asFlow()

image.png
3.channelFlow()

image.png

channelFlow builderflow builder是有一定差异的
1.flow是Cold Stream,在没有切换线程的情况下,生产者和消费者是同步非阻塞
2.channel Flow是Hot Stream,channelFlow实现了生产者和消费者异步非阻塞模型

使用flow builder,大致花费1s

image.png

image.png

使用channelFlow builder

image.png

image.png

如果flow切换线程的化花费的时间会跟使用channelFlow builder的效果差不多

切换线程

只需要使用flowOn就可以切换线程

image.png

image.png
collect()指定哪个线程,要看整个flow处于哪个CoroutineScope

!不要用withContext切换flow线程

取消flow

如果flow是在一个挂起函数内被挂起了,那么flow是可以被取消的,否则不能

终端操作符

flow的API类似于java stream的API,它同样拥有Intermediate、Opreations、TerminalOperations

flow的Terminal运算符可以是suspend函数,如collect、single、reduce等,也可以是launchIn运算符,用于在指定CoroutineScope内使用flow

Terminal运算符

1.collect
2.single/first
3.toList/toSet/toCollection
4.count
5.fold/reduce
6.launchIn/produceIn/broadcastIn

flow的生命周期

flow有onStartonCompletion来监听flow的创建和结束

image.png

image.png

在Android中使用flow创建网络请求时通过onStart操作符调用loading动画以及网络请求结束后通过onCompletion操作符取消动画,或者做一些日志的打印

flow和RxJava

flow和sequences

每一个flow其内部都是按照顺序执行的,这一点和Sequenece类似。但是flow不会阻塞主线程的运行,而sequence会阻塞主线程的运行

使用flow

image.png

image.png

使用sequence

image.png

image.png

flow和RxJava

kotlin协程库参考了RxJava

RxJava2/3Flow
SingleDefered
MaybeDefered
CompletableJob
ObservableChannel、Flow
FlowableChannel、Flow

1.Cold Stream

flow的代码块只有调用collected()才开始运行,正如RxJava创建的Observables只有调用subscribe()才开始运行一样

2.HotStream

可以借助Kotlin Channel来实现Hot Stream

3.Completion

flow完成(正常或出现异常)时,若需要执行一个操作,可以通过imperativedeclarative来完成

imperative

通过使用try...finally实现

image.png

declarative 通过onCompletion()函数实现

image.png

onCompleted(借助扩展函数实现)

image.png
但当flow异常结束时不会执行onCompleted()

4.Backpressure

它是响应式编程会遇到的问题之一

RxJavaFlowable支持的Backpressure策略包括

1.MISSING

创建的Flowable没有指定背压策略,不会对通过onNext发射的数据做缓存或丢弃处理

2.ERROR

如果放入Flowable的异步缓存池的数据超限了,就会抛出MissingBackpressureException异常

3.BUFFER

Flowable的异步缓存池和Observable的一样,没有固定大小,可以无限制添加数据,不会抛出MissingBackpressureException,但会导致OOM

4.DROP

如果Flowable的异步缓存池满了,就会丢掉将要放入缓存池中的数据

5.LATEST

如果缓存池满了,就会丢掉将要放入缓存池中的数据,这一点和DROP策略一样,不同的是,无论缓存池的状态如何,LATEST策略都会将最后一条数据强行放入缓存池中

flow支持的Backpressure通过挂起函数实现

buffer()对应BUFFER策略

image.png

image.png

conflate对应LATEST策略

image.png

image.png

flow的异常处理

flow可以用try...catch来捕捉异常

catch操作符

前面讲过onCompletion操作符,但onCompletion不能捕获异常,只能用于判断是否有异常。catch操作符可以捕获来自上游的异常

image.png

image.png

若把onCompletion、catch交换位置,当catch操作符捕获到异常后,不会影响下游

image.png

image.png

catch操作符用于实现异常透明化处理,在catch操作符内可以使用throw再次抛出异常,可以使用emit()转换为发射值还可用于其他业务。但catch只是中间操作符,不能捕获下游的异常,但我们可以多次使用catch,还可以借助onEach,把业务逻辑放到onEach中,在onEach之后时catch,再之后是collect

retryretryWhen操作符

如果上游遇到异常并使用了retry操作符,那么retry会让flow最多重试retries指定的次数

image.png

image.png
retry操作符最终调用的是retryWhen操作符

image.png
retryWhen操作符的参数是谓词,当谓词返回true时才会进行重试,谓词还接收一个attempt作为参数,表示尝试的次数,从0开始

flow的线程操作

flowOn和RxJava的observeOn

obServeOn操作符接收一个Scheduler参数,用来指定下游操作符运行在特定的线程调度器Scheduler上。flowOn操作符接收一个CoroutineContext参数,影响的是上游的操作

image.png

image.png
flow builder和map都会受到flowOn影响,并使用Dispatchers.io线程池

image.png

image.png

flow builder和两个map操作符都会受到两个flowOn的影响,第二个map操作符会切换到指定的线程池

buffer实现并发操作

buffer操作符也可以并发地执行任务,它是除了使用flowOn操作符之外的另一种方式,只是不能显式指定Dispatchers

image.png

image.png
删除buffer后

image.png

并行操作

并发与并行的区别

1.并发:一个处理器同时处理多个任务 2.并行:多个处理器或者多核处理器同时处理多个不同的任务,并行是同时发生的多个并发时间

在flow中可以使用flatMapMerge实现并行

image.png
输出的数字为乱序

flow的其他操作符

转换操作符transform

在使用它时可以任意多次调用emit,这时它和map的最大区别

image.png

image.png

限制大小的操作符take

只取前几个emit发射的值

image.png

image.png

终端操作符

reduce

类似于kotlin集合的reduce函数,能够对集合进行计算操作

对平方数列求和

image.png

fold

也类似于kotlin集合的fold函数,也需要设置初始值

对平方数列求和

image.png

reduce和fold

image.png

image.png

查看两者源码我们可以看到,reduce把第一个元素当作起始值,而fold把我们自己设定的值作为初始值

合并操作符

zip

它可以将两个flow进行合并

image.png

image.png

它会把flowa中的一个item和flowb中对应的一个item进行合并,即使flowb中的每一个item都使用了delay(),在合并过程中也会等待delay()执行完再进行合并。若a中item个数大于b中个数,新的flow的item个数等于较小的flow的item个数

combine

每次从a发出新的item会将其与folwb最新的item合并

image.png

image.png

flattenMerge

它不会组合多个flow,而是把它们作为单个流执行

image.png

image.png

image.png

扁平化操作符

flatMapConcat

它由mapflattenConcat操作符实现

image.png
在调用它之后,collect函数在收集新值之前会等待flatMapConcat内部的flow完成

image.png

image.png

flatMapMerge

image.png
它顺序调用内部代码块并且并行地执行collect函数

image.png

image.png

flatMapLatest

当发射了新值之后,上个flow就会被取消

image.png

image.png