拥抱 Kotlin Flow

1. 引言

Kotlin Flow 是 Kotlin 协程生态中处理异步数据流的核心工具,它提供了一种声明式、轻量级且与协程深度集成的响应式编程模型。与传统的 RxJava 相比,Flow 更简洁、更易于维护,尤其在 Android 开发中已成为主流选择。本文将从基础概念到高级特性全面解析 Flow,结合实战案例帮助读者深入掌握这一强大工具。

2. Flow 基础概念

2.1 冷流与热流

冷流(Cold Flow):只有在被收集(collect)时才会开始执行,每个收集器独立运行。例如:

// 创建冷流,使用flow构建器,只有在被收集时才会发射数据val coldFlow = flow {emit(1) // 收集时执行emit(2) // 收集时执行}

冷流特性

按需触发:类似「点播电影」,只有订阅时才开始播放

独立副本:每个订阅者触发独立的数据流(如多次collect会重新执行flow块)

适用场景:网络请求、数据库查询等一次性任务

热流(Hot Flow):创建后立即开始发射数据,多个收集器共享数据流。例如 StateFlowSharedFlow

// 创建热流(SharedFlow),创建后立即开始发射数据val hotFlow = MutableSharedFlow<Int>()//启动协程持续发射数据(即使没有订阅者)launch {(0..2).forEach {delay(1000)hotFlow.emit(it) // 发射0,1,2(间隔1秒)}}

热流特性

主动发射:类似「直播电视」,数据持续生产

共享数据源:多个订阅者共享最新数据

适用场景:实时状态同步(如 UI 更新)、全局事件通知

冷热流对比表

特性冷流热流
数据发射时机订阅时触发创建后持续发射
订阅者关系独立执行共享数据流
典型实现flow{}asFlow()StateFlowSharedFlow
适用场景单次任务(网络请求)实时数据(传感器、WebSocket)

2.2 核心组件

生产者:通过 emit 发送数据。

操作符:对流进行转换、过滤、合并等处理。

消费者:通过 collect 等终端操作符处理数据。

数据流模型

//生产者flow {emit("数据1") // 发送数据emit("数据2")}//操作符链.map { it.toUpperCase() } // 转换操作符.filter { it.contains("2") } // 过滤操作符//消费者.collect { println(it) } // 终端操作符

3. Flow 的创建与消费

3.1 创建方式

flowOf:快速创建固定数据的流。

//创建包含1、2、3的流,底层使用channel实现val flow = flowOf(1, 2, 3)

asFlow:将集合转换为流。

//将列表转换为流,支持惰性处理val list = listOf(1, 2, 3)val flow = list.asFlow()

callbackFlow:处理回调式异步操作。

//创建callbackFlow处理网络回调val callbackFlow = callbackFlow<String> {val listener = object : Listener {override fun onData(data: String) {trySend(data) // 安全发射数据}}registerListener(listener) // 注册监听awaitClose { unregisterListener(listener) } // 关闭时取消监听}

3.2 消费方式

collect:收集所有数据。

//在协程中收集数据,每个值触发打印flow.collect { value -> println(value) }

first:获取第一个元素。

//获取第一个元素,流为空时抛出异常val firstValue = flow.first()

4. 核心操作符详解

4.1 转换操作符

map:转换元素类型。

//将整数流转换为字符串流flow.map { it.toString() }

transform:自定义转换逻辑,可发射多个值。

//对每个元素发射两次(乘2和加1)flow.transform {emit(it * 2)emit(it + 1)}

4.2 过滤操作符

filter:保留符合条件的元素。

//过滤偶数flow.filter { it % 2 == 0 }

distinctUntilChanged:去重连续重复元素。

//去除连续重复元素(如[1,1,2,2]→[1,2])flow.distinctUntilChanged()

4.3 组合操作符

zip:合并两个流。

//合并两个流,对应位置元素相加flow1.zip(flow2) { a, b -> a + b }

combine:合并多个流的最新值。

//合并温度和湿度流,计算舒适度指数combine(tempFlow, humidityFlow) { t, h ->&#x20;(t - 18) * 0.7 + (h - 60) * 0.3&#x20;}

4.4 背压处理操作符

buffer:缓存数据,避免生产者阻塞。

//设置缓冲区大小为10,溢出时挂起生产者flow.buffer(10, BufferOverflow.SUSPEND)

conflate:丢弃中间值,仅处理最新值。

