前言
Flow是Kotlin协程中的流。RxJava是流式编程库。Flow属于冷流对应Rxjava的Observable Flowable Single MayBe和Completable等。kotlin中热流实现MutableShared和MutableFlow等,对应RxJava中热流PublisherSubject和BehaviorSubject。
Flow是基于协程的响应式编程,处理异步数据流。与RxJava相比,Flow优势在于协程中使用,即是优点(协程中使用Flow更加简单)又是局限。Flow是冷流,只有在收集端(collect)开始监听时,生产端(emit)才开始执行。Flow冷流按需生产数据,避免不必要的计算和资源浪费。
冷流:
- 只有在flow{}内部才会产生输入。如emit(xxx)
- 必须有消费者才会产生数据,需要末端操作符collect()
- 生产者和消费者--对应
Flow 处理机制
fun main() {
runBlocking {
demo()
}
}
/**
* 挂起函数
*/
private suspend fun demo(){
flow {
(1..3).forEach{
delay(1000)
// 在flow{}内部;挂起函数
println("emit value:$it")
emit(it)
}
}.collect{
// 不调用collect,上面emit不会执行
println("collect value:$it")
}
}
输出:
emit value:1
collect value:1
emit value:2
collect value:2
emit value:3
collect value:3
同一个协程中多个flow是同步执行的,第二个collect等待上个collect执行完毕。
fun main() {
runBlocking {
demo()
}
}
/**
* 两个flow在同一个协程中执行
*/
private suspend fun demo(){
flowOf(1,2,3).onEach {
delay(1000)
}.collect{
println("collect1 value:$it")
}
// 上面的flow会阻塞下面的,是同步执行的。
flowOf(4,5).onEach {
delay(1000)
}.collect{
println("collect2 value:$it")
}
}
输出:
collect1 value:1
collect1 value:2
collect1 value:3
collect2 value:4
collect2 value:5
异步执行
fun main() {
runBlocking {
// 协程1
launch {
flowOf(1,2,3).onEach {
delay(1000)
}.collect{
println("collect1 it:$it")
}
}
//协程2 上面的flow不会阻塞下面的,是异步执行
launch {
flowOf(4,5).onEach {
delay(1000)
}.collect{
println("collect2 it:$it")
}
}
}
}
输出:
collect1 it:1
collect2 it:4
collect1 it:2
collect2 it:5
collect1 it:3
两个协程中互不阻塞。
切换线程
fun main() {
runBlocking (Dispatchers.Default){
// 发送10个元素,从0到9
val myFlow = flow {
repeat(10){
// 修改原来的CoroutineContext,会异常
withContext(Dispatchers.IO){
emit(it)
}
}
}
launch {
myFlow.collect{
println("Coroutine1:$it")
}
}
}
}
Flow不能使用withContext来切协程,可以使用flowOn切协程,或者使用ChannelFlow+withContext来切
fun main() {
// 不要用withContext切换flow的线程。用flowOn来切
runBlocking {
flow {
emit(1)
}.onEach {
delay(1000)
}.map {
println("map1:${Thread.currentThread().name}")
}.flowOn(
Dispatchers.IO
).map {
println("map2:${Thread.currentThread().name}")
}.flowOn(Dispatchers.Default
).map {
println("map3:${Thread.currentThread().name}")
}.collect{
println("collect:${Thread.currentThread().name}")
}
}
}
输出:
map1:DefaultDispatcher-worker-2 @coroutine#3
map2:DefaultDispatcher-worker-1 @coroutine#2
map3:Test worker @coroutine#1
collect:Test worker @coroutine#1
可以多次使用flowOn切换不同的逻辑代码执行线程。
fun main() {
runBlocking (Dispatchers.Default){
// 发送10个元素,从0到9
val myFlow = channelFlow {
repeat(10){
// 可以修改原来的CoroutineContext
withContext(Dispatchers.IO){
channel.send(it)
}
}
}
launch {
myFlow.collect{
println("Coroutine1:$it")
}
}
}
}
flow执行完成
fun main() {
runBlocking {
flow {
emit(1)
delay(1000)
emit(2)
throw RuntimeException("test error")
}.onCompletion {
println("onCompletion")
}.catch {
println("catch:$it")
}.collect{
println("collect:$it")
}
}
}
输出:
collect:1
collect:2
onCompletion
catch:java.lang.RuntimeException: test error
flow执行完成最终会走到onCompletion,不管是否发生异常,内部try/catch实现,可以做一些释放操作。
Flow的性能和背压
处理大规模数据时,用buffer操作符进行性能优化,使用onEach进行流的中间处理。
val flowWithBuffer: Flow<Data> = fetchData()
.onEach { data ->
// 中间处理逻辑
}
.buffer() // 使用buffer操作符进行性能优化
buffer允许流插入缓冲区,缓解生产者和消费者速度不一致问题,提高性能。背压处理可以用conflate操作符。conflate会丢弃掉生产者的旧数据,保留新数据,避免背压。
val conflatedFlow: Flow<Data> = fetchData()
.onEach { data ->
// 中间处理逻辑
}
.conflate() // 使用conflate操作符进行背压处理
数据生产大于消费速度时,保证消费者只处理最新数据,避免队列无限增长导致内存问题。
热流
- 不需要在flow产生数据,可以在其他任意地方
- 不管消费者是否订阅,生产者都会产生数据
flatMapConcat
类似于RxJava中的concatMap操作符
fun main() {
runBlocking {
val myFlow = flow {
repeat(3) {
println("emit:$it threadName:${Thread.currentThread().name}")
emit(it)
}
}
launch {
// 将原来的流元素构建成一个新的源(按照原来的流元素输出)
myFlow.flatMapConcat { upstreamValue ->
flow {
repeat(2) {
emit(upstreamValue * 10 + it)
}
}
}.collect {
println("collect:$it threadName:${Thread.currentThread().name}")
}
}
}
}
输出:
emit:0 threadName:Test worker @coroutine#2
collect:0 threadName:Test worker @coroutine#2
collect:1 threadName:Test worker @coroutine#2
emit:1 threadName:Test worker @coroutine#2
collect:10 threadName:Test worker @coroutine#2
collect:11 threadName:Test worker @coroutine#2
emit:2 threadName:Test worker @coroutine#2
collect:20 threadName:Test worker @coroutine#2
collect:21 threadName:Test worker @coroutine#2
在同一个协程中emit和collect数据,通过faltMapMerge来构建成一个新的流,顺序emit和collect.
flatMapMerge
把上面代码改为flatMapMerge
fun main() {
runBlocking {
val myFlow = flow {
repeat(3) {
println("emit:$it threadName:${Thread.currentThread().name}")
emit(it)
}
}
launch {
// 将原来的流元素构建成一个新的源(按照原来的流元素输出)
myFlow.flatMapMerge { upstreamValue ->
flow {
repeat(2) {
emit(upstreamValue * 10 + it)
}
}
}.collect {
println("collect:$it threadName:${Thread.currentThread().name}")
}
}
}
}
输出:
emit:0 threadName:Test worker @coroutine#3
emit:1 threadName:Test worker @coroutine#3
emit:2 threadName:Test worker @coroutine#3
collect:0 threadName:Test worker @coroutine#2
collect:1 threadName:Test worker @coroutine#2
collect:10 threadName:Test worker @coroutine#2
collect:11 threadName:Test worker @coroutine#2
collect:20 threadName:Test worker @coroutine#2
collect:21 threadName:Test worker @coroutine#2
emit和collect在两个不同的协程。协程#3顺序emit。协程#2顺序collect。从协程号上可以看出是先创建collect协程再创建emit协程(这也简洁说明flow是冷流,由collect开始的)
去掉launch
fun main() {
runBlocking {
val myFlow = flow {
repeat(3) {
println("emit:$it threadName:${Thread.currentThread().name}")
emit(it)
}
}
// 这个会使emit和collect不在同一个协程中
myFlow.flatMapMerge { upstreamValue ->
flow {
repeat(2) {
emit(upstreamValue * 10 + it)
}
}
}.collect {
println("collect:$it threadName:${Thread.currentThread().name}")
}
}
}
输出:
emit:0 threadName:Test worker @coroutine#2
emit:1 threadName:Test worker @coroutine#2
emit:2 threadName:Test worker @coroutine#2
collect:0 threadName:Test worker @coroutine#1
collect:1 threadName:Test worker @coroutine#1
collect:10 threadName:Test worker @coroutine#1
collect:11 threadName:Test worker @coroutine#1
collect:20 threadName:Test worker @coroutine#1
collect:21 threadName:Test worker @coroutine#1
可以看到我们写到同一个协程里,但是用了flatMapMerge会使emit和collect不在同一个协程里输出。
fun main() {
runBlocking {
val myFlow = flow {
repeat(3) {
println("emit:$it threadName:${Thread.currentThread().name}")
emit(it)
}
}
// 并发数是1
myFlow.flatMapMerge(1) { upstreamValue ->
flow {
repeat(2) {
emit(upstreamValue * 10 + it)
}
}
}.collect {
println("collect:$it threadName:${Thread.currentThread().name}")
}
}
}
输出:
emit:0 threadName:Test worker @coroutine#1
collect:0 threadName:Test worker @coroutine#1
collect:1 threadName:Test worker @coroutine#1
emit:1 threadName:Test worker @coroutine#1
collect:10 threadName:Test worker @coroutine#1
collect:11 threadName:Test worker @coroutine#1
emit:2 threadName:Test worker @coroutine#1
collect:20 threadName:Test worker @coroutine#1
collect:21 threadName:Test worker @coroutine#1
当flatMapMerge的concurrency(默认16)是1(并发数是1),emit和collect在同一个协程中。则emit一次colect消费后才发送下一个emit。
flatMapLatest
fun main() {
runBlocking {
val myFlow = flow {
repeat(3) {
println("emit:$it threadName:${Thread.currentThread().name}")
emit(it)
}
}
// 会使emit和collect不在同一个协程中。并发数是1
myFlow.flatMapLatest { upstreamValue ->
flow {
repeat(2) {
emit(upstreamValue * 10 + it)
}
}
}.collect {
println("collect:$it threadName:${Thread.currentThread().name}")
}
}
}
输出:
emit:0 threadName:Test worker @coroutine#3
emit:1 threadName:Test worker @coroutine#3
emit:2 threadName:Test worker @coroutine#3
collect:20 threadName:Test worker @coroutine#2
collect:21 threadName:Test worker @coroutine#2
在两个协程中,flatMapLatest中延迟100ms进行flatMapLatest操作,只会拿到最后一次的flatMapLatest(保证消费者拿到最新,丢弃中间数据)。如果去掉flatMapLatest中的delay延迟,则还是两个协程中执行,但是可以看到emit和collect全部打印出来了。也就是说flatMapLatest中如果是耗时的则会丢弃中间数据保证最后的数据得到。
flatMapConcat默认emit和collect在同一个协程中(串式)。flatMapMerge(并发)和flatMapLatest(并发中间数据可能丢失,保证最新数据)默认emit和collect不在同一个协程中。
总结:
Flow错误处理机制、数据转换合并、性能优化、背压等