SavedInstanceState 机制:AMS在异常销毁时保存与恢复Activity状态
大家好,今天我们来聊一个老生常谈、但很多同学其实没完全搞懂的话题——SavedInstanceState。
说白了,就是当你的Activity被系统“强行”杀掉时,怎么把现场保住。我见过不少项目,上线后用户反馈“填了一堆表单,切出去回来看全没了”,十有八九就是没处理好这个机制。
一、什么时候会触发异常销毁?
先搞清楚一个问题:Activity什么时候会被系统“异常销毁”?
- 配置变更:屏幕旋转、语言切换、键盘弹出等。默认会重建Activity。
- 内存不足:后台App被系统回收,尤其是低端机上。
- 进程被杀:用户主动杀进程,或者系统为了资源回收杀掉你的进程。
你想想看,用户正在填一个复杂的注册表单,突然来个电话,接完回来发现Activity重建了,表单全空了——这体验能忍吗?
核心结论:异常销毁时,系统会调用 onSaveInstanceState() 保存状态;重建时,通过 onCreate() 或 onRestoreInstanceState() 恢复状态。
二、AMS在其中的角色
很多人以为SavedInstanceState是Activity自己搞的,其实背后是AMS(ActivityManagerService)在调度。
我简单梳理一下流程:
- AMS检测到Activity需要销毁(比如内存不足),会通知ActivityThread。
- ActivityThread调用Activity的
onSaveInstanceState(),把状态打包成Bundle。 - 这个Bundle被传回AMS,AMS把它存到内部的一个Record里。
- 当用户再次回到这个Activity时,AMS把Bundle传回给新创建的Activity实例。
- 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 完整生命周期
下面这张图是我自己画的,把整个流程串起来了:
八、实战中的避坑指南
最后分享几个我踩过的坑:
- 不要依赖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会被系统随时杀掉,做好状态保存,才能给用户流畅的体验。