Activity 状态保存
本教程共 100 篇 · 第 26 篇 · 更新于 2026-07-28 · 约 8 分钟阅读
26. Activity 状态保存
本节目标:搞懂为什么 Activity 状态会丢、用 onSaveInstanceState/onRestoreInstanceState 怎么存、ViewModel 和 rememberSaveable 各自保存什么、怎么配合用。
为什么状态会丢
Activity 在这些情况会销毁重建,状态丢失:
- 旋转屏幕:默认配置变更会销毁重建 Activity。
- 系统回收内存:后台 Activity 被系统杀,用户返回时重建。
- 语言切换、深色模式切换:配置变更触发重建。
如果你在输入框打了半天的字、滚动到列表中间,重建后全没了,用户体验很差。
两类状态,两种保存方式
Android 状态保存分两类,用不同方案:
| 状态类型 | 例子 | 保存方式 |
|---|---|---|
| 瞬时 UI 状态 | 输入框文字、滚动位置、选中项 | onSaveInstanceState / rememberSaveable |
| 持久化数据 | 用户信息、购物车、设置 | ViewModel + 数据库/SP |
关键区别:
onSaveInstanceState存到系统进程的 Bundle,Activity 重建时恢复,但系统杀进程后也丢,且有大小限制(约 1MB,建议几 KB)。- ViewModel 存在内存里,配置变更(旋转)不丢,但系统杀进程时也丢。
- 数据库/文件才是真正持久化。
onSaveInstanceState 与 onRestoreInstanceState
保存状态
onSaveInstanceState 在 Activity 可能被销毁前调用(onStop 之前)。把要保存的数据放进 Bundle:
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("draft_text", editText.text.toString())
outState.putInt("scroll_position", scrollView.scrollY)
outState.putBoolean("is_checked", checkBox.isChecked)
}
Note
- Android Studio 用
Bundle时代码很啰嗦。现代项目用 Compose 的rememberSaveable更简洁,下面讲。
恢复状态
两种方式:
方式一:onCreate 里取
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val draft = savedInstanceState?.getString("draft_text") ?: ""
val scroll = savedInstanceState?.getInt("scroll_position") ?: 0
editText.setText(draft)
scrollView.scrollTo(0, scroll)
}
方式二:onRestoreInstanceState 里取
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
val draft = savedInstanceState.getString("draft_text") ?: ""
editText.setText(draft)
}
onRestoreInstanceState 只在有保存状态时才调用,不用判 null。onCreate 里 savedInstanceState 可能为 null,要判。
Bundle 能存什么
Bundle 只能存基本类型和可序列化对象:
- 基本类型:
putInt、putString、putBoolean、putLong等。 putParcelable:实现Parcelable的对象。putSerializable:实现Serializable的对象(慢,不推荐)。
Warning
- 别把大对象(图片、大列表)塞进 Bundle,会崩
TransactionTooLargeException。大数据用数据库或文件。
ViewModel 的角色
ViewModel 设计目的就是在配置变更(旋转)时保留数据。它跟 Activity 生命周期解耦,Activity 重建时 ViewModel 不销毁。
class GameViewModel : ViewModel() {
var score = 0
var currentQuestion = 0
private val _uiState = MutableStateFlow(GameState())
val uiState = _uiState.asStateFlow()
fun answer(correct: Boolean) {
if (correct) score++
currentQuestion++
}
}
class GameActivity : ComponentActivity() {
private val viewModel: GameViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val state by viewModel.uiState.collectAsStateWithLifecycle()
GameScreen(state, viewModel::answer)
}
}
}
旋转屏幕时 Activity 重建,但 viewModel 是同一个实例,score 和 currentQuestion 不丢。
ViewModel 也会丢
ViewModel 挺过配置变更,但系统杀进程时也丢。用户切到后台很久,系统回收内存杀了进程,回来时 ViewModel 是新建的,数据没了。
所以:
- 短期、跟 UI 强相关的数据 -> ViewModel。
- 真正要持久化的数据 -> 数据库 / DataStore / 文件。
- 瞬时 UI 状态(输入框文字)->
onSaveInstanceState/rememberSaveable。
Compose 的 rememberSaveable
Compose 里保存 UI 状态用 rememberSaveable,它会自动通过 onSaveInstanceState 机制保存:
@Composable
fun LoginScreen() {
var email by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") }
TextField(
value = email,
onValueChange = { email = it },
label = { Text("邮箱") }
)
TextField(
value = password,
onValueChange = { password = it },
label = { Text("密码") }
)
}
旋转屏幕后,输入的内容还在。rememberSaveable 跟 remember 区别:
remember:配置变更(旋转)时丢失。rememberSaveable:配置变更时自动保存恢复。
自定义可保存类型
rememberSaveable 默认能存基本类型和 Parcelable/Serializable。自定义类型要实现 Parcelable 或提供自定义 Saver:
data class City(val name: String, val population: Int) : Parcelable {
// 实现 Parcelable 的方法
}
// 或用 Saver
val CitySaver = Saver<City, List<Any>>(
save = { listOf(it.name, it.population) },
restore = { City(it[0] as String, it[1] as Int) }
)
var city by rememberSaveable(stateSaver = CitySaver) {
mutableStateOf(City("北京", 21540000))
}
完整策略:三层保存
健壮的状态保存要三层配合:
1. 数据库 / DataStore 真正持久化,进程死了也在
↑
2. ViewModel 跨配置变更保留,进程被杀才丢
↑
3. rememberSaveable 跨进程被杀保留(Bundle),小数据
实战示例:
// ViewModel 存业务状态
class NoteViewModel(private val repo: NoteRepository) : ViewModel() {
var draftContent by mutableStateOf("") // 草稿,配置变更不丢
fun saveNote() {
viewModelScope.launch {
repo.save(draftContent) // 真正存数据库
}
}
}
// Compose 里瞬时 UI 状态用 rememberSaveable
@Composable
fun NoteScreen(viewModel: NoteViewModel) {
var title by rememberSaveable { mutableStateOf("") } // 旋转保留
// 注意:viewModel.draftContent 在 ViewModel 里,配置变更也保留
TextField(value = title, onValueChange = { title = it })
TextField(value = viewModel.draftContent, onValueChange = { viewModel.draftContent = it })
}
阻止重建:configChanges
某些配置变更(如旋转)可以声明「我自己处理,别重建」:
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden" />
这样旋转时不重建,回调 onConfigurationChanged。但官方不推荐,因为你得手动处理所有布局调整。让系统重建 + 状态保存是更稳妥的做法。
Tip
- 现代项目很少用
configChanges。Compose 的自适应布局能自动响应屏幕变化,配合状态保存,旋转体验已经很好了。
小结
状态丢失发生在配置变更和系统杀进程。瞬时 UI 状态用 onSaveInstanceState(传统)或 rememberSaveable(Compose),存小数据进 Bundle。ViewModel 跨配置变更保留业务数据,但系统杀进程时丢。真正持久化用数据库/DataStore。三层配合最稳。下一节学任务与返回栈。