//实时位置更新时跳过中间帧flow.conflate()

4.5 高级操作符

flatMapLatest:处理最新流,取消未完成操作。

//搜索输入时取消旧请求searchFlow.flatMapLatest { query ->searchApi.fetch(query) // 新请求到达时取消旧请求}

debounce:防抖处理(如搜索框输入)。

//输入停止1秒后触发搜索searchFlow.debounce(1000)

5. 背压管理

5.1 背压概念

当生产者速度超过消费者时,需通过背压策略处理数据积压。Flow 提供以下策略:

buffer:使用缓冲区存储数据。

conflate:丢弃旧值,保留最新值。

collectLatest:取消未完成的操作,处理最新值。

5.2 示例

//生产者每秒发射1个数据flow {(1..5).forEach {emit(it)delay(100) // 生产间隔100ms}}.collect {delay(200) // 消费间隔200ms,导致背压println(it)}//使用buffer优化,添加缓冲区缓解背压flow.buffer().collect { ... }

6. 错误处理

6.1 异常捕获

try-catch:在收集时捕获异常。

//收集时捕获异常try {flow.collect()} catch (e: Exception) {// 处理异常}

catch:在流中处理异常。

//流中出现异常时发射默认值flow.catch { e ->emit(-1) // 发生异常时发射默认值}.collect()

6.2 资源清理

onCompletion:流完成或取消时执行清理。

//流结束时释放资源flow.onCompletion { cause ->if (cause != null) {// 异常处理}cleanup() // 释放资源}.collect()

7. 冷热流转换

7.1 StateFlow

特点:持有当前状态,新订阅者可获取最新值。

使用示例

//创建StateFlow管理UI状态val uiState = MutableStateFlow<UIState>(Loading)//更新状态uiState.value = Success(data)

7.2 SharedFlow

特点:支持多订阅者,可配置缓存和重放。

使用示例

//创建SharedFlow发射事件val eventFlow = MutableSharedFlow<Event>()//发射事件eventFlow.emit(Event.Click)

7.3 stateInshareIn

stateIn:将冷流转为 StateFlow

//冷流转为StateFlow,初始值为0val stateFlow = flow.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),initialValue = 0)

shareIn:将冷流转为 SharedFlow

//冷流转为SharedFlow,重放1个数据val sharedFlow = flow.shareIn(scope = viewModelScope,started = SharingStarted.Eagerly,replay = 1)

8. 高级主题

8.1 协程上下文切换

flowOn:切换流的执行上下文。

//切换到IO线程执行耗时操作flow.flowOn(Dispatchers.IO)

8.2 取消与资源管理

取消流:通过协程取消。

//启动收集协程val job = launch {flow.collect()}//取消协程job.cancel()

8.3 与 Room 集成

示例:将 Room 查询结果转为流。

//Room Dao接口@Daointerface UserDao {@Query("SELECT * FROM users")fun getUsers(): Flow<List<User>> // 返回Flow}

9. 性能优化与最佳实践

9.1 避免阻塞操作

正确做法:使用 withContext 切换线程。

//在map操作中切换到IO线程flow.map {withContext(Dispatchers.IO) {// 耗时操作}}

9.2 合理使用背压策略

场景选择

buffer:适合生产者与消费者速度波动较大的场景。

conflate:适合只关心最新值的场景(如实时数据)。

9.3 测试与调试

工具推荐

Turbine:用于测试 Flow 的输出和错误。

testCoroutineDispatcher:控制协程执行。

10. Flow 与 RxJava 对比

特性FlowRxJava
协程集成原生支持需通过 RxKotlin 集成
背压自动处理需显式处理
线程管理flowOn 简洁subscribeOn/observeOn
内存管理轻量级,无额外开销可能存在内存泄漏风险

11. 实战案例

11.1 网络请求与数据缓存

//定义网络请求Flowfun fetchData(): Flow<List<Data>> = flow {val data = api.getData() // 网络请求emit(data)}.flowOn(Dispatchers.IO)//结合Room缓存val cachedDataFlow = fetchData().catch { e ->emit(roomDao.getData()) // 异常时读取缓存}.onEach { data ->roomDao.saveData(data) // 保存缓存}

11.2 实时数据更新

//ViewModel管理UI状态class MyViewModel : ViewModel() {private val _uiState = MutableStateFlow<UIState>(Loading)val uiState: StateFlow<UIState> = _uiStateinit {viewModelScope.launch {_uiState.value = Success(fetchData()) // 更新状态}}}

