在做 Android 页面时遇到过一个很容易忽略的问题:输入框里已经输入了一些内容,或者页面切换到了某个选项卡,一旋转屏幕,页面重新加载以后状态就没有了。
这个现象并不是系统把界面“刷新”了一次那么简单。默认情况下,屏幕方向变化属于配置改变,当前 Activity 会被销毁并重新创建。如果页面状态只保存在成员变量中,新的实例自然无法知道旧页面发生过什么。
先观察生命周期
可以在生命周期方法中打印日志:
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d("StateDemo", "onCreate");}@Overrideprotected void onDestroy() { super.onDestroy(); Log.d("StateDemo", "onDestroy");}
旋转屏幕以后会看到旧页面走过 onDestroy(),紧接着新的页面又走到 onCreate()。所以问题的本质是:如何把重建前的重要状态传给新的页面实例。
使用 onSaveInstanceState 保存临时界面状态
对于当前选中的 tab、列表滚动位置、尚未提交的筛选条件这类临时界面状态,可以通过 onSaveInstanceState() 保存:
private static final String KEY_SELECTED_TAB = "selected_tab";private static final String KEY_KEYWORD = "keyword";private int mSelectedTab = 0;@Overrideprotected void onSaveInstanceState(Bundle outState) { outState.putInt(KEY_SELECTED_TAB, mSelectedTab); outState.putString(KEY_KEYWORD, mSearchEditText.getText().toString()); super.onSaveInstanceState(outState);}
在 onCreate() 中判断 savedInstanceState 是否为空,再将状态恢复回来:
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSearchEditText = (EditText) findViewById(R.id.search_edit_text); if (savedInstanceState != null) { mSelectedTab = savedInstanceState.getInt(KEY_SELECTED_TAB, 0); String keyword = savedInstanceState.getString(KEY_KEYWORD, ""); mSearchEditText.setText(keyword); showTab(mSelectedTab); }}
部分系统控件,例如设置了 id 的 EditText,系统本身能够恢复一定状态。但如果页面状态会影响业务展示,最好仍然明确知道哪些字段需要保存,而不是完全依赖默认行为。
Bundle 适合保存什么
Bundle 适合保存数量不大、能够快速序列化的页面状态,例如:
- 选中的位置或 tab 下标
- 输入框中的短文本
- 是否展开某个区域
- 当前展示的数据 id
它并不适合保存大图、完整列表数据或大量网络返回内容。把这些对象都塞进状态中,会让重建过程变得沉重,也可能触发 TransactionTooLargeException 一类问题。
更合适的做法是保存能够重新找到数据的关键标识,例如一条内容的 id,页面重新创建后再从缓存、数据库或网络加载详情。
状态与数据不是一回事
这次问题让我比较明确地意识到,页面上看起来都属于“内容”的东西,实际上可以分成两类:
- 界面状态:当前滚到哪里、选中了什么、输入到一半的文字。
- 业务数据:文章详情、用户资料、服务器返回的列表。
界面状态适合在页面重建时保存与恢复;业务数据则应该有自己的来源和缓存方式。把这两类东西混在一起,页面稍微复杂以后就很难维护。
是否禁止 Activity 重建
也可以在配置中自行处理屏幕方向变化,避免系统重新创建 Activity。但这样只是把问题转移到了自己手上:资源切换、尺寸变化和布局更新都需要正确处理。
对于普通页面,理解并正确处理状态恢复通常更加可靠,也更符合系统的工作方式。
小结
屏幕旋转导致内容丢失,表面上是一个小问题,背后却是在提醒我们:一个页面并不是永远存在的实例。
在 Activity 可能重建的前提下,明确哪些是短暂的界面状态、哪些是需要重新获取的数据,再使用 onSaveInstanceState() 保存必要信息,页面行为才会更加稳定。