首页 / Android 入门教程 / Activity 状态保存

Android 入门教程

Activity 状态保存

本教程共 100 篇 · 第 26 篇 · 更新于 2026-07-28 · 约 8 分钟阅读

AndroidAndroid 入门教程状态保存onSaveInstanceStateViewModelrememberSaveableBundle

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。onCreatesavedInstanceState 可能为 null,要判。

Bundle 能存什么

Bundle 只能存基本类型和可序列化对象:

  • 基本类型:putIntputStringputBooleanputLong 等。
  • 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 是同一个实例,scorecurrentQuestion 不丢。

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("密码") }
    )
}

旋转屏幕后,输入的内容还在。rememberSaveableremember 区别:

  • 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。三层配合最稳。下一节学任务与返回栈。