Kotlin 04Flow stateIn 和 shareIn的區別

一 Kotlin Flow 中的 stateIn 和 shareIn

一、簡單比喻理解

想象一個水龍頭(數據源)和幾個水杯(數據接收者):

  • 普通 Flow(冷流):每個水杯來接水時,都要重新打開水龍頭從頭放水
  • stateIn/shareIn(熱流):水龍頭一直開著,水存在一個水池里,任何水杯隨時來接都能拿到水

二、stateIn 是什么?

就像手機的狀態欄

  • 總是顯示最新的一條信息(有當前值
  • 新用戶打開手機時,立刻能看到最后一條消息
  • 適合用來表示"當前狀態",比如:
    • 用戶登錄狀態(已登錄/未登錄)
    • 頁面加載狀態(加載中/成功/失敗)
    • 實時更新的數據(如股票價格)

代碼示例:

// 創建一個永遠知道當前溫度的溫度計
val currentTemperature = sensorFlow.stateIn(scope = viewModelScope,  // 在ViewModel生命周期內有效started = SharingStarted.WhileSubscribed(5000), // 5秒無訂閱就暫停initialValue = 0 // 初始溫度0度)// 在Activity中讀取(總是能拿到當前溫度)
textView.text = "${currentTemperature.value}°C"

三、shareIn 是什么?

就像廣播電臺

  • 不保存"當前值"(沒有.value屬性)
  • 新聽眾打開收音機時,可以選擇:
    • 從最新的一條新聞開始聽(replay=1)
    • 只聽新新聞(replay=0)
  • 適合用來處理"事件",比如:
    • 顯示Toast提示
    • 頁面跳轉指令
    • 一次性通知

代碼示例:

// 創建一個消息廣播站
val messages = notificationFlow.shareIn(scope = viewModelScope,started = SharingStarted.Lazily, // 有人收聽時才啟動replay = 1 // 新聽眾能聽到最后1條消息)// 在Activity中收聽廣播
lifecycleScope.launch {messages.collect { msg ->Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()}
}

四、主要區別對比

特性stateIn (狀態欄)shareIn (廣播電臺)
有無當前值有(.value 直接訪問)無(必須通過collect接收)
新訂閱者立即獲得最新值可配置獲得最近N條(replay)
典型用途持續更新的狀態(如用戶積分)一次性事件(如"購買成功"提示)
內存占用始終保存最新值按需緩存(可配置)
是否熱流

五、為什么要用它們?

  1. 節省資源:避免重復計算(多個界面可以共享同一個數據源)

    • ? 不用時:每個界面都單獨請求一次網絡數據
    • ? 使用后:所有界面共享同一份網絡數據
  2. 保持一致性:所有訂閱者看到的數據完全相同

    • 比如用戶頭像更新后,所有界面立即同步
  3. 自動管理生命周期

    • 當Activity銷毀時自動停止收集
    • 當配置變更(如屏幕旋轉)時保持數據不丟失

六、生活場景類比

場景1:微信群(stateIn)

  • 群里最后一條消息就是當前狀態(.value)
  • 新成員進群立刻能看到最后一條消息
  • 適合:工作群的狀態同步

場景2:電臺廣播(shareIn)

  • 主播不斷發送新消息
  • 聽眾打開收音機時:
    • 可以設置是否聽之前的回放(replay)
    • 但無法直接問"剛才最后一首歌是什么"(無.value)
  • 適合:交通路況實時播報

七、什么時候用哪個?

用 stateIn 當:

  • 需要隨時知道"當前值"
  • 數據會持續變化且需要被多個地方使用
  • 例如:
    • 用戶登錄狀態
    • 購物車商品數量
    • 實時位置更新

用 shareIn 當:

  • 只關心新事件,不關心歷史值
  • 事件可能被多個接收者處理
  • 例如:
    • "訂單支付成功"通知
    • 錯誤提示消息
    • 頁面跳轉指令

八、超簡單選擇流程圖

要管理持續變化的狀態嗎?是 → 需要直接訪問當前值嗎?是 → 用 stateIn否 → 用 shareIn(replay=1)否 → 這是一次性事件嗎?是 → 用 shareIn(replay=0)

記住這個簡單的口訣:
“狀態用state,事件用share,想要回放加replay”

二 Kotlin Flow 的 shareInstateIn 操作符完全指南

在 Kotlin Flow 的使用中,shareInstateIn 是兩個關鍵的操作符,用于優化流的共享和狀態管理。本教程將深入解析這兩個操作符的使用場景、區別和最佳實踐。

一、核心概念解析

1. 冷流 vs 熱流

  • 冷流 (Cold Flow):每個收集者都會觸發獨立的執行(如普通的 flow{} 構建器)
  • 熱流 (Hot Flow):數據發射獨立于收集者存在(如 StateFlowSharedFlow

2. 為什么需要 shareIn/stateIn?

  • 避免對上游冷流進行重復計算
  • 多個收集者共享同一個數據源
  • 將冷流轉換為熱流以提高效率

二、stateIn 操作符詳解

基本用法

val sharedFlow: StateFlow<Int> = flow {// 模擬耗時操作emit(repository.fetchData())
}.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),initialValue = 0
)

參數說明:

  • scope:共享流的協程作用域(通常用 viewModelScope
  • started:共享啟動策略(后文詳細講解)
  • initialValue:必須提供的初始值

特點:

  1. 總是有當前值(通過 value 屬性訪問)
  2. 新收集者立即獲得最新值
  3. 適合表示 UI 狀態

使用場景示例:

用戶個人信息狀態管理

class UserViewModel : ViewModel() {private val _userState = repository.userUpdates() // Flow<User>.map { it.toUiState() }.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(5000),initialValue = UserState.Loading)val userState: StateFlow<UserState> = _userState
}

三、shareIn 操作符詳解

基本用法

val sharedFlow: SharedFlow<Int> = flow {emit(repository.fetchData())
}.shareIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),replay = 1
)

參數說明:

  • replay:新收集者接收的舊值數量
  • extraBufferCapacity:超出 replay 的緩沖大小
  • onBufferOverflow:緩沖策略(SUSPEND, DROP_OLDEST, DROP_LATEST

特點:

  1. 可以有多個訂閱者
  2. 沒有 value 屬性,必須通過收集獲取數據
  3. 適合事件處理(如 Toast、導航事件)

使用場景示例:

全局事件通知

class EventBus {private val _events = MutableSharedFlow<Event>()val events = _events.asSharedFlow()suspend fun postEvent(event: Event) {_events.emit(event)}// 使用 shareIn 轉換外部流val externalEvents = someExternalFlow.shareIn(scope = CoroutineScope(Dispatchers.IO),started = SharingStarted.Eagerly,replay = 0)
}

四、started 參數深度解析

1. SharingStarted.Eagerly

  • 行為:立即啟動,無視是否有收集者
  • 用例:需要預先緩存的數據
  • 風險:可能造成資源浪費
started = SharingStarted.Eagerly

2. SharingStarted.Lazily

  • 行為:在第一個收集者出現時啟動,保持活躍直到 scope 結束
  • 用例:長期存在的共享數據
  • 注意:可能延遲首次數據獲取
started = SharingStarted.Lazily

3. SharingStarted.WhileSubscribed()

  • 行為
    • 有收集者時活躍
    • 最后一個收集者消失后保持一段時間(默認 0ms)
    • 可配置 stopTimeoutMillisreplayExpirationMillis
  • 用例:大多數 UI 相關狀態
// 保留5秒供可能的重新訂閱
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000)

五、關鍵區別對比

特性stateInshareIn
返回類型StateFlowSharedFlow
初始值必須提供無要求
新收集者獲取立即獲得最新 value獲取 replay 數量的舊值
值訪問通過 .value 直接訪問必須通過收集獲取
典型用途UI 狀態管理事件通知/數據廣播
背壓處理總是緩存最新值可配置緩沖策略

六、最佳實踐指南

1. ViewModel 中的標準模式

class MyViewModel : ViewModel() {// 狀態管理用 stateInval uiState = repository.data.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(5000),initialValue = null)// 事件處理用 shareInval events = repository.events.shareIn(scope = viewModelScope,started = SharingStarted.Lazily,replay = 1)
}

2. 合理選擇 started 策略

  • UI 狀態WhileSubscribed(stopTimeoutMillis = 5000)
  • 配置變更需保留Lazily
  • 全局常駐數據Eagerly

3. 避免常見錯誤

錯誤1:在每次調用時創建新流

// 錯誤!每次調用都創建新流
fun getUser() = repository.getUserFlow().stateIn(viewModelScope, SharingStarted.Eagerly, null)// 正確:共享同一個流
private val _user = repository.getUserFlow().stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val user: StateFlow<User?> = _user

錯誤2:忽略 replay 配置

// 可能丟失事件
.shareIn(scope, SharingStarted.Lazily, replay = 0)// 更安全的配置
.shareIn(scope, SharingStarted.Lazily, replay = 1)

七、高級應用場景

1. 結合 Room 數據庫

@Dao
interface UserDao {@Query("SELECT * FROM user")fun observeUsers(): Flow<List<User>>
}// ViewModel 中
val users = userDao.observeUsers().stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),initialValue = emptyList())

