前言

这篇文章主要介绍Flow的一些基础使用方法
同时介绍如何用Flow请求网络数据
下面开始!

什么是Flow

Flow翻译过来,是“流”的意思
举例说明,在大自然中,常见的如水流
是从高往低流动的
那么在计算机世界里,所谓的“流”
其实指的是数据流
也就是从获取原始数据,到进行处理,最后使用的过程
比如拿到一个json,转换为bean
然后进行筛选和过滤
拿到最后要用的最终数据
这个过程就称之为数据的流动
如下图:

而为了处理这个过程
我们就可以使用到Flow这个工具

Flow流的使用

简单使用

要使用Flow,首先需要导入协程相关工具类:

    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7-mpp-dev-11'implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7-mpp-dev-11'

最简单的Flow使用:

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

我们分析下这段代码:
1:(0…4)是一个0-4的列表,这个是原始数据
2:emit其实就是把原始数据发送出去
3:collect用来收集发送的原始数据,并且内部自己打印了收到的数据
4:flow { } 函数包裹的代码块就是负责发送数据的,这个函数返回一个Flow对象
注意:Flow流是“冷流”
意思是 collect 被调用后 flow 内的方法体才会被调用
如果没有collect方法,不管如何emit,数据都是不会发送出去的

流操作符

Flow对于数据的操作,为我们提供了一系列的API
我们称之为“操作符”
一般分为“流构建器”、“中间操作符”和“末端操作符”
流构建器一般用来构建Flow流对象
中间操作符仅仅只是预先定义一些对流的操作方式,
比如过滤,转换等
并不会主动触发动作执行
而末端操作符则是对流的最终处理
比如collect就是末端操作符
下面就介绍一些操作符

流构建器

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,然后再发射给消费者
值得一提的是,我们是可以再 map 中进行异步操作的
注意,这个map和集合没什么关系,别被误导了

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

transform

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

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

take

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

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

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)}
}

比如本例的输出为9

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 : 安安安安卓

onCompletion

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

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

输出:

1
23
5
3
4
流操作完成
12344

末端操作符

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}
}

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的时候生产者可以并发发射数据,不会被消费者阻塞

流异常

使用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)}
}

消费者的异常如何处理
尝试在消费者中抛出异常,查看是否可以被捕获

 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

Flow请求网络数据

上面介绍了Flow对数据的处理方法
我们可以配合Retrofit在我们的项目中进行使用
比如我们先定义一个请求,返回类型为Flow数据流

    @POST(ServerName.BS_API + "test/url")fun getMessage(): Flow<List<Bean>>

使用viewModelScope和viewmodel的生命周期绑定
然后对返回的数据进行过滤,得到最终需要的数据:

        viewModelScope.launch {repository.getMessage()?.filter {it.isNotEmpty()}?.collect {//得到数据}}

这样,我们就可以用Flow对返回回来的数据做各种复杂的处理了
当然,这些还都是基本用法,可以自己根据实际业务情况进行更多的封装

相关资料

Kotlin Flow详解
Kotlin Flow啊,你将流向何方?
官方 flow 地址
使用 Kotlin Flow 构建数据流 “管道”

(原创)Flow数据流的使用相关推荐

  1. Android Kotlin之Flow数据流

    文章目录 Flow介绍 使用举例 常用操作符 创建操作符 回调操作符 变换操作符 过滤操作符 组合操作符 功能性操作符 末端操作符 冷流 vs 热流 SharedFlow shareIn将普通flow ...

  2. goim 中的 data flow 数据流转及思考

    goim 文章系列(共5篇): goim 架构与定制 从goim定制, 浅谈 golang 的 interface 解耦合与gRPC goim中的 bilibili/discovery (eureka ...

  3. Kotlin 协程与flow

    目录 协程基础 launch suspend coroutineScope join 终结动作 超时 组合式协程 async Flow intellij 配置 基础 flowOn 协程基础 launc ...

  4. 什么是数据流图 Data Flow Diagram (DFD)

    什么是数据流图(DFD)?如何绘制DFD? 一张图片胜过千言万语.数据流图(DFD)是系统内信息流的传统视觉表示.一个整齐而清晰的DFD可以用图形描绘出大量的系统需求.它可以是手动的,自动的或两者的组 ...

  5. 有小伙伴说看不懂 LiveData、Flow、Channel,跟我走

    背景 Kotlin Flow 是基于 Kotlin 协程基础能力搭建的一套数据流框架,从功能复杂性上看是介于 LiveData 和 RxJava 之间的解决方案.Kotlin Flow 拥有比 Liv ...

  6. Android 基于Kotlin Flow实现一个倒计时功能

    文章目录 前情提要 实现倒计时功能 注意事项 完整代码地址 前情提要 上一篇 Android Kotlin之Flow数据流 中介绍了协程Flow,我们知道Flow数据流可以按顺序发送多个值,一个倒计时 ...

  7. goim 架构与定制

    goim 文章系列(共5篇): goim 架构与定制 从goim定制, 浅谈 golang 的 interface 解耦合与gRPC goim中的 bilibili/discovery (eureka ...

  8. 嵌入式开发板硬件操作入门学习9——集成电路芯片手册术语词汇表(中英文对照)

    原创链接:集成电路芯片半导体中英文对照术语词汇表 英语 中文 1-9 10 gigabit 10 Gb 1st Nyquist zone 第一奈奎斯特区域 3D full‑wave electroma ...

  9. Enterprise Architect 7 入门教程 1

    一.  简介 生命周期软件设计方案--Enterprise Architect是以目标为导向的软件系统.它覆盖了系统开发的整个周期,除了开发类模型之外,还包括事务进程分析,使用案例需求,动态模型,组件 ...

最新文章

  1. Optional 是个好东西,你真的会用么?
  2. 红外测试操作步骤_红外光谱仪操作规程
  3. maven 和eclipse插件
  4. Linux的Nginx七:对比|模块
  5. R语言与总体比例的置信区间
  6. 华为服务器sn号查询网站,linux 查询服务器sn
  7. uefi linux开发环境,开发者为 Linux 添加了一系列 RISC-V UEFI 支持补丁
  8. 【POJ 1269】判断两直线相交
  9. shell linux中shell脚本编写俄罗斯方块
  10. 128 数据库基本操作
  11. 可视对讲网络协议转换器怎么使用,协议转换器使用方法详细介绍
  12. go语言中遍历中文出现乱码
  13. fl_studio-声卡设置、1
  14. excel中if如何加android,Excel 如何实现函数IF的嵌套超过七层
  15. Linux面试题史上最全总结
  16. TCL/TK 学习笔记 之 用C定义自己的TCL命令
  17. 在IT产品白皮书中遇到的缩略词
  18. dbus 嵌入式linux,MeeGo操作系统DBus调试工具
  19. 摘《阿里巴巴JAVA开发手册》易错题目
  20. java语言程序设计郑莉课后答案_Java语言程序设计郑莉课后习题答案.pdf

热门文章

  1. react styled-components插件完成独立组件css样式(js重构css写法)
  2. java模拟商店购买
  3. Microsoft edge chrome版安装,显示“此页存在问题”
  4. sed amp;amp; awk工具 及一些经常使用的shell脚本
  5. NCBI 数据介绍和下载
  6. python实现sql宽字节注入+布尔盲注
  7. 2021年5月11日 星期二 晴
  8. ubuntu使用docker拉取环境,并对环境进行优化
  9. windows10下anaconda3查看|开启|退出conda虚拟环境
  10. 苹果电脑MacBook M1芯片安装SPSS(数据分析工具)教程详细介绍:保姆级教程!!!