前言
上一课学习了 if 条件渲染和 LazyColumn 懒加载列表,掌握动态UI与长列表渲染。
我们一直用 remember 保存状态,但它有一个致命缺陷:手机旋转屏幕、系统因内存不足重建Activity时,remember保存的数据会全部丢失,重置为初始值。
本课学习 rememberSaveable ,它能在系统重建(配置变更、进程重启)时自动保存和恢复状态,是真实安卓开发必须掌握的知识点。
前置:完成1~7课,掌握remember、状态、基础布局与列表
一、完整可运行示例源码
kotlin
package com.example.composefirst
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SaveableDemoPage()
}
}
}
@Composable
fun SaveableDemoPage() {
// rememberSaveable:系统重建时自动保存恢复
var count by rememberSaveable { mutableIntStateOf(0) }
Column(modifier = Modifier.padding(20.dp)) {
Text(text = "当前计数:$count")
Spacer(modifier = Modifier.height(12.dp))
Button(onClick = { count++ }) {
Text("+1")
}
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = { count = 0 }) {
Text("重置")
}
}
}
@Preview
@Composable
fun SaveablePreview() {
SaveableDemoPage()
}
二、逐行代码解读
- rememberSaveable { mutableIntStateOf(0) }
和 remember 用法几乎一模一样,唯一区别:数据会被序列化保存到Bundle中,Activity因配置变更、系统重建的时候,可以自动恢复数据。
导入路径: androidx.compose.runtime.saveable.rememberSaveable - remember 丢失数据的场景
旋转屏幕、切换系统深色模式、修改系统字体大小、切换系统语言,这些操作都会触发安卓配置变更,销毁并且重建Activity。
remember仅仅保存在运行时内存当中,页面重建之后内存清空,状态直接归零。 - rememberSaveable 底层原理
封装安卓原生 onSaveInstanceState 保存机制,把状态序列化放进Bundle,页面重建之后,从Bundle读取数据恢复状态。
存在功能限制:只能存储可以序列化的数据。整型、字符串、布尔值等基础类型开箱即用;自定义实体类需要实现Parcelable,或者手动提供Saver保存器。 - 简单区分 rememberSaveable 和 ViewModel
rememberSaveable 适合少量UI状态,开箱即用,代码简短。
ViewModel 适合大批量业务数据、跨页面共享数据,生命周期更长,旋转屏幕不会丢失。ViewModel属于架构进阶内容,后续课程再详细讲解。
三、核心概念文字对比
remember:重组的时候可以保留状态;屏幕旋转、页面重建数据丢失;数据类型没有限制;适合页面临时状态。
rememberSaveable:重组的时候可以保留状态;屏幕旋转、页面重建自动恢复;数据需要支持序列化;适合表单、计数器等需要防丢失的数据。
记忆口诀:临时状态用remember,怕丢数据用rememberSaveable。
四、综合练习题
填空题
- 能够在旋转屏幕、系统重建时自动保存恢复状态的函数是________。
- rememberSaveable 底层基于安卓传统的________机制,把数据存入Bundle。
- rememberSaveable 要求保存的数据类型必须是________的,自定义类需要实现Parcelable。
答案与解析
- rememberSaveable ;解析:专门用于跨配置变更、系统重建保存状态。
- onSaveInstanceState ;解析:安卓传统保存实例状态机制,Compose封装后自动调用。
- 可序列化;解析:Bundle只能存可序列化数据,基础类型默认支持,自定义类需Parcelable。
判断题
- remember 和 rememberSaveable 在普通重组时都能保留数据,效果一样。()
答案:正确。理由:两者在Compose重组时都会保存数据,区别只在系统重建场景。
- 用remember保存的计数器,旋转屏幕后数值会重置为初始值。()
答案:正确。理由:remember只存在内存中,Activity销毁重建后内存数据清空。
- rememberSaveable 可以保存任意类型的数据,包括不可序列化的对象。()
答案:错误。理由:rememberSaveable基于Bundle存储,数据必须可序列化,不可序列化对象会运行时报错。
简答题
- 什么场景下应该使用rememberSaveable而不是remember?举两个实际例子。
参考答案:
当状态需要在系统重建(旋转屏幕、语言切换、深色模式切换、内存回收重建)后保留时使用。
例子1:用户在表单页输入了一半内容,旋转屏幕后不能清空;
例子2:计数器页面,用户点了很多次,旋转屏幕后数值要保留。
代码实操题
把第7课的待办列表示例改造:输入文本和待办列表都使用rememberSaveable保存,确保旋转屏幕后数据不丢失。写出关键状态定义代码。参考答案
kotlin
@Composable
fun TodoListPage() {
var inputText by rememberSaveable { mutableStateOf("") }
var todoList by rememberSaveable { mutableStateOf(listOf<String>()) }
fun addTodo(item: String) {
todoList = todoList + item
}
fun removeTodo(item: String) {
todoList = todoList - item
}
// ... 其余UI代码和第7课一致
}
解析:
inputText是字符串,默认可序列化,直接使用rememberSaveable。
todoList采用不可变List,增删时生成全新集合进行赋值,正常触发重组。
注意:mutableStateListOf直接套rememberSaveable存在兼容隐患,练习场景优先选用不可变列表。
五、本课小结
- remember 仅重组保数据,屏幕旋转、页面重建数据丢失。
- rememberSaveable 依托安卓实例保存机制,系统重建自动恢复状态。
- 存入的数据需要支持序列化,自定义对象需要额外实现Parcelable。
- 选型参考:临时界面状态用remember,需要防丢失的数据选用rememberSaveable。
下一课:SideEffect副作用 + LaunchedEffect,处理异步任务和生命周期。