Flow API是什么
Flow
库是在 Kotlin Coroutines
1.3.2 发布之后新增的库。是 Kotlin
协程与响应式编程模型结合的产物,与 RxJava
非常相似。Flow
结合协程可以代替Rxjava
在Android
中的地位
跟RxJava
中一样,Flow
在订阅者进行订阅之前,其主体不会被执行。即在流生成器内部的代码到了收集流后才开始运行。
创建Flow
元素的发送通过emit
函数提供
val flow = flow {
(1..10).forEach {
emit(1)
delay(200)
}
}.flowon(Dispatchers.IO)
在Flow
内部通过emit
方法来发送数据,类似于Rxjava
的onNext()
。另外执行体内部还可以调用其他的挂起函数。
flowon(Dispatchers.IO)
指定运行时所在的调度器为IO
调度器,对它之前的操作有影响,类似于RxJava
的subscribeOn
,消费者的线程就是调度协程的线程。
在 RxJava 的学习和使用过程中, subscribeOn
和 observeOn
经常容易被混淆;而在 Flow 当中 collect
所在的协程自然就是观察者,它想运行在什么调度器上它自己指定即可,非常容易区分。
消费Flow
通过collect()
来消费Flow
类似于Rxjava
的subscribe()
。因为collect()
是一个挂起函数,所以需要在挂起函数或者协程中调用,消费者的所在的线程由消费者协程的调度器决定
fun flowDemo2() {
val flow = flow {
Log.i(TAG, "flow")
(1..10).forEach {
emit(1)
delay(200)
}
}.flowOn(Dispatchers.IO)
viewModelScope.launch {
flow.collect { Log.i(TAG, it.toString()) }
}
}
冷数据流
所谓冷数据流,就是只有消费时才会生产的数据流。一个 Flow
创建出来之后,不消费则不生产,多次消费则多次生产,生产和消费总是相对应的。RxJava
的 Observable
也是如此,每次调用它的 subscribe
都会重新消费一次。
Flow
就是冷数据流,这一点与 Channel
正对应:Channel
的发送端并不依赖于接收端。
异常的处理
直接调用catch
函数捕获异常
val flow = flow {
(1..10).forEach {
emit(1)
delay(200)
}
}.catch {
Log.i(TAG, it.message)
// 实现Rxjava的onErrorReturn
emit(10)
}.onCompletion {
// 相当于finally 无论前面是否存在异常,它都会被调用
Log.i(TAG, "onCompletion")
}
onCompletion
类似于try catch
中的finally
末端操作符
除了collect
直接消费事件的末端操作符,asLiveData()
可以直接将一个flow
流转化为一个LiveData
对象
在liveData
中可以通过emitSource
来发送
val currentName = liveData(Dispatchers.IO) {
try {
Log.i(TAG, "发送者的线程:" + Thread.currentThread().name)
emitSource(flowDemo())
} catch (e: Throwable) {
e.printStackTrace()
}
}
suspend fun flowDemo(): LiveData<Int> {
return flow {
List(100) {
emit(it)
}
}.asLiveData()
}
取消Flow
flow
没有取消的方法,直接调用消费者协程的cancel
方法即可取消flow
其他Flow的创建方式
listOf(1, 2, 3, 4).asFlow()
setOf(1, 2, 3, 4).asFlow()
flowOf(1, 2, 3, 4)
channelFlow()
channelFlow {
for (i in 1..5) {
delay(100)
send(i)
}
}.collect{
println(it)
}
flow
是"冷流"(Cold Stream)
。在没有切换线程的情况下,生产者和消费者是同步非阻塞的。在切换线程的情况下跟channelFlow
是一致
channelFlow
是Hot Stream
实现了生产者和消费者异步非阻塞模型。
Flow的其他常用操作符
map:元素变换,也可以映射成其他Flow
。集合了Rxjava
的map
与flatMap
的功能
val flow = flow {
emit(1)
}.map {
repository.sendNetworkRequestSuspend()
}
retry: 有异常的情况下重试
// 5秒轮询一次 错误重试三次
suspend fun flowDemo(): LiveData<String> {
return flow {
while (true) {
emit(repository.sendNetworkRequestSuspend())
delay(5000)
}
}.map {
it.html_url
}.retry(3).catch {
// 类似于RxJava的onError
Log.e(TAG, it.message)
}.onCompletion {
// 类似于Rxjava中的onComplete
Log.i(TAG, "finally")
}.flowOn(Dispatchers.IO).asLiveData()
}
val currentName = liveData {
try {
emitSource(flowDemo())
} catch (e: Throwable) {
e.printStackTrace()
}
}
Retrywhen:满足条件为true
时重试
zip:合并两个flow
数据流,会分别对两个流合并处理,也就是快的流要等慢的流发射完才能合并。一般用作合并两个网络请求返回数据
val nums = (1..3).asFlow().onEach { delay(300) } // numbers 1..3 every 300 ms
val strs = flowOf("one", "two", "three").onEach { delay(400) } // strings every 400 ms
val startTime = System.currentTimeMillis() // remember the start time
nums.zip(strs) { a, b -> "$a -> $b" } // compose a single string with "zip"
.collect { value -> // collect and print
println("$value at ${System.currentTimeMillis() - startTime} ms from start")
}
//1 -> one at 471 ms from start
//2 -> two at 870 ms from start
//3 -> three at 1271 ms from start
combine:使用 combine 合并时,每次从 flowA 发出新的 item ,会将其与 flowB 的最新的 item 合并。
fun main() = runBlocking {
val flowA = (1..5).asFlow().onEach { delay(100) }
val flowB = flowOf("one", "two", "three","four","five").onEach { delay(200) }
flowA.combine(flowB) { a, b -> "$a and $b" }
.collect { println(it) }
}
// 1 and one
// 2 and one
// 3 and one
// 3 and two
// 4 and two
// 5 and two
// 5 and three
// 5 and four
// 5 and five
flattenMerge : flowA、flowB 作为单个流的执行。类似于Rxjava
的的merge
val flowA = (1..5).asFlow().onEach { delay(100) }
val flowB = flowOf("one", "two", "three","four","five").onEach { delay(200) }
flowOf(flowA,flowB)
.flattenMerge(2)
.collect{ println(it) }
filter:过滤
take:获取集合数据的前几个数据
drop:过滤集合的前集几个数据
suspend fun flowDemo(): LiveData<Int> {
return flow {
(1..10).forEach {
emit(it)
}
}.take(3).asLiveData()
}
onEach:遍历
onStart:开始会执行
协程背压
Kotlin
协程支持背压。Kotlin
流程设计中的所有函数都标有suspend
修饰符-具有在不阻塞线程的情况下挂起调用程序执行的强大功能。因此,当流的收集器不堪重负时,它可以简单地挂起发射器,并在准备好接受更多元素时稍后将其恢复。
buffer() 对应RxJava中的 BUFFER 策略
没有固定大小,可以无限制添加数据,不会抛出 MissingBackpressureException 异常,但可能会导致 OOM
fun main() = runBlocking {
var start = 0L
val time = measureTimeMillis {
(1..5)
.asFlow()
.onStart { start = System.currentTimeMillis() }
.onEach {
delay(100)
println("Emit $it (${System.currentTimeMillis() - start}ms) ")
}
.buffer()
.flowOn(Dispatchers.IO)
.collect {
println("Collect $it starts (${System.currentTimeMillis() - start}ms) ")
delay(500)
println("Collect $it ends (${System.currentTimeMillis() - start}ms) ")
}
}
println("Cost $time ms")
}
Emit 1 (109ms)
Collect 1 starts (115ms)
Emit 2 (219ms)
Emit 3 (324ms)
Emit 4 (426ms)
Emit 5 (531ms)
Collect 1 ends (618ms)
Collect 2 starts (618ms)
Collect 2 ends (1122ms)
Collect 3 starts (1123ms)
Collect 3 ends (1625ms)
Collect 4 starts (1625ms)
Collect 4 ends (2127ms)
Collect 5 starts (2127ms)
Collect 5 ends (2627ms)
Cost 2683 ms
conflate() 对应 LATEST 策略
如果缓存池满了,新数据会覆盖老数据
将上面buffer()
改成conflate()
接口如下
Emit 1 (114ms)
Collect 1 starts (117ms)
Emit 2 (217ms)
Emit 3 (329ms)
Emit 4 (433ms)
Emit 5 (538ms)
Collect 1 ends (620ms)
Collect 5 starts (620ms)
Collect 5 ends (1124ms)
Cost 1171 ms
collectLatest()
只处理最新的数据,这看上去似乎与 conflate
没有区别,其实区别大了:它并不会直接用新数据覆盖老数据,而是每一个都会被处理,只不过如果前一个还没被处理完后一个就来了的话,处理前一个数据的逻辑就会被取消。
flow {
List(100) {
emit(it)
}
}.collectLatest { value ->
println("Collecting $value")
delay(100)
println("$value collected")
}
结果如下:
Collecting 0
Collecting 1
...
Collecting 97
Collecting 98
Collecting 99
▶ 100ms later
99 collected