【深入理解Kotlin协程】Google的工程师们是这样理解Flow的?

news2024/10/6 10:39:50

Question:why there is a Flow in kotlin?

问这个问题就好比在问为什么那里会有一座山存在,嗯,这貌似是一个哲学问题。当然,对于kotlin中的Flow的理解可能不会上升到这么高的哲学层次,对于Flow相关的Api掌握并使用它并不是什么难事,但是我们需要思考的是为什么会有Flow这样的存在?

其实flow的背后是协程,那么kotlin中的协程框架已经能够做到异步任务问题的解决方案了,为什么还要设计Flow呢?设计这个玩意的最初的目的和构想是为了什么样的意图?

为了搞明白这个问题的真相,我特意通过科学上网的方式,了解了一下Google的Android工程师们对Flow的理解。下面主要是记录一下理解产生Flow这个想法的过程。

注意看,下面这个男人叫小帅,他每天的一项任务就是提着水桶到湖边去取水。

在这里插入图片描述

小帅一年365天都在重复着这样的内容,可是不出意外的话,意外终于发生了,有一天小帅来到湖边后发现湖泊居然干了,于是他不得不尝试跑去寻找其他新的水源。

在这里插入图片描述
但是聪明的小帅很快就发现了问题,他在想:与其这样到不同的湖边跑来跑去,为什么不能在湖边架设一根管道,然后让水流沿着管道自动流过来呢?就像下面这样:

这样即使有多个湖泊也可以通过管道将它们连接起来:

这样以后小帅就不必每次亲自跑到湖边去检查湖泊有没有干,只要湖泊没有干,那么小帅在管道的另一头只需要拧开水龙头,就会自动有水流出。

现在回想一下在一个应用当中,其实我们请求应用界面所需的数据跟这个场景很相似,不是吗,想象一下,我们平时是不是在到处请求数据,然后拿到数据以后再返回到使用它的地方去更新View界面?就像下面这样:

我们就跟小帅一样,不停的在应用的各个地方去请求数据,拿到数据后再返回到UI界面去更新,而且这样的地方非常的多,因此我们总是为此而疲于奔命,有时甚至感到分身乏术。
在这里插入图片描述

假设我们不再请求数据,而是改为观察数据,观察数据就像安装管道一样,我们在上游的数据源位置安装管道,一旦部署到位,对数据源的任何更新都将自动流向下游的视图当中:

这样我们再也不用每次都走到湖边了。不管应用的数据源有多少个,我们只需要在不同的数据源之间架设好管道,然后将所有的管道用统一的管道相连,最终将管道的另一头连接到我们的view界面,然后就可以自动订阅数据的更新了!是不是很棒:

在这里插入图片描述
这样做其实是在数据源和最终的显示界面搭建了一条单向的管道流:

下面来回顾一下Flow的Api加深对这个问题的理解:

一个Flow流的两端分别对应生产者和消费者,因此Flow是生产者-消费者模式。

在Flow中,操作在同一个协程中按顺序执行。

我们可以通过map操作符对流进行变换,将原始数据转换为适合UI界面展示的Model实体类,这就好比在一节管道之后另外又接了一节管道。

这些操作符变换的地方都属于管道的上游,而接受数据的地方则属于管道的下游:


通过catch操作符,可以在需要时抛出异常或者发射新的数据:

最后,我们可以通过Flow.collect收集数据,这就像在管道的终端拧开水龙头一样:

当然,我们可以多次拧开水龙头,每次拧开时,都会有水流出来,因此我们可以多次调用Flow.collect,每次都会产生新的数据流:

另外需要注意的一点是,Flow.collect是一个挂起函数,因此它需要在一个协程当体中调用。

当然在很多时候,我们不需要自己亲自动手创建Flow,如果你使用官方推荐的一些Jetpack组件库或者一些著名的三方库,它们在生产数据时会为你自动输出一个Flow对象供你使用,比如下面这些:

Don’t waste resources

