Jetpack Compose 状态管理详解
Jetpack Compose 是 Android 的现代声明式 UI 工具包,状态管理是其核心概念之一。以下是完整的状态管理指南:
一、状态基础概念
1.1 什么是状态?
状态是随时间变化的数据,会直接影响 UI 的显示。在 Compose 中,状态变化会触发重组(Recomposition)。
kotlin
// 传统命令式 vs Compose 声明式
// 传统:手动更新 View
// Compose:状态变化 → 自动重组 UI
1.2 状态与重组的关系
plain
状态变化 → 触发重组 → 重新执行 Composable → UI 更新
二、状态类型详解
2.1 基础状态类型
| 类型 | 用途 | 特点 |
|---|
|------------------------|-----------|------------|
| mutableStateOf | 基本类型、对象 | 最常用,自动观察 |
| mutableStateListOf | List 集合 | 支持增删改查自动观察 |
| mutableStateMapOf | Map 集合 | 键值对自动观察 |
| mutableIntStateOf | Int 专用 | 性能优化,避免装箱 |
| mutableLongStateOf | Long 专用 | 性能优化 |
| mutableFloatStateOf | Float 专用 | 性能优化 |
| mutableDoubleStateOf | Double 专用 | 性能优化 |
2.2 代码示例
kotlin
// 基础状态
var count by remember { mutableIntStateOf(0) }
var name by remember { mutableStateOf("") }
var isChecked by remember { mutableStateOf(false) }
// 列表状态
val items = remember { mutableStateListOf<String>() }
items.add("新项") // 自动触发重组
// Map 状态
val userMap = remember { mutableStateMapOf<String, String>() }
userMap["name"] = "张三" // 自动触发重组
三、remember 家族详解
3.1 remember 变体对比
| 函数 | 作用 | 使用场景 |
|---|
|--------------------------|-------|-----------|
| remember | 普通记忆 | 配置不变时 |
| rememberSaveable | 状态保存 | 配置变化/进程死亡 |
| rememberUpdatedState | 更新状态 | 副作用中使用最新值 |
| rememberCoroutineScope | 协程作用域 | 启动协程 |
3.2 代码示例
kotlin
// 1. 普通 remember - 旋转屏幕后丢失
var text by remember { mutableStateOf("") }
// 2. rememberSaveable - 旋转屏幕后保留
var text by rememberSaveable { mutableStateOf("") }
// 3. rememberSaveable + 自定义 Saver
data class User(val name: String, val age: Int)
val userSaver = Saver<User, Bundle>(
save = { bundleOf("name" to it.name, "age" to it.age) },
restore = { User(it.getString("name")!!, it.getInt("age")) }
)
var user by rememberSaveable(stateSaver = userSaver) {
mutableStateOf(User("张三", 25))
}
// 4. rememberUpdatedState - 在 LaunchedEffect 中使用最新值
@Composable
fun TimerScreen(currentTime: Long) {
val updatedTime by rememberUpdatedState(currentTime)
LaunchedEffect(Unit) {
while (true) {
delay(1000)
println("当前时间: $updatedTime") // 始终获取最新值
}
}
}
四、状态提升(State Hoisting)
4.1 什么是状态提升?
将状态从 Composable 中移到调用方,使组件无状态、可复用、可测试。
4.2 状态提升模式
kotlin
// ❌ 未提升状态 - 难以复用和测试
@Composable
fun BadCounter() {
var count by remember { mutableIntStateOf(0) }
Column {
Text("计数: $count")
Button(onClick = { count++ }) { Text("增加") }
}
}
// ✅ 状态已提升 - 无状态组件
@Composable
fun GoodCounter(
count: Int,
onCountChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
Column(modifier) {
Text("计数: $count")
Button(onClick = { onCountChange(count + 1) }) {
Text("增加")
}
}
}
// 使用方管理状态
@Composable
fun CounterScreen() {
var count by rememberSaveable { mutableIntStateOf(0) }
GoodCounter(
count = count,
onCountChange = { count = it }
)
}
4.3 状态提升原则
-
单一数据源:状态只存一处
-
状态向下流动:通过参数传递
-
事件向上传递:通过回调传递
-
无状态组件:尽量让组件无状态
五、ViewModel 集成
5.1 基本用法
kotlin
class CounterViewModel : ViewModel() {
// 私有可变状态
private val _count = MutableStateFlow(0)
// 公开只读状态
val count: StateFlow<Int> = _count.asStateFlow()
fun increment() {
_count.value++
}
}
@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
// 收集 StateFlow 为 Compose 状态
val count by viewModel.count.collectAsStateWithLifecycle()
Column {
Text("计数: $count")
Button(onClick = { viewModel.increment() }) {
Text("增加")
}
}
}
5.2 依赖注入
kotlin
// 带参数的 ViewModel
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
// ...
}
// 在 Composable 中获取
@Composable
fun UserScreen(
viewModel: UserViewModel = viewModel(
factory = viewModelFactory {
initializer {
UserViewModel(AppRepository)
}
}
)
) {
// ...
}
六、高级状态管理
6.1 复合状态管理
kotlin
// 使用 data class 管理相关状态
data class FormState(
val username: String = "",
val email: String = "",
val isLoading: Boolean = false,
val error: String? = null
)
class FormViewModel : ViewModel() {
private val _state = MutableStateFlow(FormState())
val state: StateFlow<FormState> = _state.asStateFlow()
fun updateUsername(username: String) {
_state.update { it.copy(username = username) }
}
fun submit() {
viewModelScope.launch {
_state.update { it.copy(isLoading = true) }
try {
// 提交逻辑
_state.update { it.copy(isLoading = false, error = null) }
} catch (e: Exception) {
_state.update { it.copy(isLoading = false, error = e.message) }
}
}
}
}
6.2 状态容器模式
kotlin
// 复杂页面的状态容器
@Stable
class UiStateHolder {
var tabIndex by mutableIntStateOf(0)
var searchQuery by mutableStateOf("")
var selectedItems by mutableStateOf(setOf<String>())
var isSelectionMode by mutableStateOf(false)
fun toggleSelection(id: String) {
selectedItems = if (selectedItems.contains(id)) {
selectedItems - id
} else {
selectedItems + id
}
isSelectionMode = selectedItems.isNotEmpty()
}
}
@Composable
fun rememberUiStateHolder(): UiStateHolder {
return remember { UiStateHolder() }
}
七、副作用与状态
7.1 常用副作用对比
| 副作用 | 用途 | 特点 |
|---|
|--------------------------|--------|----------|
| LaunchedEffect | 执行协程 | 键变化时重启 |
| rememberCoroutineScope | 手动启动协程 | 跟随组合生命周期 |
| DisposableEffect | 清理资源 | 离开时清理 |
| SideEffect | 每次重组后 | 不常用 |
| produceState | 异步生成状态 | 自动管理生命周期 |
7.2 代码示例
kotlin
// LaunchedEffect - 进入组合执行一次
@Composable
fun UserScreen(userId: String) {
var user by remember { mutableStateOf<User?>(null) }
LaunchedEffect(userId) { // userId 变化时重新执行
user = repository.getUser(userId)
}
}
// produceState - 更简洁的异步状态
@Composable
fun loadUser(userId: String): State<User?> {
return produceState<User?>(initialValue = null, userId) {
value = repository.getUser(userId)
}
}
// DisposableEffect - 资源清理
@Composable
fun LifecycleAwareComponent() {
DisposableEffect(Unit) {
val listener = createListener()
addListener(listener)
onDispose {
removeListener(listener) // 离开时清理
}
}
}
八、性能优化
8.1 避免不必要重组
kotlin
// ❌ 整个对象作为状态
data class User(val name: String, val age: Int, val avatar: String)
var user by remember { mutableStateOf(User(...)) }
// 修改 name 会导致整个 User 变化
// ✅ 使用 @Stable 或拆分状态
@Stable
data class User(val name: String, val age: Int, val avatar: String)
// 或拆分为独立状态
var name by remember { mutableStateOf("") }
var age by remember { mutableIntStateOf(0) }
8.2 使用 derivedStateOf 派生状态
kotlin
// ❌ 每次重组都计算
val filteredList = list.filter { it.contains(query) }
// ✅ 只有 query 或 list 变化时才计算
val filteredList by remember {
derivedStateOf { list.filter { it.contains(query) } }
}
8.3 使用 remember 缓存计算结果
kotlin
// ❌ 每次重组都执行
val sortedList = list.sorted()
// ✅ 只有 list 变化时才排序
val sortedList = remember(list) { list.sorted() }
九、完整实战示例
kotlin
// 数据模型
data class Todo(
val id: String,
val title: String,
val isDone: Boolean = false
)
// ViewModel
class TodoViewModel : ViewModel() {
private val _todos = MutableStateFlow<List<Todo>>(emptyList())
val todos: StateFlow<List<Todo>> = _todos.asStateFlow()
private val _filter = MutableStateFlow("")
val filter: StateFlow<String> = _filter.asStateFlow()
// 派生状态:过滤后的列表
val filteredTodos: StateFlow<List<Todo>> = combine(_todos, _filter) { todos, filter ->
if (filter.isBlank()) todos
else todos.filter { it.title.contains(filter, ignoreCase = true) }
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
fun addTodo(title: String) {
_todos.update { it + Todo(UUID.randomUUID().toString(), title) }
}
fun toggleTodo(id: String) {
_todos.update { todos ->
todos.map { if (it.id == id) it.copy(isDone = !it.isDone) else it }
}
}
fun removeTodo(id: String) {
_todos.update { it.filterNot { todo -> todo.id == id } }
}
fun updateFilter(query: String) {
_filter.value = query
}
}
// UI 层
@Composable
fun TodoScreen(viewModel: TodoViewModel = viewModel()) {
val todos by viewModel.filteredTodos.collectAsStateWithLifecycle()
val filter by viewModel.filter.collectAsStateWithLifecycle()
Column(Modifier.fillMaxSize().padding(16.dp)) {
// 搜索框
OutlinedTextField(
value = filter,
onValueChange = viewModel::updateFilter,
modifier = Modifier.fillMaxWidth(),
placeholder = { Text("搜索待办...") }
)
Spacer(Modifier.height(16.dp))
// 待办列表
LazyColumn {
items(todos, key = { it.id }) { todo ->
TodoItem(
todo = todo,
onToggle = { viewModel.toggleTodo(it) },
onRemove = { viewModel.removeTodo(it) }
)
}
}
Spacer(Modifier.height(16.dp))
// 添加按钮
var newTodo by rememberSaveable { mutableStateOf("") }
Row {
OutlinedTextField(
value = newTodo,
onValueChange = { newTodo = it },
modifier = Modifier.weight(1f)
)
Button(
onClick = {
if (newTodo.isNotBlank()) {
viewModel.addTodo(newTodo)
newTodo = ""
}
}
) {
Text("添加")
}
}
}
}
// 无状态组件
@Composable
fun TodoItem(
todo: Todo,
onToggle: (String) -> Unit,
onRemove: (String) -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier.fillMaxWidth().padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
checked = todo.isDone,
onCheckedChange = { onToggle(todo.id) }
)
Text(
text = todo.title,
modifier = Modifier.weight(1f),
style = MaterialTheme.typography.bodyLarge,
textDecoration = if (todo.isDone) TextDecoration.LineThrough else null
)
IconButton(onClick = { onRemove(todo.id) }) {
Icon(Icons.Default.Delete, contentDescription = "删除")
}
}
}
十、最佳实践总结
| 场景 | 推荐方案 |
|---|
|----------|------------------------------------------------|
| 简单 UI 状态 | remember + mutableStateOf |
| 需要保存的状态 | rememberSaveable |
| 屏幕级状态 | ViewModel + StateFlow |
| 应用级状态 | 依赖注入 + 单例 Repository |
| 列表状态 | mutableStateListOf |
| 复杂状态 | data class + StateFlow |
| 派生状态 | derivedStateOf 或 combine |
| 异步状态 | produceState 或 collectAsStateWithLifecycle |
十一、常见错误与解决方案
错误 1:在 Composable 中创建 ViewModel
kotlin
// ❌ 错误:每次重组都创建新实例
@Composable
fun BadScreen() {
val viewModel = TodoViewModel() // 错误!
}
// ✅ 正确:使用 viewModel() 函数
@Composable
fun GoodScreen(viewModel: TodoViewModel = viewModel()) {
// ...
}
错误 2:忘记使用 remember
kotlin
// ❌ 错误:每次重组都重置状态
@Composable
fun BadCounter() {
var count = mutableIntStateOf(0) // 错误!
}
// ✅ 正确
@Composable
fun GoodCounter() {
var count by remember { mutableIntStateOf(0) }
}
错误 3:在 LaunchedEffect 中使用旧值
kotlin
// ❌ 错误:使用旧值
LaunchedEffect(Unit) {
while (true) {
delay(1000)
println(count) // 可能使用旧值
}
}
// ✅ 正确:使用 rememberUpdatedState
val updatedCount by rememberUpdatedState(count)
LaunchedEffect(Unit) {
while (true) {
delay(1000)
println(updatedCount) // 始终最新值
}
}
十二、总结
Jetpack Compose 状态管理的核心原则:
-
单一数据源:状态只存一处,避免同步问题
-
状态向下流动:通过参数传递状态
-
事件向上传递:通过回调传递用户操作
-
就近原则:状态放在离使用最近的地方
-
生命周期感知 :使用
rememberSaveable和ViewModel处理配置变化 -
性能优化 :使用
derivedStateOf、remember等避免不必要计算
掌握这些概念和模式,可以构建出可维护、可测试、高性能的 Compose 应用。