目录

    • 前言
    • 基础
      • 基本用法
      • 概念与核心特点
      • Android中使用
      • 与LiveData对比
      • 热流StateFlow、SharedFlow
    • 搜索输入流实现实时搜索

前言

​ Flow是kotlin协程库中的一个重要组成部分,它可以按顺序发送多个值,用于对异步数据流进行处理。所谓异步数据流就是连续的异步事件,如连续的网络请求、查询数据库等。

​ 了解LiveData的同学可能知道,使用LiveData也可以大体实现这样的处理连续的异步事件的效果,比如说每发一次网络请求执行一次postValue()方法,那么我们就能通过Observer来监听到这个值的改变,接着去进行相应的操作,但与此同时这样会遇到许多相应的问题,这里先按下不表,后面会介绍为什么使用Flow而不使用LiveData来对连续的异步事件进行处理。

​ 同时为了方便大家理解啥场景用Flow,下面会另外实现一个搜索输入流来实现实时搜索的Demo。

基础

基本用法

// 生产者:发送数据
val numbersFlow = flow {emit(1)delay(1000)emit(2)delay(1000)emit(3)
}// 收集者:接收数据
lifecycleScope.launch {numbersFlow.collect { value ->Log.d("Flow", "收到:$value")}
}

打印:

收到:1
收到:2
收到:3

这是一个最简单的Flow例子,可以看到使用生产者使用emit来发送数据,接着接收者使用collect来接收数据

概念与核心特点

上面讲了这些,好像还没具体说下Flow是个啥。

Flow是一种可以异步地发送多个值的冷流。所谓冷流就是说Flow是冷的,只有收集的时候(collect时)才会开始执行,也就是调用collect之后Flow再来生产数据再发送数据。与之对应的还有热流,典型的就是StateFlow,它不管有没有收集者,数据都会产生,下面再具体介绍。

Flow的核心特点:

特征描述
冷流Flow是冷的,直到被收集的时候(collect时)才会开始执行,有助于节省资源
支持协程Flow是基于协程构建的,支持协程,能在emit和collect中调用挂起函数
支持自动取消Flow可以结合协程作用域实现自动取消
Flow的收集过程是挂起的,需要运行在一个协程中。只要这个协程被取消了,Flow的收集就会自动终止
背压处理背压控制:消费者来不及处理生产者发送的数据时的处理机制
内置背压控制,不会造成UI卡顿或内存泄露
链式操作可以进行链式操作(map、filter等)

上面说到Flow支持链式操作,介绍一下Flow的常用的操作符map、filter的用法,其他感兴趣的可自行Chatgpt:

fun processedFlow(): Flow<String> = flow {for (i in 1..5) {delay(500)emit(i)}
}.map { // 中间操作:转换为字符串"Item $it"
}.filter {// 中间操作:过滤偶数it.last().toString().toInt() % 2 != 0
}

收集到的结果:

Item 1
Item 3
Item 5

Android中使用

这里拿一个网络请求的Demo来举例,在ViewModel进行网络请求,在Activity中对请求结果进行收集:

