Kotlin Flows 流 — 基础

Kotlin Flows 流 — 基础

上一篇研究了 Kotlin 协程。上一篇文章重点介绍了协程的一些基础知识,如协程上下文(CoroutineContext)、协程作用域(CoroutineScope)、协程构建器等。如承诺的那样,这是关于流(Flows)的后续文章。

什么是流?

可以异步计算的数据流被称为流(Flow)。Flow,像 LiveData 和 RxJava 流一样,允许你实现观察者模式:由一个对象(源)维护其依赖项列表,称为观察者(收集器),并在任何状态变化时自动通知它们。流使用挂起函数异步产生和消费值。

要创建流,首先你需要创建一个生产者:

val randomFlow: Flow<Int> = flow {repeat(10) { it ->emit(it+1) // 向流中发射请求的结果delay(1000) // 挂起协程1秒}
}

要收集流,首先你需要启动一个协程,因为流在底层是操作协程的。使用 collect 操作符来收集它发射的值。

lifecycleScope.launch {viewModel.uiStateFlow.collect { it ->binding.uiText.text = it.toString()}
}

有两种不同类型的流:

冷流(Cold Flow)

它不会开始产生值,直到有人开始收集它们。它只能有一个订阅者,并且不存储数据。 // 常规流示例

val coldFlow = flow {emit(0) emit(1)emit(2)
}launch { // 第一次调用 collectcoldFlow.collect { value ->println("cold flow collector 1 received: $value")}delay(2500)// 第二次调用 collectcoldFlow.collect { value ->println("cold flow collector 2 received: $value")}
}

// 结果 // 两个收集器都会从开始获取所有值。 // 对于两个收集器,相应的流都从开始启动。

flow collector 1 received: [0, 1, 2]
flow collector 1 received: [0, 1, 2]

热流(Hot Flow)

即使没有人收集它们,它也会产生值。它可以有多个订阅者,并且可以存储数据。 // SharedFlow 示例

val sharedFlow = MutableSharedFlow<Int>()sharedFlow.emit(0)
sharedFlow.emit(1)
sharedFlow.emit(2)
sharedFlow.emit(3)
sharedFlow.emit(4)launch {sharedFlow.collect { value ->println("SharedFlow collector 1 received: $value")}delay(2500)// 第二次调用 collectsharedFlow.collect { value ->println("SharedFlow collector 2 received: $value")}
}

// 结果 // 收集器将从它们开始收集的地方获取值。 // 这里,第1个收集器获取了所有值。但第2个收集器只获取了在2500毫秒后发射的值,因为它在2500毫秒后开始收集。

SharedFlow collector 1 received: [0,1,2,3,4]
SharedFlow collector 2 received: [2,3,4]

我们可以使用 stateIn()shareIn() 操作符分别将任何冷流转换为热流。

SharedFlow & StateFlow

StateFlow — StateFlow 是一个热流,代表一次只持有一个值的状态。它也是一个合流,意味着当新值被发射时,最近值被保留并立即发射给新的收集器。当你需要为状态维护一个单一的真实来源并自动用最新状态更新所有收集器时,它很有用。它始终有一个初始值,并且只存储最近发射的值。

class HomeViewModel : ViewModel() {private val _textStateFlow = MutableStateFlow("Hello World")val stateFlow =_textStateFlow.asStateFlow()fun triggerStateFlow(){_textStateFlow.value="State flow"}
}// 在 Activity/Fragment 中收集 StateFlow
class HomeFragment : Fragment() {private val viewModel: HomeViewModel by viewModels()override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)lifecycleScope.launchWhenStarted {// 触发流并开始监听值// collectLatest() 是 Kotlin 的 Flow API 中的一个高阶函数// 它允许你从流中收集发射的值,并仅对最近值执行转换。它类似于// collect(), 后者用于收集所有发射的值,但 collectLatest 只处理最新发射的值并// 忽略尚未处理的任何先前值。viewModel.stateFlow.collectLatest {binding.stateFlowButton.text = it}}
}// 在 Compose 中收集 StateFlow
@Compose
fun HomeScreen() {// Compose 提供了 collectAsStateWithLifecycle 函数,它// 从流中收集值,并提供最新值以供使用// 需要的地方。当新的流值被发射时,我们得到更新的// 值,并且重新组合以更新值的状态。// 它默认使用 LifeCycle.State.Started 在生命周期处于指定状态时开始收集值,并在其下降时停止。val someFlow by viewModel.flow.collectAsStateWithLifecycle()}

SharedFlow — SharedFlow 是一个热流,可以有多个收集器。它可以独立于收集器发射值,多个收集器可以从流中收集相同的值。当你需要向多个收集器广播值或当你想要有多个订阅者订阅相同的数据流时,它很有用。它没有初始值,你可以配置其重放缓存来为新收集器存储一定数量的先前发射的值。

class HomeViewModel : ViewModel() {private val _events = MutableSharedFlow<Event>() // 私有可变共享流val events = _events.asSharedFlow() // 公开暴露为只读共享流suspend fun produceEvent(event: Event) {_events.emit(event) // 直到所有订阅者接收到它才挂起}
}// 在 Activity/Fragment 中收集 StateFlow
class HomeFragment : Fragment() {private val viewModel: HomeViewModel by viewModels()override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)lifecycleScope.launchWhenStarted {// 触发流并开始监听值// collectLatest() 是 Kotlin 的 Flow API 中的一个高阶函数// 它允许你从流中收集发射的值,并仅对最近值执行转换。它类似于// collect(), 后者用于收集所有发射的值,但 collectLatest 只处理最新发射的值并// 忽略尚未处理的任何先前值。viewModel.events.collectLatest {binding.eventFlowButton.text = it}}
}// 在 Compose 中收集 StateFlow
@Compose
fun HomeScreen() {// Compose 提供了 collectAsStateWithLifecycle 函数,它// 从流中收集值,并提供最新值以供使用// 需要的地方。当新的流值被发射时,我们得到更新的// 值,并且重新组合以更新值的状态。// 它默认使用 LifeCycle.State.Started 在生命周期处于指定状态时开始收集值,并在其下降时停止。val someFlow by viewModel.events.collectAsStateWithLifecycle()}

流中的异常处理

Kotlin 流提供了几种处理异常和错误的机制。

try-catch 块 — 处理异常的基本方法之一是在流中使用 try-catch 块。

flow {try {emit(productsService.fetchProducts())} catch (e: Exception) {emitError(e)}
}

catch 操作符 — Flow 中的 catch 操作符允许你通过将错误处理逻辑封装在一个地方来处理异常。

flow {emit(productsService.fetchProducts())
}.catch { e ->emitError(e)
}

onCompletion 操作符 — 用于在流完成后执行代码,无论是成功完成还是异常完成。

flow {emit(productsService.fetchProducts())
}.onCompletion { cause ->if (cause != null) {emitError(cause)}
}

自定义错误处理 — 在 Android 的复杂场景中,我们可以创建自定义操作符或扩展函数,以适合我们应用程序的方式处理错误。

fun <T> Flow<T>.sampleErrorHandler(): Flow<Result<T>> = transform { value ->try {emit(Result.Success(value))} catch (e: Exception) {emit(Result.Error(e))}
}

Flow vs LiveData

LiveData 是生命周期感知的,这意味着它自动管理观察者的生命周期,确保仅在观察者处于活动状态时才传递更新。而流默认不是生命周期感知的。我们可以使用 Compose 中的 collectLatest() 或 collectAsStateWithLifecycle() 函数来从流中收集结果。

流提供更多的灵活性,适合更复杂的异步数据操作,而 LiveData 通常用于更简单的 UI 更新。

流提供内置的背压支持,允许控制数据发射和处理的速率,而 LiveData 不支持背压处理。

流提供丰富的操作符集合,用于顺序和结构化处理,而 LiveData 专注于将最新数据传递给观察者。

感谢阅读!如果你学到了新东西,请关注我获取更多

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

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

相关文章

C++内存管理(候捷)第四讲 笔记

上中下三个classes分析 Loki allocator的三个类&#xff0c;从低阶到高阶分别为&#xff1a;Chunk, FixedAllocator, SmallObjAllocator Chunk&#xff1a;pData指针&#xff0c;指向分配的一个chunk&#xff0c;firstAvailableBlock_索引&#xff0c;指向第一个可用区块是第几…

数据结构day3(双向链表操作)

链式存储&#xff1a; 双向链表 线性表的链式存储&#xff1a;解决了顺序存储的缺点&#xff0c;插入和删除。动态存储问题。 数据域 指针 ---> 节点 注意&#xff1a;用自己结构的指针一般就是 链表。 DoubleLInk.h文件 #ifndef DOULINK_H #define DOULINK_H…

自动导入unplugin-auto-import+unplugin-vue-components

文章介绍 接下来将会以Vite Vue3 TS的项目来举例实现 在我们进行项目开发时&#xff0c;无论是声明响应式数据使用的ref、reactive&#xff0c;或是各种生命周期&#xff0c;又或是computed、watch、watchEffect、provide-inject。这些都需要前置引入才能使用&#xff1a; …

基于PSO粒子群优化的GroupCNN分组卷积网络时间序列预测算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 粒子群优化算法&#xff08;PSO&#xff09; 4.2 分组卷积神经网络&#xff08;GroupCNN&#xff09; 4.3 PSO优化GroupCNN 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行…

【已解决】Python ValueError: math domain error 详解

【已解决】Python ValueError: math domain error 详解 在Python编程中&#xff0c;遇到ValueError: math domain error是一个相对常见的问题。此错误通常表明传递给数学函数的参数超出了其定义域。本文将深入探讨此错误的根源、解决思路、具体解决方法、常见场景分析以及扩展…

【在Linux世界中追寻伟大的One Piece】Linux进程概念

目录 1 -> 冯诺依曼体系结构 2 -> 操作系统(operator System) 2.1 -> 概念 2.2 -> 系统调用和库函数 3 -> 进程 3.1 -> 概念 3.2 -> 进程-PCB 3.3 -> 进程状态 3.3.1 -> Z(Zombie)-僵尸进程 3.3.2 -> 孤儿进程 3.4 -> 进程优先级 …

Linux下如何安装配置Fail2ban防护工具

Fail2ban是一款在Linux服务器上用于保护系统免受恶意攻击的防护工具。它通过监视系统日志&#xff0c;检测到多次失败的登录尝试或其他恶意行为后&#xff0c;会自动将攻击源的IP地址加入防火墙的黑名单&#xff0c;从而阻止攻击者进一步访问服务器。本文将介绍如何在Linux系统…

Animate.css的使用

一、安装 npm install animate.css --save二、引入 import animate.css;三、使用 <h1class"animate__animated animate__bounce"mouseenter"mouseenter"mouseleave"mouseleave">An animated element</h1>//在js中的方法 function …

五. TensorRT API的基本使用-TensorRT-network-structure

目录 前言0. 简述1. 案例运行2. 代码分析2.1 main.cpp2.2 model.cpp 总结下载链接参考 前言 自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考 本次课程我们来学习课程第五章—TensorRT API 的基本使用&#x…

Redisson分布式锁使用详解

引言 日常开发中&#xff0c;难免遇到一些并发的场景&#xff0c;为了保证接口执行的一致性&#xff0c;通常采用加锁的方式&#xff0c;因为服务是分布式部署模式&#xff0c;本地锁Reentrantlock和Synchnorized这些就先放到一边了&#xff0c;Redis的setnx锁存在无法抱保证原…

九-2、Rocky Linux软件包管理与安装 学习笔记

1. DNF的rocky linux管理 DNF: Dandified YUM,基于RPM的Linux软件包管理器&#xff0c;是YUM的下一代版本。 Dnf命令和yum命令兼容&#xff0c;依然使用yum仓库。 dnf module在软件安装上更方便&#xff0c;可以通过dnf module install 在安装软件时指定安装的版本&#xff0…

QT开发(QT的基本概述和环境的安装)

QT的概述 一.QT的介绍背景1.1 什么是QT1.2QT的发展史1.3 Qt支持的平台1.4QT版本1.5QT的优点1.6QT的应用场景 二.搭建QT开发环境2.1 QT的开发工具的下载2.2 QT环境变量配置 三.QT的三种基类四.QT Hello World程序4.1使用按钮实现4.1.1 代码方式实现4.1.2 可视化操作实现 一.QT的…

【C#】在一个给定的宽、高范围内,获取到该多边形内部的所有坐标集合?

问题点 使用C#语言在一个给定的宽、高范围内&#xff0c;获取到该多边形内部的所有坐标集合&#xff1f; 这个多边形可能存在交叉及互相重叠部分 图像的宽、高可以定义为&#xff1a;2000*2000 多边形坐标集合&#xff1a;Point[] polygon_points new Point[] { new Point…

如何在vscode中对在服务器上多卡运行的bash脚本进行debug?

问题描述 使用vscode可以很方便地添加断点&#xff0c;进行代码调试。 在使用服务器时&#xff0c;我们的python代码通常是通过bash脚本来执行的&#xff0c;那么如何进行debug呢&#xff1f; 待运行的bash 脚本示例 前半段定义了一些参数&#xff0c;后半段是执行python代码…

数据结构的概念和术语

目录 一.前言 二.数据结构的基本概念 三.数据结构的术语 一.前言 数据结构是一门研究非数值计算的程序设计中计算机的操作对象以及它们之间的关系和操作的学科。数据结构的基本数据结构包括两部分&#xff0c;线性结构跟非线性结构。 二.数据结构的基本概念 数据结构主要包括…

压测实操--kafka broker压测方案

作者&#xff1a;九月 环境信息&#xff1a; 操作系统centos7.9&#xff0c;kafka版本为hdp集群中的2.0版本。 kafka broker参数 num.replica.fetchers&#xff1a;副本抓取的相应参数&#xff0c;如果发生ISR频繁进出的情况或follower无法追上leader的情况则适当增加该值&…

CTF ssrf 基础入门

0x01 引言 我发现我其实并不是很明白这个东西&#xff0c;有些微妙&#xff0c;而且记忆中也就记得Gopherus这个工具了&#xff0c;所以重新学习了一下&#xff0c;顺便记录一下吧 0x02 辨别 我们拿到一个题目&#xff0c;他的名字可能就是题目类型&#xff0c;但是也有可能…

【使用 Pytest 记录日志文件并确保测试用例正常执行】

1. 更新测试脚本 首先&#xff0c;确保你的测试脚本 wifi_test.py 配置了日志记录&#xff0c;并包含所有测试用例&#xff1a; import subprocess import time import logging import pytest import sys# 配置日志记录 logging.basicConfig(filenamewifi_test.log, levellog…

vue侦听器(Watch)精彩案例剖析一

目录 watch介绍 监视普通数据类型 监视对象类型 watch介绍 在 Vue 中,watch主要用于监视数据的变化,并执行相应操作。一旦被监视的属性发生变化,回调函数将自动被触发。当在 Vue 中使用watch来响应数据变化时,首先要清楚,watch本质上是一个对象,且必须以对象的…

HDShredder 7 企业版案例分享: 依照国际权威标准,安全清除企业数据

HDShredder 7 企业版用户案例 天津鸿萌科贸发展有限公司是德国 Miray 公司 HDShredder 数据清除软件的授权代理商。近日&#xff0c;上海某网络科技有限公司采购 HDShredder 7 企业版x4&#xff0c;为公司数据存储资产的安全清除工作流程配备高效的执行工具。HDShredder 7 企业…