12. 总结

Kotlin Flow 凭借其简洁的 API、与协程的深度集成以及强大的背压处理能力,成为现代异步编程的首选工具。本文从基础概念到高级特性全面解析了 Flow,并通过实战案例展示了其在 Android 开发中的应用。合理使用 Flow 可以显著提升代码的可读性和可维护性,尤其在处理复杂数据流场景时更具优势。

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

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

相关文章

精益数据分析(34/126):深挖电商运营关键要点与指标

精益数据分析&#xff08;34/126&#xff09;&#xff1a;深挖电商运营关键要点与指标 在创业和数据分析的学习之旅中&#xff0c;我们都在不断探寻如何让业务更上一层楼。今天&#xff0c;我依旧带着和大家共同进步的想法&#xff0c;深入解读《精益数据分析》中电商运营的关…

Learning vtkjs之ImageCropFilter

过滤器 图片数据体积裁剪 介绍 vtkImageCropFilter可以裁剪vtkImageData。这只适用于IJK对齐的平面。 请注意&#xff0c;由于CPU限制的裁剪&#xff0c;这在大型数据集上会很慢。 效果 核心代码 需要实现这个代码主要逻辑 1、设定的crop的包围盒 其实主要是IMax IMin JM…

深入理解 C++11 delete 关键字:禁用函数的艺术

一、什么是 delete 关键字 C11 引入的 delete 关键字是一种​​显式禁用函数​​的语法机制。它允许开发者主动阻止特定函数的使用&#xff0c;比传统的私有化声明更直观、更安全&#xff0c;且能在编译期捕获更多潜在错误。 二、为什么需要 delete&#xff1f; 1. 传统方式…

深度剖析!GPT-image-1 API 开放对 AI 绘画技术生态的冲击!

4月24日凌晨&#xff0c;OpenAI正式发布了全新的图像生成模型“gpt-image-1”&#xff0c;并通过API向全球开发者开放使用&#xff0c;这意味着其GPT-4o的图像生成能力正式向开发者开放&#xff01; 在这之前&#xff0c;GPT-4o的图像生成功能于今年3月25日由 OpenAI 创始人兼 …

扣子流程图批量导入飞书多维表格

文章目录 整体结构分步骤进行处理1. 程序代码处理2. 多维表格配置 整体结构 整个代码块结构如下&#xff1a; 首先&#xff0c;我们从其他流程中拿到一个数据列表&#xff0c;通过一个循环体&#xff0c;将每一个部分的内容都通过python代码整理后&#xff0c;使用【插件】的…

【安全扫描器原理】端口扫描

【安全扫描器原理】端口扫描 1.端口扫描基本原理2.TCP扫描3.UDP扫描4.手工扫描1.端口扫描基本原理 以TCP端口为例,其原理是当一个主机向远端一个服务器的某一个端口提出建立连接的请求,如果对方有此项服务,就会同意建立连接,如果对方未安装此项服务时,则不会同意建立连接…

FastGPT部署的一些问题整理

在B站学习 图灵程序员-诸葛 的LangChain快速入门课程之《部署FastGPT构建本地应用》。在我学习课程跟着老师实践的过程中&#xff0c;踩了一些坑。这篇文章以问答的形式记录一下学习中的一些问题&#xff0c;主要面向的读者是&#xff0c;在学习同样的课程的和部署FastGPT遇到各…

如何查看k8s获取系统是否清理过docker镜像

k8s集群某个节点down掉后&#xff0c;pod就会漂移到其他节点&#xff0c;但是在该节点却又执行了拉取镜像操作&#xff0c;明明该节点之前部署过该容器的&#xff0c;不知为什么又拉取了一次镜像&#xff08;镜像拉取配置的优先使用本地&#xff09;&#xff0c;所以怀疑是触发…

聚焦智能体未来,领驭科技在微软创想未来峰会大放异彩

2025年4月23日&#xff0c;微软创想未来峰会在北京中关村国际创新中心盛大举行。作为微软中国南区核心合作伙伴及HKCSP 1T首批授权云服务商&#xff0c;深圳领驭科技有限公司受邀参会&#xff0c;携瀚鹏工业AI应用解决方案亮相峰会&#xff0c;与全球AI领袖及行业精英共话智能体…

元宇宙2.0:当区块链成为数字世界的宪法

