【Android】Flow學習及使用

目錄

    • 前言
    • 基礎
      • 基本用法
      • 概念與核心特點
      • 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; 適用于布爾值存儲和狀態碼…