好了,我们现在可以使用流了,那么到此故事就完了吗? 并没有,回到前面湖泊的比喻中,在费了九牛二虎之力安装好管道之后,小帅深知这水来之不易,于是他决定节约用水,因此小帅总是在需要的时候才会打开水龙头(比如在刷牙时),而不是一直开着水龙头让水一直流,在小帅不需要水的时候(比如睡觉时)他会关闭水龙头。这个道理非常的简单好理解,同样地,回到我们的应用中,如果某些信息不会在屏幕上面显示,界面就不应该从数据流中收集此类信息。(比如应用被用户切到后台)

需要注意的是,通过lifecycleScope.launch或者lifecycleScope.launchWhenXXX方法中对Flow进行collect的方式都是不安全的做法:

那么如何正确的做到这一点呢,官方为我们提供了以下几种方案:
在这里插入图片描述

使用repeatOnLifecycle会只在onStat-onStop之间收集数据,而在onStop-onStart之间则会停止收集数据:

下一个问题:为什么会有StateFlow?

要理解这个问题,首先得考虑一下下面这个场景:

当我们旋转屏幕时,Activity可能被销毁重建,而ViewModel却能够得以保留,假如我们通过ViewModel直接暴露出Flow,而Flow是一种冷流,每次首次收集冷数据流时,它都会重启,那么下面的代码会被重复调用:

因此,我们需要的是某种缓存区,无论重新创建多少次,这种缓冲区都可以保存数据,并在多个收集器之间共享数据。StateFlow正是为此用途而设计的。

回到我们湖泊的比喻当中,StateFlow就好比一个水箱,它可以暂时保存很多的水。

即使没有收集器,它也能保存数据。

您可以从中收集很多次,并随时根据需要更新它的值。

您可以将任何的普通数据流转换成一个StateFlow,这样做将使用StateFlow来接收上游数据流的所有更新,并存储最新的值。而且收集器的数量可以是0个或多个,因此它非常适合和ViewModel一起使用。

以下是将任意Flow转换成一个StateFlow的方法:

这里通过调用stateIn方法即可返回一个StateFlow对象,stateIn有三个参数,其中initialValue和scope非常好理解,分别表示初始值和所处的协程作用域,但是这里的 started = WhileSubscribed(5000) 是什么含义呢?要理解它又得先考虑以下的场景:

其中第一个场景是前面提到过的Activity在屏幕旋转或者配置更改时被销毁随后在短时间内重建,第二个场景是用户导航到桌面,但是此时并没有关闭应用,只是被切到后台。

在旋转的场景中,我们不希望重启任何数据流以便尽可能快地完成过渡,但是在导航到桌面的场景中,我们希望停止所有数据流,以节省电量和其他资源。那么如何正确的判断不同的场景呢?我们通过设置超时来做到这一点,当停止收集StateFlow时,不会立即停止所有上游数据流,而是会等待一段时间,比如5秒钟,如果在超时前再次收集数据流,则不会取消上游数据流。

这就是 WhileSubscribed(5000) 的作用:

下图展示了当按下Home键应用转到后台的过程中会发生什么:
当按下Home键之前,视图持续接受更新
在这里插入图片描述
当超过超时时间之后,上游数据流被取消
在这里插入图片描述
只有当用户再次打开应用时,如果发生这种情况,上游数据流会自动重启
在这里插入图片描述
而在旋转的场景中,视图只停止了很短的时间,因此StateFlow不会重启,并保持所有上游数据流都处于活动状态,就像什么都没发生一样,可即时向用户呈现旋转后的屏幕。
在这里插入图片描述
因此官方推荐我们使用StateFlow通过ViewModel来公开数据流:

关于Flow的单元测试

单元测试关键就在于两个字“模拟”,不管你在测试什么数据流,都会有一个数据来源

如果是上图这种情况,如果受测单元接受一个产生流的Respository,我们只需将输入受测单元的那个Repository变成FakeRepository就可以:

