文章目录
- [remember 最基础的状态保存(抵抗重组)](#remember 最基础的状态保存(抵抗重组))
- [rememberSaveable 重建 activity 或进程之后保留状态 (生命周期持久)](#rememberSaveable 重建 activity 或进程之后保留状态 (生命周期持久))
- [ViewModel 状态容器](#ViewModel 状态容器)
-
- [ViewModel 中如何声明状态能被 compose 使用?](#ViewModel 中如何声明状态能被 compose 使用?)
- [StateFlow 状态(数据)流 (用于ViewModel中)](#StateFlow 状态(数据)流 (用于ViewModel中))
- [SavedStateHandle 键值对保存,生命周期持久 (用于ViewModel中)](#SavedStateHandle 键值对保存,生命周期持久 (用于ViewModel中))
- [状态保存 API 对比总结](#状态保存 API 对比总结)
-
- [状态持久化中,`Bundle` 机制做了什么?](#状态持久化中,
Bundle机制做了什么?) - [rememberSaveable 中的 `Bundle` 机制](#rememberSaveable 中的
Bundle机制) - [SavedStateHandle 中的 `Bundle` 机制](#SavedStateHandle 中的
Bundle机制)
- [状态持久化中,`Bundle` 机制做了什么?](#状态持久化中,
- [组合使用 状态保存 api 示例](#组合使用 状态保存 api 示例)
- [ViewModel 的生命周期](#ViewModel 的生命周期)
在上一篇《Android Compose 状态:核心api,状态恢复,状态提升,状态容器》 提过,使用 remember 、rememberSaveable api 和 ViewModel 可以进行状态保存。
还有 StateFlow、SavedStateHandle 也可以进行状态保存。
但它们作用的层级、生命周期和解决的问题各有不同
remember 最基础的状态保存(抵抗重组)
remember {} 是最基础的 状态保存 api。remember 的核心作用是让状态在"重组"期间存活下来。
val xx by remember { mutableStateOf(...) } 到底在保存什么? mutableStateOf 是创建一个观察的状态,这个状态值变了,会通知所有读取它的 composable 重新执行,即重组。
如果没有 remember ,那每次重组,都是重新赋值成初始值。
加上了 remember,在每次状态值改变时,发生内存缓存,后续每次重组,都是先判断从缓存中获取到最新值。
比如,animateColorAsState ,用于在两个颜色值之间实现平滑的过渡动画。内部以 remember 实现

声明时,不使用 remember:val backColorState by animateColorAsState(...)
rememberSaveable 重建 activity 或进程之后保留状态 (生命周期持久)
- 重建activity的场景,如配置更改(例如:屏幕旋转、切换深浅色模式、改变语言)
- 重建进程的场景,系统杀死了后台进程
rememberSaveable 不仅能抵抗重组,还能抵抗配置更改和进程被杀。
它支持可存入 Bundle对象的 任意数据类型;对于其它数据类型,需要进行序列化,或自定义 saver。
有些状态,就是不适合持久化保存的,它们是"极其短暂的、瞬时的 UI 状态"或"纯内存引用
例如:一个按钮按下时的水波纹动画效果、一个极其短暂的防抖动时间戳。 为了这些东西去序列化存入 Bundle,反而会浪费系统性能。
这些状态丢了就丢了,完全不影响用户体验。
官方的 可组合函数库中,大量提供了 rememberXxxState 的状态,它们的底层大多数使用了 rememberSaveable。
例如:
val scrollState = rememberScrollState() // 滚动视图状态
val listState = rememberLazyListState() // 用于延迟加载列表组件
val pagerState = rememberPagerState(pageCount = { 10 }) // 用于 ViewPager 效果的轮播图或翻页组件
val snackbarHostState = remember { SnackbarHostState() } // 用于控制 Material Design 中的底部轻量提示
val sheetState = rememberModalBottomSheetState() // 用于控制从屏幕底部弹出的模态底板
...
比如,使用 androidx.compose.material3.DatePicker,发现还提供了专属的状态对象 DatePickerState,其doc注释,提示了 状态保存函数:rememberDatePickerState

该函数内就是 以 rememberSaveable 实现的。向 它传入了自定义的 saver 对象 DatePickerStateImpl.Saver ,继续跟踪查看,该 save 是使用 listSaver 实现的。

ViewModel 状态容器
用于管理 业务逻辑 和 屏幕级别的状态
其生命周期比普通的 Composable 更长,能够在使用配置更改(如屏幕旋转)时存活下来。
可声明持有多种 数据状态的引用。
可提供业务逻辑控制方法;可在方法中开启协程。
ViewModel 中如何声明状态能被 compose 使用?
State<T>类型
使用 mutableStateOf api 包装
kotlin
class UserViewModel : ViewModel() {
// 暴露给 Compose 的屏幕状态
val userName by mutableStateOf("")
}
- StateFlow 状态(数据)流
- SavedStateHandle 键值对保存,生命周期持久
StateFlow 状态(数据)流 (用于ViewModel中)
StateFlow 是 Kotlin 协程库(Coroutines)提供的一种状态持有可观察数据流。它始终有且只有一个最新值。当它的值发生变化时,它会自动通知所有的观察者。它通常被用在 ViewModel 中,用于向 UI 层(Composable)暴露业务数据或UI状态。
特点:
- 只要 ViewModel 存活(例如屏幕旋转时),它包含的数据就不会丢失。
- 但它默认无法在"系统级进程被杀(Process Death)"后存活。
示例:
kotlin
class UserViewModel : ViewModel() {
// 内部可变的状态
private val _userName = MutableStateFlow("默认用户")
// 暴露给外部的不可变状态
val userName = _userName.asStateFlow()
fun updateName(newName: String) {
_userName.value = newName
}
}
// 在 Compose 中使用
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
// 推荐使用 collectAsStateWithLifecycle() 感知生命周期收集
val userName by viewModel.userName.collectAsStateWithLifecycle()
Column {
Text("当前用户: $userName")
Button(onClick = { viewModel.updateName("张三") }) {
Text("修改名字")
}
}
}
SavedStateHandle 键值对保存,生命周期持久 (用于ViewModel中)
ViewModel 在系统由于内存不足杀死应用进程后,能够保存和恢复状态
使用时, SavedStateHandle 类型参数要作为 ViewModel 的构造函数参数
- 使用
SavedStateHandle#saveableapi ,配合mutableStateOf实现 读取和写入界面元素状态。
kotlin
class ConversationViewModel(
savedStateHandle: SavedStateHandle
) : ViewModel() {
// 状态声明委托
var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) {
mutableStateOf(TextFieldValue(""))
}
private set
fun update(newMessage: TextFieldValue) {
message = newMessage
}
}
val viewModel = ConversationViewModel(SavedStateHandle())
@Composable
fun UserInput() {
TextField(
value = viewModel.message,
onValueChange = { viewModel.update(it) }
)
}
saveable API 开箱就支持基元类型,并会收到 stateSaver 参数,以便使用自定义 Saver(就像 rememberSaveable() 一样)。
- 使用键值对 api
kotlin
class SearchViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// 定义一个键
private val SEARCH_QUERY_KEY = "search_query"
// getStateFlow 会自动从 SavedStateHandle 中读取历史值,如果没有则使用初始值 ""
// 当 SavedStateHandle[SEARCH_QUERY_KEY] 被修改时,这个 StateFlow 会自动更新
val searchQuery = savedStateHandle.getStateFlow(SEARCH_QUERY_KEY, "")
fun updateSearchQuery(query: String) {
// 更新键值对,会自动触发上方的 getStateFlow 更新
savedStateHandle[SEARCH_QUERY_KEY] = query
}
}
@Composable
fun SearchScreen(viewModel: SearchViewModel = viewModel()) {
val query by viewModel.searchQuery.collectAsStateWithLifecycle()
TextField(
value = query,
onValueChange = { viewModel.updateSearchQuery(it) },
label = { Text("搜索") }
)
}
状态保存 API 对比总结
-
remember
用在 composable 函数中 ,在重组后保持最新值侧重于 瞬时UI 状态
-
rememberSaveable (生命周期持久)
用在 composable 函数中 ,不光能在重组后保持最新值,且当配置更改或进程被杀死导致进程重建、Activity重建,compose ui 重新渲染时,也能保持最新值底层存储依赖
Bundle机制侧重于交互状态
-
StateFlow
用在 ViewModel 中 ,生命周期依赖 ViewModel 的 生命周期普通的业务数据 / 大体量数据 / 需要复杂处理转换的数据 / 不能或不方便序列化的数据
-
SavedStateHandle
用在 ViewModel 中 ,键值对保存,生命周期持久底层存储依赖
Bundle机制侧重于核心业务数据状态
状态持久化中,Bundle 机制做了什么?
rememberSaveable 中的 Bundle 机制
- 核心组件:SaveableStateRegistry(保存注册表)
rememberSaveable 并不直接操作 Bundle。在 Compose 层,有一个名为 SaveableStateRegistry 的接口。
在 Composable 中调用 rememberSaveable 时,它会产生一个唯一的 Key,并将状态值(以及如何保存它的逻辑)注册到这个注册表中。
这个注册表的作用是:在 Composable 存续期间,它像普通的 remember 一样在内存里维护数据;但在系统准备回收资源时,它负责把数据"打包"。
- Saver(序列化器)
Bundle 只能存储特定的数据类型(如 String, Int, Parcelable 等)。如果你的状态是一个复杂的自定义对象(比如一个 Data Class),Bundle 存不下。
这时候 Saver 就发挥作用了:
Save (保存):将复杂的对象拆解成 Bundle 能接受的简单类型(序列化)。
Restore (恢复):将从 Bundle 读出的简单类型重新构造成复杂的对象(反序列化)。
注:如果你存的是 String 或 Int 等基础类型,Compose 会提供默认的 Saver。
- 保存流程:从内存到系统进程
当系统因为内存压力准备杀死进程,或者发生配置更改(如旋转屏幕)时:
触发回调:Activity 的 onSaveInstanceState(outState: Bundle) 被触发。
收集数据:Compose 框架会调用 SaveableStateRegistry 的保存方法,把所有注册过的 rememberSaveable 状态通过 Saver 转换成键值对。
这些键值对被存入 Activity 的 outState 这个大 Bundle 中。
这个 Bundle 最终会被传递给系统进程(System Server)托管,它不随 App 进程的销毁而消失。
- 恢复流程:从系统进程回到 Composable
当进程重建,Activity 重新启动时:
分发数据:系统将托管的 Bundle 传回给新 Activity 的 onCreate。
重建注册表:Compose 框架根据这个 Bundle 重新构建 SaveableStateRegistry。
重新绑定:当 Composable 函数再次运行到 rememberSaveable 这一行代码时:
它会根据其位置信息或手动指定的 Key 去注册表里查找。
如果找到了之前保存的值,就通过 Saver 的 restore 方法把它变回原来的对象。
如果没找到则初始化。
SavedStateHandle 中的 Bundle 机制
-
进程被杀前(数据保存):
当系统决定回收你的 App 进程时,Activity 会触发 onSaveInstanceState。此时,
SavedStateHandle会将其内部持有的数据序列化到一个 Bundle 中,这个 Bundle 会被交给系统进程托管,它不随 App 进程的销毁而消失。 -
进程重建时(数据恢复):
系统进程会将之前保存的 Bundle 传回给新创建的 Activity 进程。
-
ViewModel 创建阶段:
当创建 ViewModel 实例时,Compose 或 Activity 默认使用的 SavedStateViewModelFactory 会介入。它会从系统的 Bundle 中提取出属于该 ViewModel 的那部分数据,并用这些数据构造出一个 SavedStateHandle。
-
注入:
Factory 将这个"装满了旧数据"的 SavedStateHandle 传入 ViewModel 的构造函数。
组合使用 状态保存 api 示例
示例功能:默认加载文章列表;从文章列表点击后,进入文章详情,并根据 文章id 加载详情数据
根据状态变化的自定义页面路由:
kotlin
// 定义页面枚举或密封类
sealed class Screen {
object List : Screen()
data class Detail(val articleId: String) : Screen()
}
@Composable
fun MyApp() {
// 维护当前的页面状态, 默认是 列表页面
var currentScreen by remember { mutableStateOf<Screen>(Screen.List) }
// 根据状态决定渲染哪个页面
when (val screen = currentScreen) {
is Screen.List -> {
ArticleListScreen(
onArticleClick = { id ->
// 切换状态,触发重组,UI 就会"跳"到详情页
currentScreen = Screen.Detail(id)
}
)
}
is Screen.Detail -> {
ArticleScreen(
articleId = screen.articleId,
onBack = { currentScreen = Screen.List } // 提供返回逻辑
)
}
}
}
文章列表页的 可组合函数,(省略 ArticleListViewModel )
kotlin
@Composable()
fun ArticleListScreen(viewModel: ArticleListViewModel = viewModel(), onArticleClick: (String) -> Unit) {
val list: List<ArticleSimpleData> = ... // 从vm获取 stateFlow 文章列表
LazyColumn { // 列表
items(list) { item ->
Box(modifier = Modifier.clickable {
onArticleClick(item.id)
})
}
}
}
文章详情页的 ViewModel 和 可组合函数:
kotlin
// 逻辑层:ViewModel 负责业务和核心状态恢复
class ArticleViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// SavedStateHandle 保存核心状态: 文章ID
private val articleId = savedStateHandle.getStateFlow("ARTICLE_ID", "")
// StateFlow 负责暴露庞大/复杂的业务数据
val articleContent: StateFlow<Article> = articleId.flatMapLatest { id ->
repository.fetchArticle(id) // 从网络或数据库获取大体量数据
}.stateIn(viewModelScope, SharingStarted.Lazily, Article.Empty)
fun updateArticleId(id: String) {
savedStateHandle["ARTICLE_ID"] = id
}
}
@Composable
fun ArticleScreen(viewModel: ArticleViewModel = viewModel(), articleId: String, onBack: () -> Unit) {
// 观察业务逻辑状态
val article by viewModel.articleContent.collectAsStateWithLifecycle()
// rememberSaveable 负责纯 UI 的状态(比如用户是否点击了"点赞"的临时动画状态,或者阅读到的滚动位置)
var isImageZoomed by rememberSaveable { mutableStateOf(false) }
// 动画:当 isImageZoomed 改变时,scale 会在 1f 和 2.5f 之间平滑过渡
val scale by animateFloatAsState(
targetValue = if (isImageZoomed) 2.5f else 1f,
// 自定义动画规格(增加一点回弹效果)
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy),
label = "ImageZoomAnimation"
)
// 文章详情
Column {
Text(article.title)
Image(
// ...
modifier = Modifier
// 图层缩放,跳过布局阶段,只在绘制阶段生效
.graphicsLayer(
scaleX = scale,
scaleY = scale
)
.clickable { isImageZoomed = !isImageZoomed }
)
}
// LaunchedEffect 会在 Composable 首次显示或 articleId 改变时执行
LaunchedEffect(articleId) {
viewModel.updateArticleId(articleId)
}
}
ViewModel 的生命周期
ViewModel 的生命周期并不默认与某个具体的 Composable 函数绑定,它的生命周期级别取决于它被存储在哪个 ViewModelStoreOwner 中
- 默认行为:Activity 级别
直接在 Composable 中调用 viewModel()(来自 androidx.lifecycle.viewmodel.compose)时,它底层会去寻找最近的 ViewModelStoreOwner。
在没有使用官方 Navigation 库的情况下,这个 Owner 通常就是所在的ComponentActivity
如上示例 ArticleViewModel、ArticleListViewModel 本质上都是 相对于所在 Activity 的单例对象
- 使用 Navigation 库时:Destination 页面级别
使用了 androidx.navigation.compose,情况会有所不同。
机制:导航图中的每一个"目的地"(Destination)都会被包装成一个 NavBackStackEntry,而这个 Entry 本身实现了 ViewModelStoreOwner。
结果:此时在 Composable 中调用 viewModel(),它获取的是绑定在当前"页面路由"上的实例。当用户点击返回键,该路由从回退栈弹出时,对应的 ViewModel 才会执行 onCleared()。
级别:这种情况下,它的级别比 Activity 低,是"页面级"的