3. 屏幕旋转机制
屏幕旋转是 Android 设备最常见的配置变更之一。当用户把手机从竖屏转为横屏,系统不仅会改变显示方向,还会触发一系列与 Activity 生命周期、资源加载和状态保存相关的连锁反应。理解这套机制,是做好旋转适配和分辨率适配的基础。
一、核心原理:配置变更与 Activity 重建
Android 将屏幕方向、屏幕尺寸、语言、字体缩放、键盘可用性等统称为“配置”(Configuration)。当这些配置发生变化时,系统默认认为当前 Activity 的界面资源可能不再适用,于是会销毁并重新创建 Activity,以便重新加载匹配新配置的资源。
这个默认行为可以概括为:配置变更 → Activity 销毁 → 重新创建 → 重新加载资源 → 恢复状态。其中最关键的一点是:Activity 的销毁并不是因为用户退出,而是系统为了适配新配置而主动发起的重建。
二、生命周期变化:旋转时的完整回调顺序
假设当前 Activity 处于前台运行状态,用户旋转屏幕后,默认情况下会经历如下回调顺序:
onPause()
onStop()
onDestroy()
onCreate()
onStart()
onRestoreInstanceState()
onResume()
其中,onCreate() 会重新执行,setContentView() 会再次调用,布局文件会根据新的屏幕方向重新选择。例如,res/layout/activity_main.xml 用于竖屏,res/layout-land/activity_main.xml 用于横屏。系统会自动匹配对应目录下的资源。
如果开发者希望在重建前保存临时数据,可以在 onSaveInstanceState() 中写入 Bundle;重建后,系统会在 onCreate() 或 onRestoreInstanceState() 中把 Bundle 传回。这样,用户输入、滚动位置、选中项等状态就能恢复。
三、onConfigurationChanged 回调:不重建的替代方案
如果开发者不希望 Activity 因旋转而重建,可以在 AndroidManifest.xml 中为 Activity 声明 android:configChanges 属性。声明后,旋转时系统不会销毁 Activity,而是回调 onConfigurationChanged(Configuration newConfig)。
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|keyboardHidden" />
在代码中重写回调:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
// 处理横屏逻辑
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
// 处理竖屏逻辑
}
}
需要注意的是,orientation 只表示方向变化,screenSize 表示屏幕尺寸变化。从 Android 3.2 开始,旋转通常同时伴随 screenSize 变化,因此只声明 orientation 往往不够,建议同时声明 orientation|screenSize。
四、重建流程中的资源匹配与状态恢复
Activity 重建时,系统会重新执行资源匹配。资源匹配遵循“最具体优先”原则:layout-land 比 layout 更具体,drawable-hdpi 比 drawable 更具体。如果横屏目录不存在,系统会回退到默认目录。
状态恢复方面,View 自身可以通过 android:id 自动保存部分状态,例如 EditText 的文本、CheckBox 的选中状态。但复杂对象、网络请求结果、临时列表等,需要开发者手动写入 Bundle 或使用 ViewModel 保存。
| 处理方式 | 是否重建 | 生命周期回调 | 适用场景 |
|---|---|---|---|
| 默认行为 | 是 | onDestroy → onCreate | 普通页面、资源需要重新匹配 |
| 声明 configChanges | 否 | onConfigurationChanged | 游戏、视频、相机等连续状态 |
| ViewModel + LiveData | 是 | onDestroy → onCreate | 需要跨重建保存数据 |
| onSaveInstanceState | 是 | onSaveInstanceState → onCreate | 轻量级 UI 状态保存 |
五、案例场景:旋转后数据丢失的排查
某应用在竖屏下输入搜索关键词,旋转到横屏后输入框内容消失。排查发现,Activity 没有声明 configChanges,旋转时发生了重建,而 EditText 虽然有 id,但输入内容没有被正确保存。解决方案有两种:一是给 EditText 设置稳定的 id,让系统自动保存;二是在 onSaveInstanceState() 中手动保存文本,并在 onCreate() 中恢复。
另一个常见场景是:旋转后网络请求重复发起。原因是 Activity 重建导致 onCreate() 再次执行,如果请求逻辑写在 onCreate 中,就会重复请求。此时应使用 ViewModel 持有请求状态,或通过 isFinishing()、标志位判断避免重复加载。
六、误区与复盘
- 误区一:认为旋转只是 UI 变化,不会影响生命周期。实际上默认会触发完整的销毁重建流程。
- 误区二:只声明
orientation不声明screenSize。在 Android 3.2 及以上,旋转通常同时触发 screenSize 变化,声明不完整仍会重建。 - 误区三:在
onConfigurationChanged()中不更新布局。声明 configChanges 后,系统不会自动重新加载布局,必须手动处理。 - 误区四:把大量数据放在 Activity 成员变量中。重建后成员变量会重新初始化,应使用 ViewModel 或 Bundle 保存。
总结来说,屏幕旋转机制的关键在于理解“配置变更”与“Activity 重建”之间的关系。默认重建让系统自动完成资源匹配,适合大多数页面;而 onConfigurationChanged 提供了不重建的替代路径,适合需要保持连续状态的场景。无论选择哪种方式,都要妥善处理状态保存与恢复,避免旋转后数据丢失或重复加载。