如果受测单元本身就是一个产生流的Respository,那么只需将输入Respository的DataSource变成FakeDataSource:

然后通过first()或take()这样的方法取到对应的模拟数据项进行验证

参考:Kotlin Flows in practice

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/30541.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaSE笔记——多态

文章目录前言一、向上转型回顾1.忘掉对象类型二、转机1.方法调用绑定2.产生正确的行为3.可扩展性三、构造器和多态1.构造器调用顺序2.构造器内部多态方法的行为四、协变返回类型总结前言 本文是学习Java编程思想记录的笔记,主要内容介绍在 Java 中多态的概念。 多…

Servlet API(HttpSerrvlet+HttpServletRequest+HttpServletResponse)

目录 🐲 1. HttpServlet 🐲 2. HttpServletRequest HTTP请求 🦄 2.1 打印请求信息(创建 ShowRequest 类) 🦄 2.2 获取 GET 请求中的参数(创建 GetParameter 类) 🦄 2.3 获取 POST 请求中的参数(创建 PostParame…

java学习day57(Spring Cloud)Spring Cloud 微服务

主要课程内容 第⼀部分:微服务架构 互联网应用架构演进 微服务架构的体现思想及优缺点 微服务架构的核心概念 第⼆部分:SpringCloud概述 Sping Cloud 是什么 Sping Cloud 解决什么问题 Sping Cloud 架构 第三部分:案例准备 第四部分&#xff…

2022华为杯研究生数学建模竞赛DS数模选题建议

2022华为杯研究生数学建模竞赛DS数模选题建议 开放性:F>E>AD>BC. 难度:AD>BC>EF (仅C君个人看法) A题 移动场景超分辨定位问题 此题是物理cv类题目,属于比较新颖的超分辨率图像检测类任务&#xff0c…

1、Java的json得到我们想要的数据结构

Java的json得到我们想要的数据结构 第一步:首先我们要知道json就两种数据结构。 !!!第一种数据结构:对象用{ }表示 !!!第二种数据结构:数组用[ ]表示 我们用这个案例来…

在智能家居领域产品中常用芯片

芯片是当前“电子科技设备的灵魂”所在,几乎决定了所有电子设备的综合性能,现如今智能家居带来了全新的使用场景与交互方式,从扫地机器人、智能洗碗机、智能冰箱等智能机器,到智能照明、智能感知、网络通讯、家庭影音等智能系统&a…

H264基础知识入门

之前视频基础,有讲到视频的原始数据YUV,相比RBG,数据确实减少了,但还是一个非常大数据量,会占用很大空间以及在给网络传输带来很大压力。所以必须要对视频进行压缩,减少占用空间。这里主要分享H264编码技术…

数字IC设计之——低功耗设计

目录 概述 背景 为什么需要低功耗设计 CMOS IC功耗分析 基本概念 功耗的分类 功耗相关构成 不同层次低功耗设计方法 芯片中的功耗分布以及对应的低功耗方案 低功耗方案 系统算法级的低功耗技术 编码阶段的低功耗技术 门控时钟 Clock Gating 物理实施的低功耗技术 操作数分离&am…

【第六部分 | JavaScript高级】1:面向对象

目录 【第一章】面向对象 | Class创建、构造函数、方法 | Class继承 | 三个注意点 | 静态成员 | 原型对象 __ _proto___ | 类的本质 【第一章】面向对象 | Class创建、构造函数、方法 创建类 class name {// class body }var xx new name() 构造函数 class Person {co…

【Godot】数据响应的方式执行功能

Godot Engine 版本:4.0 beta 6 下载地址:Index of /godotengine/4.0/beta6/ (downloads.tuxfamily.org) 在这个教程中,学会理解以数据为主的进行处理执行逻辑的代码编写方式,虽然看似简单,但是确是方便又好用。 以及下…

Git使用教程

Git项目的三个工作区域的概念: 1、Git仓库Git 仓库目录是 Git 用来保存项目的元数据和对象数据库的地方。 这是 Git 中最重要的部分,从其它计算机克隆仓库时,拷贝的就是这里的数据。 2、工作目录工作目录是对项目的某个版本独立提取出来的内容…

Ansible之 AWX 创建管理项目的一些笔记

写在前面 分享一些 AWX 创建管理项目的笔记博文内容涉及: 容器化 AWX 手工创建项目Demo通过 SCM 创建项目 Demo项目角色,更新策略介绍,SCM 凭据的创建 食用方式: 需要了解 Ansible理解不足小伙伴帮忙指正 傍晚时分,你坐…

ssm项目改造spring boot项目

快速创建 Spring Boot 项目 添加依赖 如果是普通 Maven 项目&#xff0c;需要手动添加。 <!-- 打包方式 jar 包 --> <packaging>jar</packaging><!-- 指定父工程 --> <parent><groupId>org.springframework.boot</groupId><ar…

操作系统学习笔记(Ⅰ):概述

目录 1 操作系统概念 1.1 定义 1.2 功能 1.系统资源的管理者 2.用户和计算机硬件间接口 3.最接近硬件的层次 2 操作系统的特征 2.1 并发 2.2 共享 2.3 虚拟 2.4 异步 3 发展和分类 3.1 手工操作阶段 3.2 批处理阶段 1.单道批处理阶段 2.多道批处理系统 3.3 分…

启明欣欣STM32开发板闪烁LED实验

最近在咸鱼上买了一块启明欣欣的STM32板子&#xff0c;准备在上面测试open62541和CANopen&#xff0c;到货后如下图&#xff0c; 找商家要了资料&#xff0c;然后运行一个LED灯的实验来简单测试下板子&#xff0c;本文记录一下这个过程。 一 准备 安装Keil 5.35&#xff0c;安…

【selection】 学习光标API并实现编辑区插入表情图片的功能

目录场景介绍selection介绍selection APIrange 介绍range API实现编辑区插入表情图片参考资料场景介绍 在写web版聊天器时&#xff0c;遇到一个需求&#xff1a; 聊天时用户可以在编辑区加入表情图片&#xff0c;并且表情图片要插入在光标位置。// *web版聊天器地址&#xff…

useMemo 使用误区

文章の目录问题背景useMemo 使用前后组件性能对比结论问题背景 在某一个h5项目中&#xff0c;使用了 useMemo 对项目中的组件进行优化&#xff0c;减少组件不必要的re-render, 优化后的结果&#xff1a; 在组件的props和状态未改变时&#xff0c;组件不再进行 re-render 表面上…

生意不好如何逆风翻盘 | 多门店经营必读技巧(1):导购管理 连锁店管理的技巧 连锁店生意经 如何做导购管理

很多连锁店老板反馈&#xff0c;为了优化门店的销售业绩&#xff0c;什么方法都试过了&#xff0c;改店铺陈列、搞优惠活动、做会员管理.......为什么分店的业绩还是看不到明显的提升&#xff1f; 方法试过了&#xff0c;结果没变化&#xff0c;那只能是执行这块出了问题&#…

量子计算(八):观测量和计算基下的测量

文章目录 观测量和计算基下的测量 一、观测量 二、计算基下的测量 三、投影测量 观测量和计算基下的测量 一、观测量 量子比特&#xff08;qubit&#xff09;不同于经典的比特&#xff08;bit&#xff09;&#xff0c;一个量子比特|>可以同时处于|0>和|1>两个状态…

Linux从入门到精通(八)——Linux磁盘管理

文章篇幅较长&#xff0c;建议先收藏&#xff0c;防止迷路 文章跳转Linux从入门到精通&#xff08;八&#xff09;——Linux磁盘管理goLinux从入门到精通&#xff08;九&#xff09;——Linux编程goLinux从入门到精通&#xff08;十&#xff09;——进程管理goLinux从入门到精…