官方 flow 地址:https://www.kotlincn.net/docs/reference/coroutines/flow.html

flow 说明

流简单使用

suspend fun flow1() {flow<Int> {(0..4).forEach {emit(it)//生产者发送数据}}.collect {println(it)//消费者处理数据}
}

本例中 flow { … } 构建块中的代码可以挂起

流是冷的,所以 collect 被调用后 flow 内的方法体才会被调用

流操作符

本文要讲的操作符:
flowwOf,asFlow,map,transform,take,toList,toSet,first,reduce,buffer,collectLast,zip,combine,

流构建器

flowof

可以将 flowOf 内的可变长参数一一发射

flowOf(1, 2, 5, 4).collect {println(it)}
asFlow

flowOf 可以将集合转换成 flow 发射

suspend fun asFlowM(){listOf(1,2,9,0,8).asFlow().collect{println(it)}
}

过渡流操作符

过渡操作符可以对流进行转换,也就是获取生产者发射的数据做一定更改,然后转给消费者

map

我们可以再 map 中执行一些过渡操作,比如本例中将生产者发送的数据*9,然后再发射给消费者

suspend fun mapM(){(1..9).asFlow().map {it*9}.collect{println(it)}
}

值得一提的是,我们是可以再 map 中进行异步操作的,比如有以下代码:

 flow<Int> {var userId = login()userId}.map {var permission = getPermission(it)permission}.collect{println("打印权限 $it")}

这段代码中我们模拟了这样一个情景,先在 flow 中调用 login 方法异步获取用户 id,然后再 map 中调用 getPermission 异步获取用户权限。然后将用户权限信息返回给消费者。

通常我们的代码中有多个接口需要连续调用的时候就很适合用这种方法,可以十分有效的避免接口调用嵌套

转换操作符 transform

transform 主要强调的是类型的转换

(1..3).asFlow() // 一个请求流//transform中的泛型<Int,String> 表示将Int类型转换为String后,继续发射.transform<Int, String> { request ->emit("transform Int to String $request")}.collect { response -> println(response) }

本例中发射的数据是 Int 类型,但是我们 collect 中需要的数据是 String 类型,所以我们可以再 transform 中进行将 Int 类型转换为 String,然后使用 emit 继续发射

限长操作符 take

take 操作符可以限定我们要消费的数据的数量,见代码

(1..9).asFlow().take(3).collect {println(it)}

本例中,我们生产者发送了 1…9,一共 9 个数字,但是因为使用了 take(3)操作符,所以只有前三个发射的数字才能被消费者消费到

末端流操作符

末端操作符,我理解的就是消费者调用的方法,比如 collect 就是末端操作符

toList

会把数据消费到一个 List 列表中

suspend fun toList():List<Int> {return (1..9).asFlow().filter { it % 2 == 0 }.toList()
}
toSet

同 toList

frist

获取第一个元素

suspend fun firstM(): Int {return (2..9).asFlow().filter { it % 2 == 1 }.first()
}
reduce

reduce 的兰布达表达式会提供运算公式负责计算。

在 reduce 的兰布达表达式中,可以对当前要消费的值和之前计算的值进行计算,得到新值返回。所有值消费完成后返回最终值

suspend fun reduceM():Int {return (1..9).asFlow().reduce { accumulator, value ->println("$accumulator : $value")accumulator + value}
}

流是连续的

流上下文

缓冲

官方demo了解缓冲出现的原因


通过图中我们发现,生产者生产数据的时候消费者是需要等待的,然后生产者发送完数据后消费者处理数据,期间生产者必须等消费之处理完数据才能继续发射数据

这就是一种相互阻塞了,那么有没有一种办法能够让消费者消费数据的时候生产者能继续生成对象呢,还真有buffer就可以

buffer

buffer可以缓存生产者数据,不会被消费者阻塞

suspend fun bufferM() {val startMillis = System.currentTimeMillis()flow<Int> {(1..3).forEach {delay(300)emit(it)}}.buffer(4).collect {delay(400)println(it)println("时间已经过了${System.currentTimeMillis() - startMillis}")}
}

代码执行打印日志:

1
时间已经过了745
2
时间已经过了1148
3
时间已经过了1552

如果我们没有用buffer,那么总时长应该2100ms

使用了buffer总时长是:1552=300+400*3

所以使用buffer的时候生产者可以并发发射数据,不会被消费者阻塞

组合多个流

conflate

当生产者发射数据速度大于消费者的时候,消费者只能拿到生产者最新发射的数据

suspend fun conflate(){flow<Int> {(1..9).forEach {delay(100)emit(it)}}.conflate().collect {delay(300)println(it)}
}

比如上面这段代码,因为有conflate的存在,输出如下:

1
3
6
9

如果没有conflate存在输出如下:

1
2
3
4
5
6
7
8
9

两者对比,明显能发现使用conflate的例子替我们忽略了很多无法即时处理的数据

collectLast

这个操作符的意思:如果生产者数据以及发射过来了,消费者还没有把上一个数据处理完,那么直接停止处理上一条数据,直接处理最新的数据


suspend fun collectLastM(){flow<Int> {(1..9).forEach {delay(100)emit(it)}}.collectLatest {delay(800)println(it)}
}

比如本例的输出如下:

zip

zip操作符可以把两个流合并为一个流,然后再zip方法中将两个流发射的数据进行处理组合后继续发射给消费者,
如果两个流长度不一致,按比较短的流来处理:

  1. 两个流长度一致,都是3
suspend fun zipM(){val flow1 = (1..3).asFlow()val flow2 = flowOf("李白","杜甫","安安安安卓")flow1.zip(flow2){a,b->"$a : $b"}.collect {println(it)}
}

输出:

1 : 李白
2 : 杜甫
3 : 安安安安卓

上面的代码我们进行一下改变,将flow1的长度改为5

val flow1 = (1..5).asFlow()

查看输出结果:

1 : 李白
2 : 杜甫
3 : 安安安安卓

所以验证一下我们开头的结论,两个长度不同的流zip合并,消费者输出的数据长度是较短的流的长度

combine

上一节zip的缺点我们清楚了,就是两个流长度不等的时候,较长的流后面部分无法输出

那么combine就是用来解决zip这个缺点的(也很难说是缺点,只是应用场景不同罢了,你姑且可以认为是缺点)


suspend fun combineM(){val flowA = (1..5).asFlow()val flowB = flowOf("李白","杜甫","安安安安卓")flowA.combine(flowB){a,b->"$a : $b"}.collect {println(it)}
}

输出日志:

1 : 李白
2 : 李白
2 : 杜甫
3 : 杜甫
3 : 安安安安卓
4 : 安安安安卓
5 : 安安安安卓

我们的两个流,数字流长度为5,字符串流为3。

实现的效果简单逻辑分析:

flow发射1,flow2发射 ”李白“ ,打印:1 : 李白
flow发射2,flow2未发射数据  ,打印:2 : 李白
flow未发射,flow2发射 ”杜甫“ ,2 : 杜甫
flow发射3,flow2未发射 ,打印:3 : 杜甫
flow未发射,flow2发射 ”安安安安卓“ ,打印:3 : 安安安安卓
flow发射4,flow2发射完成  ,打印:4 : 安安安安卓
flow发射5,flow2发射完成  ,打印:5 : 安安安安卓

展平流

下面三个流,暂时不写,因为我没有想到应用场景

flatMapConcat
flatMapMerge
flagMapLatest

流异常

使用try/catch包裹流

我们是可以使用try/catch来收集流异常的,但是不建议用这种方法

使用flow的catch操作符处理流

使用flow 的catch操作符处理异常更优雅

不过catch也有缺点,它只能捕获生产者的异常不能捕获消费者的异常


suspend fun trycatch() {flow<Int> {(1..3).forEach {if (it == 2) {//故意抛出一个异常throw NullPointerException("强行空指针,嘿嘿嘿嘿")}emit(it)}}.catch {e->e.printStackTrace()emit(-1)//异常的情况下发射一个-1}.collect{println(it)}
}

消费者的异常如何处理

上一节我们学校了catch生产者的异常,那么消费者产生的异常该如何处理呢。

尝试在消费者中抛出异常,查看是否可以被捕获
 flow<Int> {for (i in 1..3) {emit(i)}}.catch {emit(-1)}.collect {if(it==2){//在消费者中抛出数据throw IllegalArgumentException("数据不合法")}println(it)}

输出:

1
Exception in thread "main" java.lang.IllegalArgumentException: 数据不合法at HahaKt$consumerCatch$$inlined$collect$1.emit(Collect.kt:138)
将异常代码放在onEach中catch异常
suspend fun consumerCatch() {flow<Int> {for (i in 1..3) {emit(i)}}.onEach {if (it == 2) {//与上面的不同,在消费之前先用onEach处理一下throw IllegalArgumentException("数据不合法")}}.catch {emit(-1)}.collect {println(it)}
}

输出:

1
-1

流完成onCompletion

使用onCompletion可以再流完成的时候再发送一个值

 flowOf(1, 23, 5, 3, 4).onCompletion {println("流操作完成")emit(12344)//这里不返回值也没关系}.collect {println(it)}

输出:

1
23
5
3
4
刘操作完成
12344

Kotlin Flow详解相关推荐

  1. 【协程】冷流flow详解

    一.目前异步返回多个值方案 如果存在异步返回多个值的需求,我们探索以下解决方案 1.1.集合 集合返回了多个值,但是一个一个返回的,不是异步 fun simpleList(): List<Int ...

  2. Kotlin -by 详解

    Kotlin 中 by 关键字用来简化实现代理 (委托) 模式,不仅可以类代理,还可以代理类属性, 监听属性变化,下面我们来介绍by的几种主要使用场景: 类的代理 class 属性延迟加载 lazy ...

  3. Kotlin DSL详解

    DSL简介 所谓DSL领域专用语言(Domain Specified Language/ DSL),其基本思想是"求专不求全",不像通用目的语言那样目标范围涵盖一切软件问题,而是专 ...

  4. Android kotlin实战之协程suspend详解与使用

    前言 Kotlin 是一门仅在标准库中提供最基本底层 API 以便各种其他库能够利用协程的语言.与许多其他具有类似功能的语言不同,async 与 await 在 Kotlin 中并不是关键字,甚至都不 ...

  5. Android Jetpack组件DataStore之Proto与Preferences存储详解与使用

    一.介绍 Jetpack DataStore 是一种数据存储解决方案,允许您使用协议缓冲区存储键值对或类型化对象.DataStore 使用 Kotlin 协程和 Flow 以异步.一致的事务方式存储数 ...

  6. Kotlin Flow | SharedFlow和StateFlow详解

    文章目录 Getting Started SharedFlow Handling Shared Events Event Emission With SharedFlow Replay and Buf ...

  7. python输入参数改变图形_Python基于Tensor FLow的图像处理操作详解

    本文实例讲述了Python基于Tensor FLow的图像处理操作.分享给大家供大家参考,具体如下: 在对图像进行深度学习时,有时可能图片的数量不足,或者希望网络进行更多的学习,这时可以对现有的图片数 ...

  8. Kotlin学习笔记26 协程part6 协程与线程的关系 Dispatchers.Unconfined 协程调试 协程上下文切换 Job详解 父子协程的关系

    参考链接 示例来自bilibili Kotlin语言深入解析 张龙老师的视频 1 协程与线程的关系 import kotlinx.coroutines.* import java.util.concu ...

  9. Kotlin——中级篇(二): 属性与字段详解

    在前面的章节中,详细的为大家讲解到了Kotlin中对类的类的定义.使用.初始化.初始化.类继承等内容,但是在一个类中,几乎上是不可能不出现属性与字段(field)的,这一篇文章就为大家奉上Kotlin ...

  10. 第 2-3 课:流式布局组件详解(Flow、Wrap)

    这节课将继续讲解 Flutter 的常用组件中的布局使用的组件,本节课主要讲解 Flutter 里的流式布局(或者瀑布)组件的用法.在 Flutter 中主要通过 Flow 和 Wrap 组件来实现流 ...

最新文章

  1. pandas.DataFrame.groupby
  2. Windows 不能在 本地计算机 启动 SQL Server 。错误代码126.
  3. 7.QML Qt Quick——基于Qt Quick Controls 2实现图片浏览器
  4. 如果有一个类是 myClass , 关于下面代码正确描述的是?
  5. vue项目使用npm run dev 编译到一半不动了
  6. 计算机应用等级考试1,计算机等级考试一级试题
  7. oSIP开发者手册 (二)
  8. 计算机基础说课稿十分钟,关于《计算机应用基础》的说课稿一、教材分析(说教材):.pdf...
  9. python按行拆分表格_Pandas进阶之DataFrame行列拆分
  10. PLC模糊控制之模糊化
  11. 定义平行四边形类,继承四边形类,增加判断是否为平行四边形的函数
  12. 城头土命适合做计算机电脑职业,土命人适合的职业
  13. MCE | “神药”二甲双胍后,糖尿病药物研究谁将是下一个顶流?
  14. 名人名言大全API 推荐
  15. Android TextView 字体 加粗以及判断是否加粗
  16. 数仓建设之IP库的匹配
  17. 看《梦华录》上头的人都该尝试下这款抖音特效
  18. Python 简单的龟鱼游戏
  19. 大数据智能算法及测评技术(二)
  20. ESP32学习笔记(45)——DAC接口使用

热门文章

  1. mac桌面存在顽固文件无法删除?怎么办?
  2. 思维导图之----javascript正则表达式知识树
  3. 23王道——层次遍历、非递归中序遍历
  4. word文档页眉清除和页码设置
  5. 昨晚《体育世界》LBJ在CCTV5
  6. 双路CPU笔记本计算机,什么是双路cpu cpu双路什么意思 - 云骑士一键重装系统
  7. 成都到乐山1日游攻略
  8. python3自动抢淘宝单
  9. mpchart点击_在MPAndroidChart中,如何为Barchart中的每个Bar添加click事件?
  10. 河南联通申请公网ip