本文涵盖 Compose 状态管理的全套方案:状态提升、ViewModel + StateFlow、CompositionLocal、SavedStateHandle、以及复杂场景下的架构选型。配合 Compose 四大核心概念 中的 State 章节阅读效果最佳。
状态管理全景图 本地状态(组件内部)
mutableStateOf + remember
↓ 提升
状态提升(父子传参)
状态在父组件,通过参数下发
↓ 跨页面
ViewModel(页面级)
StateFlow + collectAsState,生命周期感知
↓ 跨组件
CompositionLocal(作用域共享)
Theme、Context 等隐式传递
↓ 跨应用
持久化状态
rememberSaveable / DataStore / Room 层级 方案 存活范围 适用场景 组件内部 remember + mutableStateOf组件在组合树中 开关、展开/折叠、输入框 父子通信 状态提升 (State Hoisting) 父组件生命周期 表单、列表项 页面级 ViewModel + StateFlowActivity/Fragment 生命周期 页面数据、网络请求结果 作用域共享 CompositionLocalComposable 子树 主题、导航栈、依赖注入 持久化 rememberSaveable / DataStore进程存活 / 持久文件 配置变更保留 / 设置、Token 全局 单例 + StateFlow Application 用户信息、购物车
状态提升(State Hoisting)—— 最基本的心智模型 什么是状态提升 将状态从子组件「提升」到最近的共同父组件 ,子组件通过参数接收状态和事件回调。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Composable fun SearchField_Bad () { var text by remember { mutableStateOf("" ) } TextField(value = text, onValueChange = { text = it }) } @Composable fun SearchField ( value: String , onValueChange: (String ) -> Unit , modifier: Modifier = Modifier ) { TextField( value = value, onValueChange = onValueChange, modifier = modifier, placeholder = { Text("搜索…" ) } ) } @Composable fun HomeScreen () { var query by remember { mutableStateOf("" ) } Column { SearchField(value = query, onValueChange = { query = it }) Text("搜索:$query " ) } }
状态提升三原则 原则 说明 单一数据源 状态只有一个持有者,避免多处各自维护导致不一致 单向数据流 状态向下传递(参数),事件向上冒泡(回调) 不可变性 参数用 val / data class,接收方不修改传入的状态
提升到什么层级? 1 2 3 4 ① 兄弟组件间共享 → 提升到共同父组件 ② 多页面间共享 → 提升到 ViewModel ③ 整个子树隐式共享 → 用 CompositionLocal ④ 全局(登录态等) → 单例 + StateFlow
🔑 状态提升是 Compose 的通用模式 ,几乎所有官方 Material 组件都遵循此模式(如 TextField、Checkbox、Switch)。
ViewModel + StateFlow —— 页面级状态管理(推荐) 为什么需要 ViewModel remember 只能在 Composable 中存活。一旦页面被销毁重建(屏幕旋转、进程重启),状态就丢了。ViewModel 绑定到 Activity/Fragment 的生命周期,可以在配置变更后存活 。
1 2 3 implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0" ) implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0" )
标准模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 data class HomeUiState ( val isLoading: Boolean = true , val items: List<String> = emptyList(), val errorMessage: String? = null , val searchQuery: String = "" ) class HomeViewModel : ViewModel () { private val _uiState = MutableStateFlow(HomeUiState()) val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow() init { loadData() } fun onSearchQueryChange (query: String ) { _uiState.update { it.copy(searchQuery = query) } } fun refresh () { _uiState.update { it.copy(isLoading = true ) } loadData() } private fun loadData () { viewModelScope.launch { try { val data = fetchItems() _uiState.update { it.copy( isLoading = false , items = data , errorMessage = null )} } catch (e: Exception) { _uiState.update { it.copy( isLoading = false , errorMessage = e.message )} } } } } @Composable fun HomeScreen (viewModel: HomeViewModel = viewModel() ) { val uiState by viewModel.uiState.collectAsState() when { uiState.isLoading -> LoadingIndicator() uiState.errorMessage != null -> ErrorView(uiState.errorMessage!!) { viewModel.refresh() } else -> ContentList( items = uiState.items, query = uiState.searchQuery, onQueryChange = viewModel::onSearchQueryChange, onRefresh = viewModel::refresh ) } }
为什么用 StateFlow 而不是 LiveData 对比 StateFlow LiveData 类型安全 ✅ 编译时检查 ⚠️ 运行时检查 初始值 ✅ 必须有初始值 ❌ 可选 线程 ✅ 不限线程 ⚠️ 仅主线程 setValue Collect ✅ collectAsState() ✅ observeAsState() Flow 操作符 ✅ map、combine、filter ❌ 需要 Transformations 测试 ✅ 直接 runTest collect ⚠️ 需要 InstantTaskExecutorRule
Google 官方推荐 StateFlow 作为 Compose 状态容器的首选 。
单状态对象 vs 多状态对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class BadViewModel : ViewModel () { val isLoading = MutableStateFlow(false ) val items = MutableStateFlow<List<String>>(emptyList()) val error = MutableStateFlow<String?>(null ) } data class HomeUiState ( val isLoading: Boolean = false , val items: List<String> = emptyList(), val error: String? = null ) class GoodViewModel : ViewModel () { private val _uiState = MutableStateFlow(HomeUiState()) val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow() fun onLoaded (data : List <String >) { _uiState.update { it.copy(isLoading = false , items = data , error = null ) } } }
🔑 一条原则 :一个 ViewModel → 一个 data class UiState → 一个 StateFlow。
SavedStateHandle —— 进程被杀死后恢复 ViewModel 可以存活配置变更但无法存活进程被杀。SavedStateHandle 解决了这个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class SearchViewModel ( private val savedStateHandle: SavedStateHandle ) : ViewModel() { var query by mutableStateOf( savedStateHandle.get <String>("query" ) ?: "" ) private set fun onQueryChange (newQuery: String ) { query = newQuery savedStateHandle["query" ] = newQuery } val selectedTab = savedStateHandle.getStateFlow("tab" , 0 ) fun saveForm (form: FormData ) { savedStateHandle["form_json" ] = Json.encodeToString(FormData.serializer(), form) } fun restoreForm () : FormData { val json = savedStateHandle.get <String>("form_json" ) ?: return FormData() return Json.decodeFromString(FormData.serializer(), json) } }
场景 方案 配置变更(旋转屏幕) ViewModel(默认) 进程被杀死后恢复 SavedStateHandle跨进程 / 长期保存 DataStore / Room
CompositionLocal —— 隐式跨组件共享 当状态需要在整个子树中共享,但又不想逐层手动传递时,使用 CompositionLocal。
典型场景 MaterialTheme.colors 统一颜色LocalContext.current 获取 Context自定义主题、语言、导航栈 自定义 CompositionLocal 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 data class AppTheme ( val isDark: Boolean = false , val accentColor: Color = Color.Blue ) val LocalAppTheme = staticCompositionLocalOf { AppTheme() }@Composable fun AppThemeProvider ( isDark: Boolean , content: @Composable () -> Unit ) { val theme = if (isDark) { AppTheme(isDark = true , accentColor = Color(0xFFBB86FC )) } else { AppTheme() } CompositionLocalProvider(LocalAppTheme provides theme) { content() } } @Composable fun ThemedButton () { val theme = LocalAppTheme.current Button( colors = ButtonDefaults.buttonColors( containerColor = theme.accentColor ) ) { Text("按钮" ) } }
compositionLocalOf vs staticCompositionLocalOf compositionLocalOfstaticCompositionLocalOf重组范围 仅用到 .current 的组件 所有组件 性能 值变化频繁时更优 值几乎不变时更优 典型用途 动态主题色、滚动位置 Context、主题类型、导航控制器
⚠️ 不要用 CompositionLocal 替代参数传递 。只有当跨越多层、逐层传参显得冗余时才使用。
复杂状态管理方案对比 当应用状态越来越复杂(多个 ViewModel 间共享数据、缓存、跨页面通信),可以考虑更结构化的方案:
方案 特点 适合 ViewModel + StateFlow 官方原生,零额外依赖 ⭐ 中小型项目首选 MVI(Model-View-Intent) 单向数据流,Action → State → UI 复杂 UI,需要严格状态管理 unidirectional data flow (UDF) Google 官方推荐架构 配合 ViewModel 的自然延伸 Molecule Compose 运行时驱动状态流 复杂业务逻辑用 Compose 表达 Store(Circuit/Mobius) 状态机驱动,支持 effect 中间件 大型应用,团队协作
MVI 核心结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 data class CounterState (val count: Int = 0 , val isLoading: Boolean = false )sealed interface CounterEvent { data object Increment : CounterEvent data object Decrement : CounterEvent data object Reset : CounterEvent data class AsyncIncrement (val after: Long ) : CounterEvent } class CounterViewModel : ViewModel () { private val _state = MutableStateFlow(CounterState()) val state: StateFlow<CounterState> = _state.asStateFlow() fun onEvent (event: CounterEvent ) { when (event) { CounterEvent.Increment -> _state.update { it.copy(count = it.count + 1 ) } CounterEvent.Decrement -> _state.update { it.copy(count = it.count - 1 ) } CounterEvent.Reset -> _state.update { it.copy(count = 0 ) } is CounterEvent.AsyncIncrement -> { _state.update { it.copy(isLoading = true ) } viewModelScope.launch { delay(event.after) _state.update { it.copy(count = it.count + 1 , isLoading = false ) } } } } } } @Composable fun CounterScreen (viewModel: CounterViewModel = viewModel() ) { val state by viewModel.state.collectAsState() Column(horizontalAlignment = Alignment.CenterHorizontally) { Text("计数:${state.count} " , style = MaterialTheme.typography.headlineMedium) if (state.isLoading) { CircularProgressIndicator(modifier = Modifier.padding(8. dp)) } Row { Button(onClick = { viewModel.onEvent(CounterEvent.Increment) }) { Text("+1" ) } Button(onClick = { viewModel.onEvent(CounterEvent.Decrement) }) { Text("-1" ) } Button(onClick = { viewModel.onEvent(CounterEvent.Reset) }) { Text("重置" ) } } Button(onClick = { viewModel.onEvent(CounterEvent.AsyncIncrement(2000 )) }) { Text("2秒后 +1" ) } } }
MVI 的优点 优点 说明 可追溯 每个状态变更都源自一个 Event,方便调试和回放 可测试 ViewModel 是纯函数:(State, Event) → State 线程安全 MutableStateFlow.update {} 原子操作单向数据流 UI → Event → ViewModel → State → UI
全局状态 —— 跨 Activity 共享 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 object CartManager { private val _items = MutableStateFlow<List<CartItem>>(emptyList()) val items: StateFlow<List<CartItem>> = _items.asStateFlow() val totalCount: StateFlow<Int > = _items .map { it.sumOf { item -> item.quantity } } .stateIn(GlobalScope, SharingStarted.WhileSubscribed(), 0 ) val totalPrice: StateFlow<Double > = _items .map { it.sumOf { item -> item.price * item.quantity } } .stateIn(GlobalScope, SharingStarted.WhileSubscribed(), 0.0 ) fun addItem (item: CartItem ) { _items.update { list -> val idx = list.indexOfFirst { it.id == item.id } if (idx >= 0 ) { list.toMutableList().also { it[idx] = it[idx].copy(quantity = it[idx].quantity + 1 ) } } else { list + item } } } fun removeItem (id: String ) { _items.update { it.filter { item -> item.id != id } } } fun clear () { _items.value = emptyList() } } @Composable fun CartBadge () { val count by CartManager.totalCount.collectAsState() Badge { Text("$count " ) } }
⚠️ 全局单例要慎用:测试难隔离、生命周期不可控。优先考虑 ViewModel + 依赖注入(Hilt/Koin)。
性能优化 状态粒度 —— 越细越好 1 2 3 4 5 6 7 8 9 data class BigState (val a: Int , val b: Int , val c: Int , val d: Int )Row { Text("a: ${state.a} " ) Text("b: ${state.b} " ) Text("c: ${state.c} " ) Text("d: ${state.d} " ) }
1 2 3 4 5 6 7 8 9 10 11 12 val a by viewModel.flowA.collectAsState()val b by viewModel.flowB.collectAsState()val c by viewModel.flowC.collectAsState()val d by viewModel.flowD.collectAsState()Row { Text("a: $a " ) Text("b: $b " ) Text("c: $c " ) Text("d: $d " ) }
🔑 权衡 :单一 StateFlow 方便管理,但可能导致不必要的重组。如果页面复杂且有独立的 UI 区域,拆分为多个 StateFlow。
derivedStateOf —— 缓存计算结果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Composable fun ItemList (viewModel: ItemViewModel = viewModel() ) { val items by viewModel.items.collectAsState() val filter by viewModel.filter.collectAsState() val filteredItems by remember { derivedStateOf { items.filter { it.matches(filter) } } } LazyColumn { items(filteredItems, key = { it.id }) { item -> ItemRow(item) } } }
derivedStateOf 内部会做相等性比较,只有结果变化才通知重组。
常见坑与最佳实践 坑 原因 解决 旋转屏幕状态丢失 用了 remember 没用 ViewModel 页面级状态用 ViewModel ViewModel 中创建 State 导致内存泄漏 在 ViewModel init 里持有 Composable 引用 ViewModel 只管数据,不管 UI collectAsState() 阻塞主线程默认在主线程 collect Flow 内部用 flowOn(Dispatcher.IO) 多个 ViewModel 间状态不同步 各自维护同一份数据 提取到 Repository 或共享 StateFlow CompositionLocal 导致全树重组 使用了 compositionLocalOf 且值频繁变化 用 staticCompositionLocalOf StateFlow 热流导致后台持续计算 WhileSubscribed(5000) 默认永不停设置合适的 stopTimeoutMillis mutableStateOf 放错位置放在 ViewModel 但不用 collectAsState ViewModel 中用 StateFlow,Composable 中用 mutableStateOf
架构速查表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ┌─────────────────────────────────────────────────────┐ │ Activity / Fragment │ │ ┌───────────────────────────────────────────────┐ │ │ │ set Content { AppTheme { NavHost { ... } } } │ │ │ └───────────────────────────────────────────────┘ │ │ │ │ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │Screen A │ │ Screen B │ │ Screen C │ │ │ │ │ │ │ │ │ │ │ │viewModel │ │ viewModel │ │ viewModel │ │ │ │ .state │ │ .state │ │ .state │ │ │ │ ↓ │ │ ↓ │ │ ↓ │ │ │ │collectA- │ │ collectA- │ │ collectA- │ │ │ │sState() │ │ sState() │ │ sState() │ │ │ │ ↓ │ │ ↓ │ │ ↓ │ │ │ │ UI ← │ │ UI ← │ │ UI ← │ │ │ │ Event │ │ Event │ │ Event │ │ │ └──────────┘ └──────────────┘ └──────────────┘ │ │ │ │ ┌──────────────────────────────────────────────┐ │ │ │ Repository Layer (StateFlow) │ │ │ │ ┌────────────┐ ┌──────────────────────┐ │ │ │ │ │CartManager │ │ UserSessionManager │ │ │ │ │ │(Singleton) │ │ (Singleton) │ │ │ │ │ └────────────┘ └──────────────────────┘ │ │ │ └──────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────┐ │ │ │ Data Layer (Room / DataStore / Retrofit) │ │ │ └──────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────┘
完整实战:搜索 + 收藏功能 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 data class SearchUiState ( val query: String = "" , val results: List<Repo> = emptyList(), val favorites: Set<String> = emptySet(), val isLoading: Boolean = false , val error: String? = null ) sealed interface SearchEvent { data class QueryChanged (val query: String) : SearchEvent data object Search : SearchEvent data class ToggleFavorite (val repoId: String) : SearchEvent } class SearchViewModel ( private val repository: RepoRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel() { private val _state = MutableStateFlow(SearchUiState()) val state: StateFlow<SearchUiState> = _state.asStateFlow() init { savedStateHandle.get <String>("query" )?.let { query -> _state.update { it.copy(query = query) } } } fun onEvent (event: SearchEvent ) { when (event) { is SearchEvent.QueryChanged -> { _state.update { it.copy(query = event.query) } savedStateHandle["query" ] = event.query } SearchEvent.Search -> { val query = _state.value.query.ifBlank { return } _state.update { it.copy(isLoading = true , error = null ) } viewModelScope.launch { try { val results = repository.search(query) _state.update { it.copy(isLoading = false , results = results) } } catch (e: Exception) { _state.update { it.copy(isLoading = false , error = e.message) } } } } is SearchEvent.ToggleFavorite -> { _state.update { state -> val newFavorites = state.favorites.toMutableSet().apply { if (contains(event.repoId)) remove(event.repoId) else add(event.repoId) } state.copy(favorites = newFavorites) } } } } } @Composable fun SearchScreen ( viewModel: SearchViewModel = viewModel() , onRepoClick: (String) -> Unit ) { val state by viewModel.state.collectAsState() Scaffold( topBar = { SearchBar( query = state.query, onQueryChange = { viewModel.onEvent(SearchEvent.QueryChanged(it)) }, onSearch = { viewModel.onEvent(SearchEvent.Search) } ) } ) { padding -> when { state.isLoading -> Box( Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.Center ) { CircularProgressIndicator() } state.error != null -> ErrorRetry(state.error!!) { viewModel.onEvent(SearchEvent.Search) } state.results.isEmpty() && state.query.isNotEmpty() -> EmptyResult() else -> LazyColumn(modifier = Modifier.padding(padding)) { items(state.results, key = { it.id }) { repo -> RepoItem( repo = repo, isFavorite = repo.id in state.favorites, onFavorite = { viewModel.onEvent(SearchEvent.ToggleFavorite(repo.id)) }, onClick = { onRepoClick(repo.id) } ) } } } } }
以上模式可以无痛扩展到任意页面:定义 UiState → 定义 Event → ViewModel 处理 Event 产生新 State → UI 收集。