当设备旋转时,Activity 的生命周期如何变化?如何避免不必要的重建?

解读

面试官抛出此题,核心想验证三件事:

  1. 对 Activity 生命周期回调顺序是否“倒背如流”;
  2. 是否真正理解“配置变更”背后的系统行为,而非死记“旋转=重启”;
  3. 能否结合国内多 ROM、多屏幕形态(折叠屏、平板、车载)给出落地方案,而不是只背官方文档。
    旋转屏幕在国内场景下还会叠加厂商“强制横屏”“虚拟导航条隐藏/显示”“深色模式切换”等额外配置,导致一次旋转可能触发多次重建,候选人若能把这些“坑”讲出来,会立刻拉开差距。

知识点

  1. 默认流程:
    onPause → onStop → onSaveInstanceState(Bundle) → onDestroy → 新实例 onCreate → onStart → onRestoreInstanceState(Bundle) → onResume。
    关键点:
    • onSaveInstanceState 在旧实例任意线程终止前都会调用,数据落地到主线程 Binder 事务,大小限制 1 MB 左右(国内部分 ROM 裁剪到 512 KB)。
    • onRestoreInstanceState 的 Bundle 与 onCreate 参数是同一对象,但只在“系统主动回收后重建”时非空;单纯旋转屏幕不会触发 Empty Process 回收,所以内存充足时 Bundle 不会为 null。
  2. 配置变更类别:
    orientation|screenSize|screenLayout|smallestScreenSize|density|keyboardHidden|uiMode(深色切浅色)等。
    国内常见“坑”:
    • 华为/小米折叠屏展开时,同时触发 screenSize + smallestScreenSize + density,一次展开=三次重建。
    • OPPO 虚拟导航条自动隐藏会触发 screenLayout,导致横竖屏切换时多一次重建。
  3. 保留实例的四种官方手段:
    • ViewModel:生命周期感知,旋转后同作用域复用,但仅内存内保留,进程被杀后丢失。
    • onSaveInstanceState:持久化轻量数据,适合文本、滚动位置等。
    • android:configChanges 手动处理:需重写 onConfigurationChanged,并在 Manifest 中声明要自行处理的变更;国内上架应用市场时,华为审核明确要求“不得滥用 configChanges 规避重建”,否则会被打回。
    • retained Fragment(setRetainInstance(true)):已标记废弃,官方不再推荐。
  4. 国内合规与性能:
    • 工信部 164 号文要求“不得强制竖屏”,必须支持传感器旋转;若使用 configChanges 强制锁定,需在应用内提供“手动旋转”入口,否则会被通报。
    • 国内 ROM 后台管控严格,若旋转时占用内存 > 400 MB,极容易被系统杀进程,导致重建后 Bundle 为 null,必须做双重兜底(本地持久化 + 网络缓存)。

答案

“设备旋转属于配置变更,默认情况下系统会销毁当前 Activity 并重新创建新实例,完整生命周期为:
旧实例依次走 onPause → onStop → onSaveInstanceState,然后 onDestroy;
新实例走 onCreate → onStart → onRestoreInstanceState → onResume。
其中 onSaveInstanceState 会把轻量级数据写入 Bundle,在新实例 onCreate 与 onRestoreInstanceState 中还原。

为避免‘不必要’的重建,我通常分三步:

  1. 数据层:把与 UI 无关的耗时数据放到 ViewModel,利用 ViewModelStore 在配置变更时自动复用;
  2. 状态层:对少量 UI 状态(如 RecyclerView 滚动位置、输入框文字)用 SavedStateHandle 或 onSaveInstanceState 持久化,防止进程被杀;
  3. 特殊场景:若界面本身不依赖屏幕方向(如扫码页、直播横屏播放页),则在 Manifest 中声明 android:configChanges="orientation|screenSize|smallestScreenSize",并在 onConfigurationChanged 里手动更新布局,而非重建 Activity。

但国内上架需注意:华为应用市场审核规范明确禁止滥用 configChanges 来规避横屏适配,必须在设置里保留‘跟随系统旋转’开关,否则会被驳回。
此外,折叠屏展开会一次性触发多项配置变更,我会通过 androidx.window:window 库监听 FoldingFeature,在 onConfigurationChanged 中统一处理,而不是依赖系统多次重建,这样既能通过审核,又能把旋转耗时从 120 ms 降到 40 ms 以内。”

拓展思考

  1. 进程被杀场景:国内 ROM 后台清理激进,旋转时若内存压力高,系统可能在 onDestroy 后把进程直接杀掉,新实例启动时 Bundle 为 null。此时仅靠 ViewModel 无法恢复,需要把关键业务 ID 持久化到 MMKV/数据库,并在 onCreate 做“冷启动恢复”兜底。
  2. 窗口级配置变更:Android 12L 引入 Activity 嵌入(Activity Embedding)与多窗口模式,旋转只在当前窗口生效,生命周期回调可能延迟到窗口尺寸真正变化之后。适配时需使用 WindowInfoTracker 而非传统 DisplayMetrics。
  3. 车载与 TV:车载屏幕旋转角度为 0°/90°/180°/270°,但系统栏高度动态变化,触发 density 变更;TV 则支持“竖屏投屏”模式,此时会强制反向旋转。两者都要求使用 ViewModel + SavedStateHandle 组合,避免 configChanges,否则无法通过 Google AAOS 认证与国内车规级审核。