这是 Compose 系列的第七篇,前面六篇我们认识并学会了使用各种基础组件,但组件是"死"的,数据才是"活"的。今天,我们就来彻底搞懂 Compose 中的状态管理。
一、前言
在传统的 Android View 系统中,我们会用 findViewById 获取控件实例,然后调用 setText、setVisibility 等相关方法来手动更新UI。这种命令式的更新方式,需要我们精确控制每一步的UI变化。
Compose 则采用了声明式编程 ,其核心逻辑可以用一个公式概况:
U I = f ( s t a t e ) UI = f(state) UI=f(state)
UI仅是 状态(state) 的函数映射。我们不再需要手动操作UI,只需要操作数据。数据变了,UI自动跟着变。这也是 Compose 高效且优雅的根基。
二、什么是状态
在 Android 开发中,状态(State)是一个绕不开的核心概念。应用中任何可以随时间变化的值都是状态------比如输入框中的文字、开关的开启或关闭、网络请求返回的数据,以及各种组件动画...
在任何程序中,"状态 "是指在某个特定时刻,应用中存在的所有数据的快照。在 Compose 中,状态特指那些能够触发界面重组(Recomposition) 的可观察数据对象。
当状态改变时,重新执行生成 UI 的函数。这个过程叫重组。
三、一段不工作的代码
先看下面一段代码:
kotlin
@Composable
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello!",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.bodyMedium
)
OutlinedTextField(
value = "",
onValueChange = { },
label = { Text("Name") }
)
}
}
代码运行效果如下所示:

运行它并在输入框里输入任意字符------你会发现界面上什么都不会发生。
原因在于:OutlinedTextField 的 value 被硬编码为空字符串,onValueChange 也是空实现。Compose 不会帮你记住用户输入了什么,也不会去主动更新UI。
要使得状态去有所改变------状态变了,UI才会变。
要让输入框正常工作,需要三样东西:
- 可观察的状态
- 记住状态(避免重组时丢失)
- 事件回调(更新状态)
四、认识MutableStateOf
Compose使用 State<T> 来描述一个状态,泛型 T 是状态的具体类型。State<T> 是一个可观察对象 。当它的 value 发生变化时,所有读取过这个值的 Composable 函数都会被调度进行重组。
当 Composable 需要对 State 的 value 进行修改,使用 MutableState<T> 来表示可修改状态 ,其包裹的数据是一个可修改的var类型。
kotlin
@Stable
public interface MutableState<T> : State<T> {
override var value: T // 可修改的var类型
public operator fun component1(): T
public operator fun component2(): (T) -> Unit
}
在 Composable 中声明状态,可以有三种写法:
kotlin
// 方式一:直接使用 .value
val nameState = remember { mutableStateOf("") }
OutlinedTextField(
value = nameState.value,
onValueChange = { nameState.value = it }
)
// 方式二:属性委托(推荐)⭐
var name by remember { mutableStateOf("") }
OutlinedTextField(
value = name,
onValueChange = { name = it }
)
// 方式三:解构声明
val (name, setName) = remember { mutableStateOf("") }
OutlinedTextField(
value = name,
onValueChange = { setName(it) }
)
属性委托(by)需要导入:
kotlinimport androidx.compose.runtime.getValue import androidx.compose.runtime.setValue
推荐使用方式二(属性委托),代码最简洁且易读。
现在让我们用 mutableStateOf 改写 HelloConent ,并添加Log日志输出:
kotlin
@Composable
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
var name by mutableStateOf("") // 添加 mutableStateOf
Log.d(TAG, ">>>>>> HelloContent() name = " + name)
Text(
text = "Hello, $name!",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.bodyMedium
)
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") }
)
}
}
运行上面的代码效果如下:

运行后你会发现, TextField 里还是什么都没有,但是日志可以看到每输入一个字符, HelloContent 就调用一次且 name 一直是空内容。为什么?
出现上面的情况很好理解:我们使用了mutableStateOf,但给到的值是空字符串 。即每次重组都会重新创建一个新的状态实例,初始值始终是空字符串。
kotlin
var name by mutableStateOf("")
五、认识 remember
使用 mutableStateOf 记住了状态,但还有一个问题:Composable函数可能会被反复执行(重组)。如果状态直接写在函数体里,每次重组都会重新创建,那状态和没记住一样!
这个时候就需要使用 remember了。Composable 首次执行时,remember 会把值缓存在组合(Composition)中,后续重组时直接返回之前的值,不会重新创建。
kotlin
@Composable
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
var name by remember { mutableStateOf("") } // 状态被记住
Log.d(TAG, ">>>>>> HelloContent() name = " + name)
if (name.isNotEmpty()) { // 新增一行判空处理
Text(
text = "Hello, $name!",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.bodyMedium
)
}
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") }
)
}
}
现在输入框可以正常工作了。每当用户输入,onValueChange 会更新 name ,Compose 检测到状态变化,自动重组读取了 name 的 Text 和 OutlinedTextField 。

