3. 屏幕旋转机制

屏幕旋转是 Android 设备最常见的配置变更之一。当用户把手机从竖屏转为横屏,系统不仅会改变显示方向,还会触发一系列与 Activity 生命周期、资源加载和状态保存相关的连锁反应。理解这套机制,是做好旋转适配和分辨率适配的基础。

一、核心原理:配置变更与 Activity 重建

Android 将屏幕方向、屏幕尺寸、语言、字体缩放、键盘可用性等统称为“配置”(Configuration)。当这些配置发生变化时,系统默认认为当前 Activity 的界面资源可能不再适用,于是会销毁并重新创建 Activity,以便重新加载匹配新配置的资源。

这个默认行为可以概括为:配置变更 → Activity 销毁 → 重新创建 → 重新加载资源 → 恢复状态。其中最关键的一点是:Activity 的销毁并不是因为用户退出,而是系统为了适配新配置而主动发起的重建。

配置变更默认触发 Activity 重建,重建过程会依次经历 onPause → onStop → onDestroy → onCreate → onStart → onResume。如果开发者没有正确处理状态保存,旋转后界面数据可能丢失。

二、生命周期变化:旋转时的完整回调顺序

假设当前 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。

使用 onConfigurationChanged 可以避免重建,适合游戏、视频播放器、相机等需要保持连续状态的场景。但它也意味着开发者要手动处理所有布局和资源切换,复杂度更高。

四、重建流程中的资源匹配与状态恢复

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 保存。
旋转适配的核心不是“禁止重建”,而是“正确应对重建”。能重建时优先重建,让系统自动匹配资源;确实需要连续状态时,再使用 configChanges 或 ViewModel 辅助。
屏幕旋转:配置变更与 Activity 重建流程 竖屏运行中 Activity onResume 用户旋转屏幕 Configuration 变化 系统判断处理方式 是否声明 configChanges 默认:Activity 重建 onPause → onStop → onDestroy onCreate → onStart → onResume 重新匹配 layout-land / layout Bundle / ViewModel 恢复状态 声明 configChanges:不重建 Activity 保持存活 回调 onConfigurationChanged() 手动更新布局与资源 需自行处理所有适配逻辑 核心结论:默认重建优先匹配资源;声明 configChanges 可避免重建,但需手动适配

总结来说,屏幕旋转机制的关键在于理解“配置变更”与“Activity 重建”之间的关系。默认重建让系统自动完成资源匹配,适合大多数页面;而 onConfigurationChanged 提供了不重建的替代路径,适合需要保持连续状态的场景。无论选择哪种方式,都要妥善处理状态保存与恢复,避免旋转后数据丢失或重复加载。