// ViewModel
fun fetchUserData(): Flow<String> = flow {delay(2000)// 模拟网络请求获取数据emit("User Data")delay(1500)// 模拟更新数据emit("Updated Data")
}.flowOn(Dispatchers.IO) // 指定在IO线程执行// Activity
lifecycleScope.launchWhenStarted {viewModel.fetchUserData().onEach { data ->println(data)}.catch { e ->// 异常处理Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_SHORT).show()}.collect()
}

打印:

User Data
Updated Data

与LiveData对比

上面的在Android中的使用Flow来对网络请求结果收集如果用LiveData来写的话:

  • 不使用LiveData{}
// ViewModel
private val _userData = MutableLiveData<String>()
val userData: LiveData<String> = _userDatafun fetchUserDataManual() {viewModelScope.launch {delay(2000)_userData.postValue("User Data")delay(1500)_userData.postValue("Updated Data")}
}// Activity
viewModel.userData.observe(this) { data ->println(data)
}
  • 使用LiveData{}
// ViewModel
fun fetchUserData(): LiveData<String> = liveData(Dispatchers.IO) {try {delay(2000)emit("User Data")delay(1500)emit("Updated Data")} catch (e: Exception) {emit("Error: ${e.message}")}
}// Activity
viewModel.fetchUserData().observe(this) { data ->println(data)
}

可以发现好像使用LiveData也可以实现连续发送网络请求接着进行处理,但使用LiveData时会存在以下问题与局限性:

  • 感知生命周期但不支持挂起函数:无法在LiveData中使用suspend关键字

因为LiveData是基于观察者模式和生命周期感知构建的,不是基于suspend的挂起函数的异步机制,所以LiveData中不支持挂起函数调用,可以使用LiveData{}构建器来让LiveData支持调用挂起函数:

fun fetchData(): LiveData<String> = liveData(Dispatchers.IO) {val result = fetchFromNetwork() // 可以调用suspend函数emit(result)
}
  • 不支持链式操作

Flow链式操作:

textFlow.debounce(500)// 停止输入500ms才触发一次.map { it.trim() }// 去掉空格.distinctUntilChanged()// 只有当输入发生变化时才会接着往下执行.onEach { vm.updateKeyword(it) }// 更新.launchIn(lifecycleScope)// 启动Flow

可以看到将一连串的处理像链条一样串联起来,LiveData并没有这些链式操作符。

  • 缺乏背压控制:无法响应高频数据流(如搜索输入流)
  • 只能有限支持异步数据流
  • 组合操作符少:没有map、filter等操作符
  • 逻辑冗余:要手动处理线程、去重、防抖、错误捕获等问题

使用建议:

  • 如果只需要简单的、生命周期安全的 UI 数据绑定,LiveData 是轻量选择。
  • 如果需要复杂的异步流、响应式变换、防抖、取消处理等,Flow 更合适。

热流StateFlow、SharedFlow

常见的热流有StateFlow和SharedFlow。(LiveData也是一个热流)

知道了冷流是只有接收者接受数据时,发送者才会去产生数据再发送数据给接收者。并且每个接收者都会触发完整的数据流从头开始接收完整的数据源。

而热流就是不管有没有接受者来接收数据,发送者都会生产数据,多个接受者时共享同一份数据源的,同时接受者并不会接收完整的数据源,发送者数据生产到哪了接受者就接收到哪的数据。

说的通俗一点就是:

  • 冷流就像刷视频,我们开始刷这个视频这个视频才会开始播放,并且是从头开始播放
  • 热流就像直播,我们不看直播这个直播也在播放,点进直播间观看也并不是从头开始看,而是只能从当前的内容开始看

冷流Demo:

//每次collect都会重新发射数据 
val coldFlow = flow { println("开始生产数据")emit(1) emit(2) 
} // 观察者1
coldFlow.collect { println("观察者1: $it") } // 输出:1,2 
// 观察者2
coldFlow.collect { println("观察者2: $it") } // 再次输出:1,2

适用场景:

  • 网络请求、数据库查询等需要独立数据源的场景
  • 每个订阅者需要从头消费完整的数据

热流Demo:

// 创建热流(SharedFlow) 
val hotFlow = MutableSharedFlow<Int>()// 启动协程持续发射数据(即使没有订阅者)
CoroutineScope(Dispatchers.Default).launch { repeat(4) { delay(1000) // 发射 0 1 2 3 4hotFlow.emit(it)} 
}// 观察者1(延迟1秒订阅) 
CoroutineScope(Dispatchers.Main).launch {delay(1000) hotFlow.collect { println("观察者1: $it") } // 只能收到 1,2,3,4
} // 观察者2(延迟5秒订阅) 
CoroutineScope(Dispatchers.Main).launch { delay(5000) hotFlow.collect { println("观察者2: $it") } // 收不到任何数据(发射已结束) 
}

适用场景:

  • 需要共享实时数据的场景(如IM消息、用户定位更新)
  • 数据生产是连续且独立的

总结:

冷流(Flow、asFlow)热流(StateFlow、SharedFlow)
数据产生发送时机接受者收集数据时(collect)直接产生数据,不管有没有接受者收集
数据独立性每个接受者收到的数据时独立的所有接受者共享数据
数据历史每个接受者从头开始获取完整数据只能获取订阅后产生的数据

搜索输入流实现实时搜索

场景:有一个搜索框,没有搜索图标我们无法手动点击进行搜索,而是对文本进行监听实现实时自动搜索。

在这使用的Flow是callbackFlow,介绍一下它与Flow的区别:

  • flow{}用于挂起函数式的顺序执行
  • callbackFlow{}用于将异步的、回调式的数据源封装成Flow

简单来说,callbackFlow是需要依赖异步回调拿数据的场景,没办法直接emit(),比如说监听文本变化这种。而不是像flow那样,发送完网络请求直接emit()即可。

所以需要对文本进行监听的话,需要使用callbackFlow将TextWatcher转成流。

		val et = vBinding.etSearchval textFlow = callbackFlow {val watcher = object : TextWatcher {override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}override fun afterTextChanged(s: Editable?) {val str = s?.toString() ?: ""// 将变化的字符串交给FlowtrySend(str)}}et.addTextChangedListener(watcher)awaitClose {// 当Flow被取消时,移除监听器避免内存泄露et.removeTextChangedListener(watcher)}}textFlow.debounce(500).map { it.trim() }.distinctUntilChanged().onEach {vm.updateKeyword(it)}.launchIn(lifecycleScope)

这里textFlow做的处理有:

  • 用户通知输入500ms时才做处理,减少频繁触发
  • distinctUntilChanged()表示输入内容不变时不触发搜索
  • 收集trySend发送的字符串来进行搜索
  • launchIn(lifecycleScope)在当前生命周期范围内启动协程收集Flow

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

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

相关文章

idea常做的配置改动和常用插件

IDEA 使用 最强教程&#xff0c;不多不杂。基于idea旗舰版 2019.2.3左右的版本&#xff0c;大多数是windows的&#xff0c;少数是mac版的 一、必改配置 1、ctrl滚轮 调整字体大小 全局立即生效&#xff1a;settings -> Editor -> General -> Change font size with …

3. 物理信息神经网络(PINNs)和偏微分方程(PDE),用物理定律约束神经网络

导言&#xff1a;超越时间&#xff0c;拥抱空间 在前两篇章中&#xff0c;我们已经走过了漫长而深刻的旅程。我们学会了用常微分方程&#xff08;ODE&#xff09;来描述事物如何随时间演化&#xff0c;从一个初始状态出发&#xff0c;描绘出一条独一无二的生命轨迹。我们还学会…

Flutter基础(基础概念和方法)

概念比喻StatefulWidget会变魔术的电视机State电视机的小脑袋&#xff08;记信息&#xff09;build 方法电视机变身显示新画面setState按遥控器按钮改变状态Scaffold电视机的外壳 StatefulWidget&#xff1a;创建一个按钮组件。State&#xff1a;保存点赞数&#xff08;比如 i…

K8s——Pod(1)

目录 基本概念 ‌一、Pod 的原理‌ ‌二、Pod 的特性‌ ‌三、Pod 的意义‌ 状态码详解 ‌一、Pod 核心状态详解‌ ‌二、其他关键状态标识‌ ‌三、状态码运维要点‌ 探针 ‌一、探针的核心原理‌ ‌二、三大探针的特性与作用‌ ‌参数详解‌ ‌三、探针的核心意义…

MySQL 存储过程面试基础知识总结

文章目录 MySQL 存储过程面试基础知识总结一、存储过程基础&#xff08;一&#xff09;概述1.优点2.缺点 &#xff08;二&#xff09;创建与调用1.创建存储过程2.调用存储过程3.查看存储过程4.修改存储过程5.存储过程权限管理 &#xff08;三&#xff09;参数1.输入参数2.输出参…

NLP文本数据增强

文章目录 文本数据增强同义词替换示例Python代码示例 随机插入示例Python代码示例 随机删除示例Python代码示例 回译&#xff08;Back Translation&#xff09;示例Python代码示例 文本生成模型应用方式示例Python代码示例 总结 文本数据增强 数据增强通过对原始数据进行变换、…

(LeetCode 每日一题) 594. 最长和谐子序列 (哈希表)

题目&#xff1a;594. 最长和谐子序列 思路&#xff1a;哈希表&#xff0c;时间复杂度0(n)。 用哈希表mp来记录每个元素值出现的次数&#xff0c;然后枚举所有值x&#xff0c;看其x1是否存在&#xff0c;存在的话就可以维护最长的子序列长度mx。 C版本&#xff1a; class Sol…

FreePDF:让看英文文献像喝水一样简单

前言 第一次看英文文献&#xff0c;遇到不少看不懂的英文单词&#xff0c;一个个查非常费劲。 后来&#xff0c;学会了使用划词翻译&#xff0c;整段整段翻译查看&#xff0c;极大提升看文献效率。 最近&#xff0c;想到了一种更快的看文献的方式&#xff0c;那就是把英文PD…

Scikit-learn:机器学习的「万能工具箱」

——三行代码构建AI模型的全栈指南** ### **一、诞生背景&#xff1a;让机器学习从实验室走向大众** **2010年前的AI困境**&#xff1a; - 学术界模型难以工程化 - 算法实现碎片化&#xff08;MATLAB/C主导&#xff09; - 企业应用门槛极高 > **破局者**&#xff1a;Da…

GPT-1论文阅读:Improving Language Understanding by Generative Pre-Training

这篇论文提出了 GPT (Generative Pre-Training) 模型&#xff0c;这是 GPT系列&#xff08;包括 GPT-2, GPT-3, ChatGPT, GPT-4 等&#xff09;的奠基之作。它标志着自然语言处理领域向大规模无监督预训练任务特定微调范式的重大转变&#xff0c;并取得了显著的成功。 文章链接…

Hadoop大数据-Mysql的数据同步工具Maxwell安装与使用( 详解)

目录 一、前置基础知识 1、主从复制&#xff08;Replication&#xff09; 2、数据恢复 3、数据库热备 4、读写分离 5、存储位置及命名 二、Maxwell简介 1、简介 2、Maxwell同步数据特点 2.1.历史记录同步 2.2.断点续传 三、前期准备 1、查看网卡&#xff1a; 2、…

分布式系统的一致性模型:核心算法与工程实践

目录 一、分布式一致性的核心挑战二、主流一致性算法原理剖析1. Paxos&#xff1a;理论基础奠基者2. Raft&#xff1a;工业级首选方案3. ZAB&#xff1a;ZooKeeper的引擎 三、算法实现与代码实战Paxos基础实现&#xff08;Python伪代码&#xff09;Raft日志复制核心逻辑 四、关…

Apache HTTP Server部署全攻略

httpd 简介 httpd&#xff08;Apache HTTP Server&#xff09;是一款历史悠久的开源 Web 服务器软件&#xff0c;由 Apache 软件基金会开发和维护。自 1995 年首次发布以来&#xff0c;Apache 一直是 Web 服务器领域的领导者&#xff0c;以其稳定性、安全性和灵活性著称。根据…

信号处理学习——文献精读与code复现之TFN——嵌入时频变换的可解释神经网络(下)

书接上文: 信号处理学习——文献精读与code复现之TFN——嵌入时频变换的可解释神经网络&#xff08;上&#xff09;-CSDN博客 接下来是重要的代码复现&#xff01;&#xff01;&#xff01;GitHub - ChenQian0618/TFN: this is the open code of paper entitled "TFN: A…

线上故障排查:签单合同提交报错分析-对接e签宝

在企业管理系统中&#xff0c;合同生成与签署环节至关重要&#xff0c;尤其是在使用第三方平台进行电子签署时。本文将通过实际的报错信息&#xff0c;分析如何进行线上故障排查&#xff0c;解决合同生成过程中出现的问题。 #### 1. 错误描述 在尝试生成合同并提交至电子签署…

知攻善防靶机 Linux easy溯源

知攻善防 【护网训练-Linux】应急响应靶场-Easy溯源 小张是个刚入门的程序猿&#xff0c;在公司开发产品的时候突然被叫去应急&#xff0c;小张心想"早知道简历上不写会应急了"&#xff0c;于是call了运维小王的电话&#xff0c;小王说"你面试的时候不是说会应急…

原神八分屏角色展示页面(纯前端html,学习交流)

原神八分屏角色展示页面 - 一个精美的前端交互项目 项目简介 这是一个基于原神游戏角色制作的八分屏展示页面&#xff0c;采用纯前端技术实现&#xff0c;包含了丰富的动画效果、音频交互和视觉设计。项目展示了一些热门原神角色&#xff0c;每个角色都有独立的介绍页面和专属…

华为认证二选一:物联网 VS 人工智能,你的赛道在哪里?

一篇不讲情怀只讲干货的科普指南 一、华为物联网 & 人工智能到底在搞什么&#xff1f; 华为物联网&#xff08;IoT&#xff09; 的核心是 “万物互联”。 通过传感器、通信技术&#xff08;如NB-IoT/5G&#xff09;、云计算平台&#xff08;如OceanConnect&#xff09;&…

CloudLens for PolarDB:解锁数据库性能优化与智能运维的终极指南

随着企业数据规模的爆炸式增长,数据库性能管理已成为技术团队的关键挑战。本文深入探讨如何利用CloudLens for PolarDB实现高级监控、智能诊断和自动化运维,帮助您构建一个自我修复、高效运行的数据库环境。 引言:数据库监控的演进 在云原生时代,传统的数据库监控方式已不…

MySQL中TINYINT/INT/BIGINT的典型应用场景及实例

以下是MySQL中TINYINT/INT/BIGINT的典型应用场景及实例说明&#xff1a; 一、TINYINT&#xff08;1字节&#xff09; 1.状态标识 -- 用户激活状态&#xff08;0未激活/1已激活&#xff09; ALTER TABLE users ADD is_active TINYINT(1) DEFAULT 0; 适用于布尔值存储和状态码…