关于"重组"的细节 ,比如它怎么做到只刷新变化的部分、什么时候会触发全量重组,我会在下一篇文章介绍。今天你只需要记住:创建状态,必须包一层
remember。
六、认识 rememberSaveable
remember 好用,但缺点也明显:它只能在重组 时保留数据,无法应对 Activity 重建 (比如旋转屏幕)或者跨进程存在等场景。
测试:在输入框中输入文字,旋转屏幕,你会发现输入内容被清空了。
请看示例效果:

kotlin
18:23:01.820 11733-11733 MainActivity D >>>>>> HelloContent() name = c
18:23:02.585 11733-11733 MainActivity D >>>>>> HelloContent() name = co
18:23:03.319 11733-11733 MainActivity D >>>>>> HelloContent() name = com
18:23:04.119 11733-11733 MainActivity D >>>>>> HelloContent() name = comp
18:23:04.803 11733-11733 MainActivity D >>>>>> HelloContent() name = compo
18:23:05.603 11733-11733 MainActivity D >>>>>> HelloContent() name = compos
18:23:06.019 11733-11733 MainActivity D >>>>>> HelloContent() name = compose
18:23:08.820 11733-11733 MainActivity D >>>>>> HelloContent() name =
这个时候我们就需要使用 rememberSaveable 了。它可以像 Activity 的 onSaveInstanceState 那样在进程被杀时自动保存状态,同时像onRestoreInstanceState 一样随进程重建而自动恢复。
代码修改后的示例:
kotlin
@Composable
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
var name by rememberSaveable { mutableStateOf("") } // 把 remember -> rememberSaveable
Log.d(TAG, ">>>>>> HelloContent() name = " + name)
if (name.isNotEmpty()) {
Text(
text = "Hello, $name!",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.bodyMedium
)
}
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") }
)
}
}
运行效果和日志输出如下:

kotlin
18:31:00.386 12471-12471 MainActivity D >>>>>> HelloContent() name = c
18:31:01.102 12471-12471 MainActivity D >>>>>> HelloContent() name = co
18:31:01.803 12471-12471 MainActivity D >>>>>> HelloContent() name = com
18:31:02.502 12471-12471 MainActivity D >>>>>> HelloContent() name = comp
18:31:03.070 12471-12471 MainActivity D >>>>>> HelloContent() name = compo
18:31:03.902 12471-12471 MainActivity D >>>>>> HelloContent() name = compos
18:31:04.570 12471-12471 MainActivity D >>>>>> HelloContent() name = compose
18:31:06.716 12471-12471 MainActivity D >>>>>> HelloContent() name = compose
18:31:11.572 12471-12471 MainActivity D >>>>>> HelloContent() name = composec
18:31:12.235 12471-12471 MainActivity D >>>>>> HelloContent() name = composecc
18:31:13.956 12471-12471 MainActivity D >>>>>> HelloContent() name = composecc
只需要把 remember 换成 rememberSaveable ,现在无论旋转屏幕还是因内存不足被系统杀死后重新打开,输入内容都能完美恢复。
适用场景:用户输入、滚动状态、开关状态等 UI 相关的、需要跨配置变更保留的轻量级状态。
七、状态提升(State Hoisting)
之前的例子中,状态 name 定义在 HelloContent 内部。这就意味着 HelloContent 是一个 有状态(Stateful) 的组件------组件自己持有并管理状态。
这种写法在小示例中没问题,但在实际项目中,随着业务逻辑增多,我们不可能将所有逻辑塞进一个 Composable 中。那该如何修改?类比 Java 里方法过长我们就重构为多个短方法,自然会想到的做法就是拆分Composable ,但当你拆分Composable时才发现:子组件也需要改变数据,又该怎么办呢?
答案就是使用状态提升(State Hoisting)。
状态提升(State Hoisting) 就是把状态从组件内部"提"到调用方(父组件)去,让组件变成无状态(Stateless) 的,所有状态由父组件通过参数传入,并通过回调将事件传回父组件。
在传统 Android View中,将状态从View上提到Activity或者ViewModel可以把View和逻辑解耦。
状态提升的好处:
- 单一数据源:数据只由父组件管理,不会多处散落。
- 组件可复用:子组件变成了"无状态组件(Stateless Composable)",给它什么数据它就展示什么,换个父组件依然能用。
- 便于调试:所有修改都通过父组件的回调进行,数据流向清晰可追踪。
具体代码如何修改请看下面:
kotlin
// 无状态的 HelloContent ------ 状态由外部传入
@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello,$name!",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.bodyMedium
)
OutlinedTextField(
value = name,
onValueChange = onNameChange,
label = {
Text("Name")
}
)
}
}
// 父组件持有状态
@Composable
fun HelloScreen() {
var name by rememberSaveable { mutableStateOf("") }
HelloContent(name = name, onNameChange = { name = it })
}
状态提升的核心原则是:把状态提升到读取和写入它的所有 Composable 之间的最低共同祖先。
八、单向数据流(UDF)
状态提升的自然延伸就是单向数据流(Unidirectional Data Flow,UDF) ------这是一种设计模式,核心就两句话:
- 状态向下流动:父组件把状态通过参数传给子组件
- 事件向上流动 :子组件通过回调把事件传给父组件