引言&#xff1a;当虚拟世界成为“新大陆” 清晨&#xff0c;你戴上VR设备进入一个由数字建筑构成的城市&#xff0c;这里的地皮属于全球玩家&#xff0c;街边的艺术品标着NFT认证码&#xff0c;咖啡馆里的人们用加密货币支付咖啡&#xff0c;而社区规则由持有代币的居民投票决…

力扣hot100——239.滑动窗口最大值

题目链接&#xff1a; 239. 滑动窗口最大值 - 力扣&#xff08;LeetCode&#xff09; 优先级队列 优先级队列自动按照大小排序&#xff0c;队首即为最大元素&#xff0c;但取队首时要注意元素是否在滑动窗口内&#xff0c;如果不在则弹出。 class Solution { public:vector&…

Alibaba国际站商品详情AP接口概述,json数据示例返回参考

前言 Alibaba国际站商品详情API&#xff08;通常称为item_get接口&#xff09;是阿里巴巴开放平台提供的一项核心服务&#xff0c;允许开发者通过商品ID获取商品的详细信息。该接口广泛应用于电商系统集成、数据分析、竞品监控等场景&#xff0c;支持企业自动化获取商品标题、…

[论文阅读]Adversarial Semantic Collisions

Adversarial Semantic Collisions Adversarial Semantic Collisions - ACL Anthology Proceedings of the 2020 Conference on Empirical Methods in Natural Language Processing (EMNLP) 对抗样本是相似的输入但是产生不同的模型输出&#xff0c;而语义冲突是对抗样本的逆…

25【干货】在Arcgis中根据字段属性重新排序并自动编号的方法(二)

上一篇关于属性表自动编号的文章因为涉及到代码&#xff08;【干货】在Arcgis中根据字段属性重新排序并自动编号的方法&#xff08;一&#xff09;&#xff09;&#xff0c;担心大家有些东西确实不熟悉&#xff0c;今天就更新一篇不需要代码也能达到这个目的的方法。主要的思路…

从后端研发角度出发,使用k8s部署业务系统

k8s&#xff0c;作为目前最流行的容器编排中间件&#xff0c;大家应该都听说过&#xff0c;很多公司也都在用&#xff0c;但基本都是运维在管理k8s&#xff0c;开发人员一般涉及不到&#xff0c;开发人员只需要写业务代码&#xff0c;然后运维人员负责制作镜像&#xff0c;然后…

Vue3 Echarts 3D圆柱体柱状图实现教程以及封装一个可复用的组件

文章目录 前言一、实现原理二、series ——type: "pictorialBar" 简介2.1 常用属性 三、代码实战3.1 封装一个echarts通用组件 echarts.vue3.2 首先实现一个基础柱状图3.3 添加上下2个椭圆面3.4 进阶封装一个可复用的3D圆形柱状图组件 总结 前言 在前端开发的数据可视…

WPF 上位机开发模板

WPF 上位机开发模板 WPF上位机开发模板,集成了基础操作菜单、海康视觉实时图像界面、串口通讯、网口通讯、主流PLC通讯、数据存储、图片存储、参数配置、权限管理、第三方webapi接口接入、数据追溯与查询等功能。 一、项目结构 WpfSupervisor/ ├── Models/ …

浏览器插件,提示:此扩展程序未遵循 Chrome 扩展程序的最佳实践,因此已无法再使用

1、发现的问题如下&#xff1a; 如果你是比较新的 Chrome 135.0.7049.42&#xff08;含&#xff09;以上版本的话&#xff0c;可以通过修改 chorme://flags 来彻底解决。 2、在浏览器分别输入两个地址&#xff1a; chrome://flags/#extension-manifest-v2-deprecation-disable…

【原创】从s3桶将对象导入ES建立索引,以便快速查找文件

总体功能&#xff1a; 这段程序的作用是&#xff1a; 从指定的S3桶中读取所有对象的元数据&#xff08;文件名、大小、最后修改时间、存储类型、ETag等&#xff09;&#xff0c;并把这些信息写入到Elasticsearch&#xff08;ES&#xff09;中&#xff0c;建立索引&#xff0c…

git 查看用户信息

在 Git 中查看用户信息是一项常见的任务&#xff0c;可以帮助你确认当前仓库的配置或全局的 Git 配置是否正确设置。你可以通过多种方式来查看这些信息。 查看全局用户信息 全局用户信息是应用于所有 Git 仓库的默认设置。要查看全局用户信息&#xff0c;可以使用以下命令&am…