一 Android StateFlow 完整教程:從入門到實戰
StateFlow 是 Kotlin 協程庫中用于狀態管理的響應式流,特別適合在 Android 應用開發中管理 UI 狀態。本教程將帶全面了解 StateFlow 的使用方法。
1. StateFlow 基礎概念
1.1 什么是 StateFlow?
StateFlow 是 Kotlin 協程提供的一種熱流(Hot Flow),它具有以下特點:
- 總是有當前值(初始值必須提供)
- 只保留最新值
- 支持多個觀察者
- 與 LiveData 類似但基于協程
1.2 StateFlow vs LiveData
特性 | StateFlow | LiveData |
---|---|---|
生命周期感知 | 否(需配合 lifecycleScope) | 是 |
需要初始值 | 是 | 否 |
基于 | 協程 | 觀察者模式 |
線程控制 | 通過 Dispatcher | 主線程 |
背壓處理 | 自動處理 | 自動處理 |
2. 基本使用
2.1 添加依賴
在 build.gradle 中添加:
dependencies {implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
}
2.2 創建 StateFlow
class MyViewModel : ViewModel() {// 私有可變的StateFlowprivate val _uiState = MutableStateFlow<UiState>(UiState.Loading)// 公開不可變的StateFlowval uiState: StateFlow<UiState> = _uiState.asStateFlow()sealed class UiState {object Loading : UiState()data class Success(val data: String) : UiState()data class Error(val message: String) : UiState()}fun loadData() {viewModelScope.launch {_uiState.value = UiState.Loadingtry {val result = repository.fetchData()_uiState.value = UiState.Success(result)} catch (e: Exception) {_uiState.value = UiState.Error(e.message ?: "Unknown error")}}}
}
2.3 在 Activity/Fragment 中收集 StateFlow
class MyActivity : AppCompatActivity() {private val viewModel: MyViewModel by viewModels()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)lifecycleScope.launch {repeatOnLifecycle(Lifecycle.State.STARTED) {viewModel.uiState.collect { state ->when (state) {is MyViewModel.UiState.Loading -> showLoading()is MyViewModel.UiState.Success -> showData(state.data)is MyViewModel.UiState.Error -> showError(state.message)}}}}}private fun showLoading() { /*...*/ }private fun showData(data: String) { /*...*/ }private fun showError(message: String) { /*...*/ }
}
3. 高級用法
3.1 結合 SharedFlow 處理一次性事件
class EventViewModel : ViewModel() {private val _events = MutableSharedFlow<Event>()val events = _events.asSharedFlow()sealed class Event {data class ShowToast(val message: String) : Event()object NavigateToNextScreen : Event()}fun triggerEvent() {viewModelScope.launch {_events.emit(Event.ShowToast("Hello World!"))}}
}// 在Activity中收集
lifecycleScope.launch {repeatOnLifecycle(Lifecycle.State.STARTED) {viewModel.events.collect { event ->when (event) {is EventViewModel.Event.ShowToast -> showToast(event.message)EventViewModel.Event.NavigateToNextScreen -> navigateToNext()}}}
}
3.2 狀態合并 (combine)
val userName = MutableStateFlow("")
val userAge = MutableStateFlow(0)val userInfo = combine(userName, userAge) { name, age ->"Name: $name, Age: $age"
}// 收集合并后的流
userInfo.collect { info ->println(info)
}
3.3 狀態轉換 (map, filter, etc.)
val numbers = MutableStateFlow(0)val evenNumbers = numbers.filter { it % 2 == 0 }.map { "Even: $it" }evenNumbers.collect { println(it) }
4. 性能優化
4.1 使用 stateIn 緩存 StateFlow
val networkFlow = flow {// 模擬網絡請求emit(repository.fetchData())
}val cachedState = networkFlow.stateIn(scope = viewModelScope,started = SharingStarted.WhileSubscribed(5000), // 5秒無訂閱者停止initialValue = "Loading..."
)
4.2 避免重復收集
// 錯誤方式 - 每次重組都會創建新的收集器
@Composable
fun MyComposable(viewModel: MyViewModel) {val state by viewModel.state.collectAsState()// ...
}// 正確方式 - 使用 derivedStateOf 或 remember
@Composable
fun MyComposable(viewModel: MyViewModel) {val state by remember { viewModel.state }.collectAsState()// ...
}
5. 測試 StateFlow
5.1 單元測試
@Test
fun `test state flow`() = runTest {val viewModel = MyViewModel()val results = mutableListOf<MyViewModel.UiState>()val job = launch {viewModel.uiState.collect { results.add(it) }}viewModel.loadData()advanceUntilIdle()assertEquals(3, results.size) // Loading, Success/ErrorassertTrue(results[0] is MyViewModel.UiState.Loading)job.cancel()
}
5.2 使用 Turbine 測試庫
dependencies {testImplementation "app.cash.turbine:turbine:0.12.1"
}@Test
fun `test with turbine`() = runTest {val viewModel = MyViewModel()viewModel.uiState.test {viewModel.loadData()assertEquals(MyViewModel.UiState.Loading, awaitItem())val success = awaitItem()assertTrue(success is MyViewModel.UiState.Success)cancelAndIgnoreRemainingEvents()}
}
6. 常見問題解答
Q1: StateFlow 和 LiveData 哪個更好?
StateFlow 更適合協程環境,LiveData 更簡單但功能較少。新項目推薦 StateFlow。
Q2: 如何處理背壓(Backpressure)?
StateFlow 自動處理背壓,只保留最新值。
Q3: 為什么我的收集器沒有收到更新?
檢查:
- 是否在正確的生命周期范圍內收集
- Flow 是否有發射新值
- 是否在正確的協程上下文中
Q4: 如何避免內存泄漏?
使用 repeatOnLifecycle
或 flowWithLifecycle
確保只在活躍生命周期收集。
7. 完整示例項目
以下是一個完整的 ViewModel 示例:
class UserViewModel(private val userRepository: UserRepository) : ViewModel() {private val _userState = MutableStateFlow<UserState>(UserState.Loading)val userState: StateFlow<UserState> = _userState.asStateFlow()private val _events = MutableSharedFlow<UserEvent>()val events: SharedFlow<UserEvent> = _events.asSharedFlow()init {loadUser()}fun loadUser() {viewModelScope.launch {_userState.value = UserState.Loadingtry {val user = userRepository.getUser()_userState.value = UserState.Success(user)} catch (e: Exception) {_userState.value = UserState.Error(e.message ?: "Unknown error")_events.emit(UserEvent.ShowErrorToast("Failed to load user"))}}}fun updateUserName(name: String) {viewModelScope.launch {val currentUser = (_userState.value as? UserState.Success)?.user ?: return@launchval updatedUser = currentUser.copy(name = name)_userState.value = UserState.Success(updatedUser)userRepository.updateUser(updatedUser)}}sealed class UserState {object Loading : UserState()data class Success(val user: User) : UserState()data class Error(val message: String) : UserState()}sealed class UserEvent {data class ShowErrorToast(val message: String) : UserEvent()}
}
通過本教程,應該已經掌握了 StateFlow 的核心用法。StateFlow 是構建響應式 Android 應用的強大工具,結合協程可以提供更簡潔、更安全的狀態管理方案。
二 stateFlow和sharedFlow的區別
關于 StateFlow
和 SharedFlow
的問題非常關鍵。它們都是 Kotlin Flow
的“熱流”(hot stream)變種,廣泛用于 事件驅動編程 和 狀態管理。下面逐條詳細解答問題:
? 1. StateFlow 和 SharedFlow 的區別?能否互相轉換?
特性 | StateFlow | SharedFlow |
---|---|---|
是否需要初始值 | ? 是(必須有) | ? 否(可選) |
是否有當前值 .value | ? 是 | ? 沒有 |
是否支持粘性訂閱(可立即獲取最新值) | ? 是(始終有當前值) | ? 否(默認非粘性,取決于 replay) |
用于什么 | 狀態管理(UI 狀態) | 一次性事件(導航、吐司等) |
是否自動重發 | ? 是:新訂閱者立即拿到當前值 | ? 否(需設置 replay > 0 才能重發) |
是否支持多次發送相同值 | ? 否(相同值不會觸發 collect) | ? 是 |
是否丟失事件 | ? 不會丟(始終保留一個最新值) | ? 可能丟(如果沒有設置 replay 或 buffer) |
🔁 能否互相轉換?
不能直接轉換,因為它們底層設計不同。但可以人為地模擬:
val sharedFlow = MutableSharedFlow<Int>(replay = 1)
val stateFlow = sharedFlow.stateIn(scope, SharingStarted.Eagerly, initialValue)
或者從 StateFlow
獲取 SharedFlow
的行為:
val stateFlow: StateFlow<Int> = MutableStateFlow(0)
val sharedFlow = stateFlow.shareIn(scope, SharingStarted.Lazily)
? 2. 是不是 StateFlow 就是 SharedFlow 多了 .value
和初始值?
可以這么說,但這只是表面。它們的語義不同:
StateFlow
表示的是狀態的容器(如 ViewModel 中的 UI 狀態)。SharedFlow
更像是事件的通道,沒有“當前狀態”的概念。
// StateFlow 的特點
println(stateFlow.value) // 始終可以拿到當前值// SharedFlow 沒有 value,只能 collect
sharedFlow.collect { event -> ... }
? 3. 如何處理“被壓的”問題(Backpressure、丟失事件)?
📌 StateFlow
:
- 沒有丟失問題:永遠保留最后一個值。
- 新訂閱者會立刻收到當前值。
- 不支持 backpressure,因為沒有隊列:只有一個值。
val sf = MutableStateFlow(0)
sf.value = 1
sf.value = 2
// collect 會立即收到 2(中間的1不會被保留)
📌 SharedFlow
:
- 默認不保留任何事件(
replay = 0
,無 buffer)。 - 如果事件很快發送但沒人 collect,就直接丟失。
- 可以設置
replay
和extraBufferCapacity
來控制:
val shared = MutableSharedFlow<Int>(replay = 2,extraBufferCapacity = 5
)
- 設置
onBufferOverflow = BufferOverflow.DROP_OLDEST/ DROP_LATEST / SUSPEND
控制丟棄策略。
? 使用建議總結
需求 | 推薦使用 |
---|---|
需要保存并暴露當前狀態(如 UI 狀態) | ? StateFlow |
需要發送一次性事件,如 Toast、導航 | ? SharedFlow |
想在訂閱時立即拿到最后值 | ? StateFlow |
想發送多個相同值 | ? SharedFlow (StateFlow 會去重) |
三 MutableStateFlow 是如何更新值的?
_seatLevel.value = newValue
和 _seatLevel.update { ... }
都可以用來更新 MutableStateFlow
的值,但它們在 并發安全性 和 使用場景 上有區別:
1. _seatLevel.value = newValue
(直接賦值)
- 作用:直接設置
MutableStateFlow
的新值。 - 特點:
- 非原子操作:如果在多線程環境下,直接賦值可能會導致競態條件(Race Condition),因為
value
的讀寫不是線程安全的。 - 簡單直接:適用于單線程或確定不會并發更新的場景。
- 非原子操作:如果在多線程環境下,直接賦值可能會導致競態條件(Race Condition),因為
- 示例:
_seatLevel.value = 5 // 直接設置新值
2. _seatLevel.update { ... }
(原子更新)
- 作用:以 原子方式 計算并更新
MutableStateFlow
的值。 - 特點:
- 原子操作:
update
是線程安全的,內部使用CAS (Compare-And-Swap)
機制,確保在并發環境下不會出現數據競爭。 - 基于當前值計算:可以訪問當前值(
current
),并返回新值。 - 適合條件更新:適用于需要依賴當前值進行計算的場景(如遞增、條件過濾等)。
- 原子操作:
- 示例:
這里:_seatLevel.update { current ->if (newLevel in 0..100) newLevel else current }
current
是當前_seatLevel
的值。- 返回的值會作為新值(如果
newLevel
不在0..100
范圍內,則保持原值)。
關鍵區別
特性 | _seatLevel.value = newValue | _seatLevel.update { ... } |
---|---|---|
線程安全 | ? 非原子操作,可能競態條件 | ? 原子操作,線程安全 |
是否依賴當前值 | ? 直接賦值,不關心當前值 | ? 可以訪問當前值并計算新值 |
適用場景 | 單線程或確定無并發更新 | 多線程環境或需要條件更新 |
如何選擇?
- 如果只是簡單賦值,且確定不會并發更新(例如 UI 線程中),可以直接用
value = newValue
。 - 如果涉及多線程,或需要基于當前值計算新值(如遞增、條件過濾等),應該用
update { ... }
。
底層實現
update
內部使用了while (true)
+compareAndSet
機制,確保原子性:
它會不斷嘗試更新,直到成功(避免并發沖突)。public inline fun update(function: (T) -> T) {while (true) {val prevValue = valueval nextValue = function(prevValue)if (compareAndSet(prevValue, nextValue)) {return}} }
總結
value = newValue
:簡單直接,但不適合并發場景。update { ... }
:線程安全,適合多線程或需要依賴當前值的更新。
在你的 updateLevel
函數中,由于涉及條件判斷(newLevel in 0..100
),使用 update
是更安全、更合理的選擇。
四 為什么update 更新是原子性?
相關代碼
public inline fun update(function: (T) -> T) {while (true) {val prevValue = valueval nextValue = function(prevValue)if (compareAndSet(prevValue, nextValue)) {return}}
}
這段代碼之所以能保證 MutableStateFlow
的更新是 原子性(Atomic) 的,關鍵在于它使用了 CAS(Compare-And-Swap) 機制,并通過 while (true)
循環確保最終成功更新。我們逐行分析其原子性保證:
1. compareAndSet(prevValue, nextValue)
是關鍵
compareAndSet
是 Atomic
操作(底層基于 CPU 的 CAS 指令),它的行為是:
fun compareAndSet(expect: T, newValue: T): Boolean
- 如果當前
value == expect
(說明其他線程尚未修改),則更新為newValue
并返回true
。 - 如果當前
value != expect
(說明其他線程已經修改),則放棄更新并返回false
。
由于 compareAndSet
是 硬件級原子操作,它保證了:
- 讀取
value
、比較expect
和 寫入newValue
這三個步驟是 不可分割的(不會被其他線程打斷)。
2. while (true)
循環的作用
如果 compareAndSet
失敗(返回 false
),說明其他線程已經修改了 value
,此時:
- 重新讀取最新的
value
(val prevValue = value
)。 - 重新計算
nextValue
(function(prevValue)
)。 - 再次嘗試
compareAndSet
,直到成功為止。
這種 樂觀鎖(Optimistic Locking) 策略確保了:
- 即使多線程競爭,最終所有更新都會 按順序應用(不會丟失任何修改)。
- 不會發生 死鎖(因為沒有阻塞,只是重試)。
3. 為什么能保證原子性?
compareAndSet
是原子的:單次compareAndSet
調用是線程安全的。- 循環直到成功:即使其他線程并發修改,當前線程最終會基于最新的值成功更新。
- 函數式更新:
function(prevValue)
的計算是基于最新的prevValue
,不會出現臟數據。
4. 對比非原子更新的問題
如果直接用 value = newValue
:
// 非原子操作,可能導致競態條件
_seatLevel.value = _seatLevel.value + 1
在多線程環境下:
- 線程 A 讀取
value = 0
。 - 線程 B 讀取
value = 0
。 - 線程 A 寫入
value = 1
。 - 線程 B 寫入
value = 1
(本應是 2,但結果錯誤!)。
而 update
能避免這個問題:
_seatLevel.update { current -> current + 1 } // 線程安全
5. 原子性的直觀體現
從代碼中可以看出原子性的關鍵點:
val prevValue = value
:讀取當前值(可能被其他線程修改)。val nextValue = function(prevValue)
:基于當前值計算新值。if (compareAndSet(prevValue, nextValue))
:- 只有
value
仍等于prevValue
時才會更新。 - 如果失敗(說明其他線程已修改),則 重試,確保最終更新是基于最新值。
- 只有
6. 類似機制的對比
- Java 的
AtomicInteger
:atomicInt.updateAndGet(x -> x + 1); // 同樣基于 CAS
- 數據庫的樂觀鎖:
UPDATE table SET value = newValue WHERE value = oldValue; -- 類似 CAS
總結
這段代碼的原子性由以下兩點保證:
compareAndSet
的原子性:確保比較和更新的操作不可分割。- 循環重試機制:確保并發沖突時最終能成功更新。
因此,update { ... }
是線程安全的,適合在多線程環境下使用,而直接 value = newValue
不是。