在 Compose 中,TextField 就是 UDF 的典型体现:
- 状态向下:
value参数把当前值传给TextField - 事件向上:
onValueChange回调把用户输入传回给调用方
采用单向数据流,你可以把显示 UI 的 Composable 和存储、改变状态的部分彻底分离开来。
九、状态容器的演进
随着状态提升的层级越来越高,状态可能会被提到 Activity 级别甚至更高。这时候再在 Composable 参数里层层传递,代码会变得臃肿不堪。
Compose 提供了几种 状态容器(State Holder) 的选择,根据状态的职责范围来区分:
9.1 UI逻辑 vs 业务逻辑
首先要区分两种逻辑:
- UI 逻辑:和界面展示相关的逻辑。比如:根据输入内容决定按钮是否可点、控制侧滑菜单的开关。
- 业务逻辑:和应用数据相关的逻辑。比如:保存数据到数据库、发起网络请求、处理业务规则。
这两种逻辑对应不同的状态容器方案。
9.2 普通类 + remember:管理UI逻辑
如果状态只涉及 UI 逻辑,且生命周期和 Composable 一致,可以用一个普通类配合 remember:
kotlin
class HelloState(initialName: String = "") {
var name by mutableStateOf(initialName)
private set
fun updateName(newName: String) {
name = newName
}
}
@Composable
fun rememberHelloState(initialName: String = ""): HelloState {
return remember { HelloState(initialName) }
}
@Composable
fun HelloContent(helloState: HelloState) {
OutlinedTextField(
value = helloState.name,
onValueChange = { helloState.updateName(it) }
)
}
这种方式适合逻辑并不复杂的有状态可组合项。
9.3 ViewModel:管理业务逻辑
当状态涉及业务逻辑(网络请求、数据库操作等),或者需要跨屏幕共享时,就应该使用 ViewModel。
ViewModel 的优势在于:
- 生命周期更长:不受配置变更影响,存活于整个屏幕(Destination)的生命周期
- 与数据层交互:可以方便地调用 Repository 或 UseCase
- 可测试性:可以单独对 ViewModel 做单元测试
kotlin
class HelloViewModel : ViewModel() {
private val _name = mutableStateOf("")
val name: State<String> = _name
fun updateName(newName: String) {
_name.value = newName
}
}
@Composable
fun HelloScreen(viewModel: HelloViewModel = viewModel()) {
HelloContent(
name = viewModel.name.value,
onNameChange = { viewModel.updateName(it) }
)
}
9.4 如何选择
| 状态类型 | 推荐方案 | 生命周期 |
|---|---|---|
| 单个Composable内部使用的UI状态 | remember + mutableStateOf | 组合(Composition) |
| 跨配置变更保留的UI状态 | rememberSaveable | Activity重建 |
| 多个Composable 共享的UI逻辑 | 普通状态容器类 + remember | 组合(Composition) |
| 涉及业务逻辑的状态 | ViewModel | 当前导航目的地(即当前页面,离开即自动销毁) |
| 需要跨导航共享的状态 | ViewModel + 共享作用域 | 父级 NavGraph 或 Activity |
十、结语
本篇内容核心总结出来就是:
mutableStateOf创建可观察状态,变化时触发重组remember让状态在重组时不被重置rememberSaveable让状态在配置变更和进程终止后依然保留- 状态提升把状态上移到调用方,让组件变为无状态(Stateless),提高复用性
- 单向数据流是 Compose 推荐的设计模式:状态向下,事件向上
- 状态容器 的选择取决于逻辑类型------UI逻辑用普通类 +
remember,业务逻辑用viewModel
掌握好状态管理,是写出高质量 Compose 应用的关键一步。
十一、参考资料
- Android官方文档:状态与Jetpack Compose
- Android 官方文档:管理状态
- Android 官方文档:状态提升
- Android 官方文档:重组
- Android 官方文档:Compose中的状态
- 《 Jetpack Compose 从入门到实战 》