SavedInstanceState 机制:AMS在异常销毁时保存与恢复Activity状态

大家好,今天我们来聊一个老生常谈、但很多同学其实没完全搞懂的话题——SavedInstanceState

说白了,就是当你的Activity被系统“强行”杀掉时,怎么把现场保住。我见过不少项目,上线后用户反馈“填了一堆表单,切出去回来看全没了”,十有八九就是没处理好这个机制。

一、什么时候会触发异常销毁?

先搞清楚一个问题:Activity什么时候会被系统“异常销毁”?

  • 配置变更:屏幕旋转、语言切换、键盘弹出等。默认会重建Activity。
  • 内存不足:后台App被系统回收,尤其是低端机上。
  • 进程被杀:用户主动杀进程,或者系统为了资源回收杀掉你的进程。

你想想看,用户正在填一个复杂的注册表单,突然来个电话,接完回来发现Activity重建了,表单全空了——这体验能忍吗?

核心结论:异常销毁时,系统会调用 onSaveInstanceState() 保存状态;重建时,通过 onCreate()onRestoreInstanceState() 恢复状态。

二、AMS在其中的角色

很多人以为SavedInstanceState是Activity自己搞的,其实背后是AMS(ActivityManagerService)在调度。

我简单梳理一下流程:

  1. AMS检测到Activity需要销毁(比如内存不足),会通知ActivityThread。
  2. ActivityThread调用Activity的 onSaveInstanceState(),把状态打包成Bundle。
  3. 这个Bundle被传回AMS,AMS把它存到内部的一个Record里。
  4. 当用户再次回到这个Activity时,AMS把Bundle传回给新创建的Activity实例。
  5. Activity在 onCreate()onRestoreInstanceState() 中拿到Bundle,恢复数据。

嗯,这里要注意:AMS只是“搬运工”,它不关心Bundle里装了什么。真正干活的是Activity自己。

三、onSaveInstanceState 的调用时机

我经常被问到:“onSaveInstanceState到底什么时候调用?”

官方文档说得很清楚:在Activity可能被销毁之前。但具体是哪个时间点?

看源码你会发现,它是在 onPause() 之后、onStop() 之前被调用的。但有个例外:如果你主动调用 finish(),系统不会调用 onSaveInstanceState()——因为你是主动销毁,不需要保存。

避坑指南:我曾经遇到一个bug,在 onPause() 里释放了某些资源,结果 onSaveInstanceState() 调用时资源已经没了,导致保存的数据不完整。后来我把资源释放挪到了 onStop() 里才解决。

四、保存什么?怎么保存?

系统默认会帮你保存一些东西:比如EditText的文本、ListView的滚动位置、CheckBox的选中状态等。但自定义的数据,你得自己动手。

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putString("user_name", mUserName);
    outState.putInt("current_step", mCurrentStep);
    outState.putSerializable("form_data", mFormData);
}

恢复时有两种方式:

// 方式一:在onCreate中恢复
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState != null) {
        mUserName = savedInstanceState.getString("user_name");
        mCurrentStep = savedInstanceState.getInt("current_step");
        mFormData = (FormData) savedInstanceState.getSerializable("form_data");
    }
}

// 方式二:在onRestoreInstanceState中恢复
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    // 这里恢复,onCreate已经执行完了,视图已经创建好
    mUserName = savedInstanceState.getString("user_name");
}

我个人习惯用 onRestoreInstanceState(),因为这时候视图已经初始化完毕,可以直接操作UI组件。但如果你只是恢复数据变量,在 onCreate() 里做也没问题。

五、Bundle的容量限制

这里有个坑,我踩过。Bundle底层是Binder传输,Binder有大小限制——通常1MB左右。如果你往Bundle里塞了大量数据(比如一张大图的Bitmap),会直接抛 TransactionTooLargeException

警告:不要往SavedInstanceState里存大对象!Bitmap、长字符串、大数组都不行。我曾经见过一个项目把整个网络请求结果(JSON字符串)存进去,结果在低端机上频繁崩溃。

解决方案:

  • 只保存关键状态(比如ID、索引),具体数据从数据库或网络重新加载。
  • 如果非要保存大数据,考虑用 onRetainNonConfigurationInstance()(已废弃)或ViewModel。
  • 或者把数据写到本地文件,只保存文件路径。

六、ViewModel vs SavedInstanceState

很多同学问:有了ViewModel,是不是就不用SavedInstanceState了?

不是的。ViewModel在配置变更(比如旋转屏幕)时能存活,但进程被杀死后,ViewModel也会被销毁。这时候SavedInstanceState是唯一的救命稻草。

场景 ViewModel SavedInstanceState
屏幕旋转 存活 可用(但通常不需要)
内存不足杀进程 销毁 必须用
用户主动杀进程 销毁 不会触发保存

所以最佳实践是:ViewModel + SavedInstanceState 配合使用。ViewModel处理配置变更,SavedInstanceState兜底进程被杀的情况。

七、流程图:SavedInstanceState 完整生命周期

下面这张图是我自己画的,把整个流程串起来了:

SavedInstanceState 完整生命周期流程图 Activity 正常运行 AMS 决定销毁 Activity onSaveInstanceState() 调用 Bundle 传回 AMS 存储 Activity 被销毁 用户重新打开 Activity AMS 传回 Bundle onCreate / onRestoreInstanceState 虚线表示进程被杀后的恢复路径

八、实战中的避坑指南

最后分享几个我踩过的坑:

  • 不要依赖onSaveInstanceState一定会调用:用户按Back键、主动finish()时不会调用。所以别把“保存”逻辑全押在这上面。
  • Fragment的SavedInstanceState:Fragment也有自己的SavedInstanceState机制,但要注意FragmentManager会帮你保存一部分状态。如果你用add/remove动态管理Fragment,记得在onSaveInstanceState里保存Fragment的tag或id。
  • 序列化问题:自定义对象要实现Serializable或Parcelable。我建议用Parcelable,性能更好。但如果你对象结构复杂,可以考虑用Gson转成JSON字符串再存——不过要注意大小限制。
  • 测试方法:开发者选项里有个“不保留活动”,打开后切换到后台就会销毁Activity。用这个功能测试你的SavedInstanceState逻辑是否完善。

个人经验:我曾经维护过一个电商App,用户选好商品、填好地址、选好优惠券,切出去回来看全没了。排查发现是Fragment嵌套太深,SavedInstanceState没正确传递。后来我统一用ViewModel + SavedStateHandle 才彻底解决。

好了,关于SavedInstanceState机制就聊这么多。记住一句话:永远假设你的Activity会被系统随时杀掉,做好状态保存,才能给用户流畅的体验。

公众号:蓝海资料掘金营,微信deep3321