2. 實現自動刷新功能

val autoRefreshData = flow {while(true) {emit(repository.fetchLatest())delay(30_000) // 每30秒刷新}
}.shareIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),replay = 1
)

3. 多源數據合并

val combinedData = combine(repo1.data.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1),repo2.data.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
) { data1, data2 ->data1 + data2
}.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(),initialValue = emptyList()
)

八、性能優化技巧

  1. 合理設置 replay

    • UI 狀態:replay = 1(確保新訂閱者立即獲得狀態)
    • 事件通知:replay = 0(避免重復處理舊事件)
  2. 使用 WhileSubscribed 的過期策略

    started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000,replayExpirationMillis = 60_000 // 1分鐘后丟棄緩存
    )
    
  3. 避免過度緩沖

    .shareIn(scope = ...,replay = 1,extraBufferCapacity = 1, // 總共緩沖2個值onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    

九、測試策略

1. 測試 StateFlow

@Test
fun testStateFlow() = runTest {val testScope = TestScope()val flow = flowOf(1, 2, 3)val stateFlow = flow.stateIn(scope = testScope,started = SharingStarted.Eagerly,initialValue = 0)assertEquals(0, stateFlow.value) // 初始值testScope.advanceUntilIdle()assertEquals(3, stateFlow.value) // 最后發射的值
}

2. 測試 SharedFlow

@Test
fun testSharedFlow() = runTest {val testScope = TestScope()val flow = flowOf("A", "B", "C")val sharedFlow = flow.shareIn(scope = testScope,started = SharingStarted.Eagerly,replay = 1)val results = mutableListOf<String>()val job = launch {sharedFlow.collect { results.add(it) }}testScope.advanceUntilIdle()assertEquals(listOf("A", "B", "C"), results)job.cancel()
}

十、總結決策樹

何時使用 stateIn

  • 需要表示當前狀態(有 .value 屬性)
  • UI 需要立即訪問最新值
  • 適合:頁面狀態、表單數據、加載狀態

何時使用 shareIn

  • 處理一次性事件
  • 需要自定義緩沖策略
  • 適合:Toast 消息、導航事件、廣播通知

選擇哪種 started 策略?

  • WhileSubscribed():大多數 UI 場景
  • Lazily:配置變更需保留數據
  • Eagerly:需要預加載的全局數據

通過本教程,應該已經掌握了 shareInstateIn 的核心用法和高級技巧。正確使用這兩個操作符可以顯著提升應用的性能和資源利用率。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/79555.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/79555.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/79555.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

WebRTC 服務器之SRS服務器概述和環境搭建

1.概述 SRS&#xff08;Simple Realtime Server&#xff09;是一款高性能、跨平臺的流媒體服務器&#xff0c;支持多種協議&#xff0c;包括 RTMP、WebRTC、HLS、HTTP-FLV、SRT、MPEG-DASH 和 GB28181。本文介紹了 SRS&#xff0c;包括其用途、關鍵功能、架構和支持協議。SRS 旨…

Dify - Embedding Rerank

注意&#xff1a;v100顯卡會出現不適配&#xff0c;不推薦使用 1. 安裝 Docker ubuntu 22.04 docker 安裝&使用_ubuntu22.04 安裝docker-CSDN博客 2. 安裝vllm pip install -U xformers torch torchvision torchaudio triton --index-url https://download.pytorch.org/w…

LeetCode:鏈表的中間結點

1、題目描述 給你單鏈表的頭結點 head &#xff0c;請你找出并返回鏈表的中間結點。 如果有兩個中間結點&#xff0c;則返回第二個中間結點。 示例 1&#xff1a; 輸入&#xff1a;head [1,2,3,4,5] 輸出&#xff1a;[3,4,5] 解釋&#xff1a;鏈表只有一個中間結點&#xff…

LabVIEW溫控系統熱敏電阻滯后問題

在 LabVIEW 構建的溫控系統中&#xff0c;熱敏電阻因熱時間常數大&#xff08;2 秒左右&#xff09;產生的滯后效應&#xff0c;致使控溫出現超調與波動。在不更換傳感器的前提下&#xff0c;可從算法優化、硬件調整和系統設計等維度著手解決。 ? 一、算法優化? 1. 改進 PI…

技術犯規計入個人犯規嗎·棒球1號位

在棒球運動中&#xff0c;雖然沒有“技術犯規”這一特定術語&#xff0c;但存在多種違規行為或違反規則的情況&#xff0c;通常會導致判罰或處罰。以下是常見的違規行為及相關規則&#xff1a; 1. 投手違規&#xff08;Balk&#xff09; 定義&#xff1a;投手在壘上有跑壘員時…

Python核心技巧 類與實例:面向對象編程的基石

、核心概念圖解 &#x1f3af; 類 vs 實例 類&#xff1a;對象的藍圖&#xff08;如"汽車設計圖"&#xff09; 實例&#xff1a;類的具體實現&#xff08;如"你的特斯拉Model 3"&#xff09; class MyClass: # 類聲明 count 0 # 類…

協程補充---viewModelScope 相關知識點

viewModelScope.launch 默認在 Dispatchers.Default 線程池執行Dispatchers.Default 是一個后臺線程池&#xff0c;專門用于 CPU 密集型任務如果需要在主線程執行&#xff0c;必須顯式指定 Dispatchers.Main remember 是 Compose 的狀態管理函數(queueMenus) 是依賴項&#xff…

linux stm32mp157 GIC-V2 中斷處理過程分析

/* ** 中斷觸發時&#xff0c;調用的 handle_arch_irq 入口地址。 ** 因為此時&#xff0c;掛接的就是 gic_handle_irq 函數&#xff01;gic_handle_irq 是個全局函數指針&#xff0c; ** static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs) ** 它是Lin…

動態指令參數:根據組件狀態調整指令行為

&#x1f90d; 前端開發工程師、技術日更博主、已過CET6 &#x1f368; 阿珊和她的貓_CSDN博客專家、23年度博客之星前端領域TOP1 &#x1f560; 牛客高級專題作者、打造專欄《前端面試必備》 、《2024面試高頻手撕題》、《前端求職突破計劃》 &#x1f35a; 藍橋云課簽約作者、…

直方圖比較

目錄 1、直方圖比較的概念 2、直方圖比較的主要原因 3、典型應用場景 4、基礎直方圖比較 5、多通道直方圖比較 6、實時直方圖檢測 1、直方圖比較的概念 直方圖比較是通過數學方法計算兩個直方圖之間的相似度或差異度的技術。在計算機視覺中&#xff0c;直方圖是對圖像特征…

Windows11 VS code 安裝 Cline 調用 Github MCP 配置過程坑點匯總

背景 為了調研 MCP 在 windows 上如何使用本地的命令執行一些操作而實現自動化的過程&#xff0c;在 B 站視頻的指導下&#xff0c;進行相應填坑過程&#xff0c;最終運行起來&#xff0c;并實現 github 自動化編程并提交代碼的過程。 B 站 Cline 視頻演示 Cline Cline 是一…

kdump詳解

kdump 是 Linux 系統中的一種內核崩潰轉儲機制&#xff0c;用于在系統崩潰時將內存中的數據保存到磁盤上&#xff0c;以便后續分析系統崩潰的原因。以下是對 kdump 的詳細介紹&#xff1a; 1、工作原理 kdump 利用了 Linux 系統中的雙啟動機制。當系統啟動時&#xff0c;它會…

RGB三原色

本文來源 &#xff1a; 騰訊元寶 ??RGB三原色&#xff08;紅綠藍&#xff09;詳解?? RGB&#xff08;Red, Green, Blue&#xff09;是光學的三原色&#xff0c;通過不同比例的混合可以產生人眼可見的絕大多數顏色。它是現代顯示技術&#xff08;如屏幕、投影儀&#xff09…

CSS兼容性:挑戰與策略

CSS兼容性&#xff1a;挑戰與策略 引言 在前端開發的廣闊領域中&#xff0c;跨瀏覽器兼容性無疑是最棘手且難以預測的挑戰之一。當我們精心設計的網頁在Chrome中完美呈現&#xff0c;卻在Safari中布局崩潰&#xff0c;或在Firefox中交互失效時&#xff0c;這種挫折感是每位前…

[ 設計模式 ] | 單例模式

單例模式是什么&#xff1f;哪兩種模式&#xff1f; 單例模式就是一個類型的對象&#xff0c;只有一個&#xff0c;比如說搜索引擎中的索引部分&#xff0c;360安全衛士的桌面懸浮球。 餓漢模式和懶漢模式&#xff1a;餓漢模式是線程安全的&#xff0c;懶漢模式不是線程安全的…

Notebook.ai 開源程序是一套工具,供作家、游戲設計師和角色扮演者創建宏偉的宇宙 - 以及其中的一切

?一、軟件介紹 文末提供程序和源碼下載 Notebook.ai 開源程序是一套工具&#xff0c;供作家、游戲設計師和角色扮演者創建宏偉的宇宙 - 以及其中的一切。 二、軟件特點 Notebook 是作家的規劃工具&#xff0c;用于創建從宇宙到角色、情節到單個項目的任何內容。通過瀏覽器、…

centos7.0無法安裝php8.2/8.3

在centos安裝php8.2報錯 configure: error: *** A compiler with support for C17 language features is required. 配置過程檢測到你的系統編譯器不支持 C17 語言特性&#xff0c;而 PHP 8.2 的編譯需要編譯器支持 C17 sudo yum update -y sudo yum install centos-releas…

Three.js + React 實戰系列 - 客戶評價區細解教程 Clients 組件?(回答式評價 + 評分星級)

對個人主頁設計和實現感興趣的朋友可以訂閱我的專欄哦&#xff01;&#xff01;謝謝大家&#xff01;&#xff01;&#xff01; 在這篇博客中&#xff0c;我們將實現一個簡潔的 Hear from My Clients 客戶評價區域。這個區塊在個人主頁中可以突顯用戶體驗和專業度&#xff0c;幫…

Vim 命令從頭學習記錄

學習鏈接&#xff1a;eleon-vim基礎教程 Vim - 基礎翻屏操作 光標移動&#xff1a;hjkl 20j 向下移動20行&#xff0c;w 向后移動一個字符&#xff0c;b 向前移動一個字符。 Ctrl u 向上翻半頁 UP Ctrl d 向下翻半頁 Down Ctrl f 向下翻整頁 Forward Ctrl b 向上翻整頁 …

Linux系統編程--基礎指令(!!詳細講解+知識拓展)

第一講 基礎指令 ? 我們現如今自己使用的電腦大部分是用的都是windows或者macOS&#xff0c;并配合上由微軟和蘋果開發的圖形化界面&#xff0c;所以使用鼠標再屏幕上進行點擊即可完成許多任務。但是作為操作系統的學習者&#xff0c;在linux的基礎上不再使用圖形化界進行操作…