Android Jetpack Compose 状态管理实战:从 remember 到 ViewModel 完整教程

Jetpack Compose 已经成为 Android 官方推荐的 UI 框架,但很多开发者在刚上手时都会对它的状态管理感到困惑:remember、mutableStateOf、rememberSaveable、ViewModel……这些到底该怎么选?本文从最基础的概念出发,配合真实代码示例,带你彻底搞清楚 Compose 中的状态管理体系。
一、为什么 Compose 需要特殊的状态管理?
传统 View 系统中,视图有自己的状态(比如 EditText 记住用户输入)。但 Compose 的核心思想是单向数据流——UI 是状态的函数,状态变化触发重组(Recomposition)。这意味着:
普通变量在重组时会被重新初始化,无法保持状态
必须用 Compose 提供的状态容器才能让 UI 响应数据变化
状态的生命周期需要和 Composable 的生命周期匹配
简单来说:不用 remember,你的变量会在每次重组时归零。
// ❌ 错误写法:count 每次重组都会被重置为 0
@Composable
fun BrokenCounter() {
var count = 0
Button(onClick = { count++ }) {
Text("点击次数: $count")
}
}
// ✅ 正确写法:用 remember + mutableStateOf
@Composable
fun WorkingCounter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("点击次数: $count")
}
}二、remember 和 mutableStateOf 的本质
remember 的作用是在重组间保存一个值,而 mutableStateOf 创建了一个可观察的状态对象——当它的值改变时,Compose 会自动触发依赖它的 Composable 重新组合。
两者通常配合使用,但要注意它们是独立的:
// mutableStateOf 不用 remember:每次重组都创建新的 State 对象(状态丢失)
var state = mutableStateOf(0) // ❌
// remember 不用 mutableStateOf:值被记住了,但改变它不触发重组
var count by remember { mutableStateOf(0) } // ✅
// 等价写法(by 是 Kotlin 委托属性语法糖)
val countState = remember { mutableStateOf(0) }
countState.value++ // 直接用 .value踩坑记录: 很多新手会忘记 remember 只在当前 Composable 的生命周期内有效。当 Composable 从组合树中移除再重新添加时(比如切换 Tab),remember 的值会丢失。
三、rememberSaveable:跨越配置变更保存状态
当屏幕旋转或系统资源回收时,remember 的值会丢失。rememberSaveable 能将状态自动保存到 Bundle,行为类似 onSaveInstanceState。
@Composable
fun SearchBar() {
// 旋转屏幕后输入内容不会丢失
var searchText by rememberSaveable { mutableStateOf("") }
TextField(
value = searchText,
onValueChange = { searchText = it },
label = { Text("搜索") }
)
}对于无法自动序列化的自定义类型,需要实现 Saver:
data class City(val name: String, val code: String)
val CitySaver = run {
val nameKey = "name"
val codeKey = "code"
mapSaver(
save = { mapOf(nameKey to it.name, codeKey to it.code) },
restore = { City(it[nameKey] as String, it[codeKey] as String) }
)
}
@Composable
fun CitySelector() {
var selectedCity by rememberSaveable(stateSaver = CitySaver) {
mutableStateOf(City("北京", "BJ"))
}
// ...
}经验建议: 用 remember 还是 rememberSaveable?如果这个状态丢失了用户会不高兴(比如填写了一半的表单),用 rememberSaveable;如果只是 UI 临时状态(比如下拉菜单展开收起),用 remember 就够了。
四、状态提升:让组件更可复用
一个优秀的 Composable 应该是"无状态"的——把状态提升到调用者,让组件只负责展示。这就是 Compose 官方推荐的状态提升(State Hoisting)模式。
// ❌ 有状态的组件(难以复用,无法测试)
@Composable
fun StatefulTextField() {
var text by remember { mutableStateOf("") }
TextField(value = text, onValueChange = { text = it })
}
// ✅ 无状态的组件(状态提升)
@Composable
fun StatelessTextField(
text: String,
onTextChange: (String) -> Unit
) {
TextField(value = text, onValueChange = onTextChange)
}
// 调用时在外部持有状态
@Composable
fun ParentComposable() {
var text by remember { mutableStateOf("") }
StatelessTextField(
text = text,
onTextChange = { text = it }
)
}状态提升的核心原则:状态应该被提升到所有需要它的 Composable 的最低公共祖先。
五、ViewModel 集成:管理界面级状态
对于跨越多个 Composable 的复杂状态,或者需要在后台执行业务逻辑的场景,应该使用 ViewModel。Compose 提供了 viewModel() 函数来获取 ViewModel 实例:
// 添加依赖(build.gradle.kts)
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0")
// ViewModel 定义
class UserViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UserUiState())
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
fun loadUser(userId: String) {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
try {
val user = userRepository.getUser(userId)
_uiState.update { it.copy(user = user, isLoading = false) }
} catch (e: Exception) {
_uiState.update { it.copy(error = e.message, isLoading = false) }
}
}
}
fun updateName(name: String) {
_uiState.update { it.copy(user = it.user?.copy(name = name)) }
}
}
data class UserUiState(
val user: User? = null,
val isLoading: Boolean = false,
val error: String? = null
)
// 在 Composable 中使用
@Composable
fun UserScreen(
userId: String,
viewModel: UserViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(userId) {
viewModel.loadUser(userId)
}
when {
uiState.isLoading -> CircularProgressIndicator()
uiState.error != null -> Text("错误:${uiState.error}")
uiState.user != null -> UserContent(
user = uiState.user!!,
onNameChange = viewModel::updateName
)
}
}注意: 使用 collectAsStateWithLifecycle() 而非 collectAsState(),前者会在 Composable 不可见时自动暂停收集,更省资源。
六、选择指南:用哪个?
总结一下不同状态管理方案的适用场景:
remember + mutableStateOf:单个 Composable 内部的临时 UI 状态(动画、展开收起等)
rememberSaveable:需要在配置变更后恢复的 UI 状态(表单输入、列表滚动位置)
状态提升:多个 Composable 共享的状态,提升到最近公共父节点
ViewModel + StateFlow:屏幕级状态、需要业务逻辑处理的数据、跨越导航的状态
DataStore / 数据库:需要持久化的用户设置或应用数据
掌握这套体系后,你会发现 Compose 的状态管理其实非常清晰。从 remember 起步,根据需求逐层上升到 ViewModel,每一层都有明确的职责边界。希望这篇文章能帮你在实际项目中做出正确的技术选型。
发布评论
热门评论区: