乐于分享
好东西不私藏

Android 横竖屏旋转全流程源码深度解析

Android 横竖屏旋转全流程源码深度解析

本文基于 Android 15 AOSP 源码,从传感器感知到旋转动画播放,完整剖析屏幕旋转的每一个环节。涉及 DisplayRotationScreenRotationAnimationActivityRecordWindowManagerService 等核心类。


一、全局流程概览

一句话总结:传感器变化 → WMS冻屏截图 → 通知SystemUI → Configuration派发 → Activity重建 → 解冻播放旋转动画。

整个横竖屏旋转可以划分为以下六个阶段:

阶段
关键操作
核心类
① 传感器感知
onProposedRotationChanged DisplayRotation.OrientationListener
② WMS冻屏
startFreezingDisplay

,截图创建顶层Layer
ScreenRotationAnimation
③ 通知SystemUI
onRotateDisplay

 跨进程调用
DisplayRotationController
④ Configuration派发
onConfigurationChanged

 逐层派发
RootWindowContainer

DisplayContent
⑤ Activity重建
relaunchActivityLocked

(onDestroy→onCreate)
ActivityRecord
⑥ 解冻与动画
stopFreezingDisplayLocked

,播放旋转动画
SurfaceAnimator

ScreenRotationAnimation

下面逐一展开分析。


二、阶段一:传感器感知与旋转决策

2.1 OrientationListener 回调

一切始于 DisplayRotation 内部类 OrientationListener 的回调。当系统传感器(SystemSensorManager)检测到设备方向变化时:

// DisplayRotation.java - OrientationListener@OverridepublicvoidonProposedRotationChanged(@Surface.Rotation int rotation){// 发送交互功耗提升,优化重绘性能    mService.mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);    dispatchProposedRotation(rotation);if (isRotationChoiceAllowed(rotation)) {// 用户手动确认模式:显示旋转确认按钮        mRotationChoiceShownToUserForConfirmation = rotation;finalboolean isValid = isValidRotationChoice(rotation);        sendProposedRotationChangeToStatusBarInternal(rotation, isValid);    } else {// 自动旋转模式:直接触发旋转更新        mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED;        mService.updateRotation(false/* alwaysSendConfiguration */,false/* forceRelayout */);    }}

2.2 updateRotationUnchecked —— 旋转决策核心

updateRotation 最终调用到 DisplayRotation.updateRotationUnchecked,这是旋转决策的核心方法:

// DisplayRotation.javabooleanupdateRotationUnchecked(boolean forceUpdate){// 1. 前置检查if (!forceUpdate) {// 是否暂停旋转更新if (mDeferredRotationPauseCount > 0returnfalse;// 是否在 Transition 动画中if (mDisplayContent.inTransition() && ...) returnfalse;// 是否在最近任务动画中if (mDisplayContent.mFixedRotationTransitionListener.shouldDeferRotation()) returnfalse;    }// 2. 计算新的旋转角度finalint oldRotation = mRotation;int rotation = rotationForOrientation(lastOrientation, oldRotation);// 3. 角度未变则直接返回if (oldRotation == rotation) returnfalse;// 4. 记录新角度    mRotation = rotation;    mDisplayContent.setLayoutNeeded();    mDisplayContent.mWaitingForConfig = true;// 5. 通知 SystemUI 开始远程旋转    startRemoteRotation(oldRotation, mRotation);returntrue;}

关键点rotationForOrientation 方法根据应用声明的 orientation(如 portrait、landscape、unspecified 等)和当前传感器角度,综合决策出最终的旋转角度。其中 0度、90度为逆时针旋转方向。


三、阶段二:WMS冻屏与截图

Android 15 默认启用 Shell Transitions 模式,旋转动画逻辑移到SystemUI的WM Shell进程执行,减少WMS核心服务负担,仅兼容旧设备保留legacy冻屏路径。

3.1 冻屏流程

startFreezingDisplay                    // WindowManagerService.java  └─ doStartFreezingDisplay       ├─ mScreenFrozenLock.acquire()  // 申请屏幕冻结锁       ├─ mAtmService.startPowerMode() // 启动性能模式减少冻屏时间       ├─ freezeInputDispatchingLw()   // 冻结输入事件分发       ├─ mAppTransition.freeze        // 冻结 App Transition       └─ new ScreenRotationAnimation(displayContent, originalRotation)            // 传递的是原始 rotation(旧角度)

3.2 ScreenRotationAnimation 构造

ScreenRotationAnimation 在构造时完成了几件关键的事:

// ScreenRotationAnimation 构造函数(Shell Transitions 版本,Android 15 默认模式)ScreenRotationAnimation(Context context, TransactionPool pool, Transaction t,        TransitionInfo.Change change, SurfaceControl rootLeash, int animHint, int flags) {// 1. 记录起止尺寸和旋转角度    mStartWidth = change.getStartAbsBounds().width();    mStartHeight = change.getStartAbsBounds().height();    mStartRotation = change.getStartRotation();    mEndRotation = change.getEndRotation();// 2. 创建动画 Leash 容器层    mAnimLeash = new SurfaceControl.Builder()        .setParent(rootLeash)        .setEffectLayer()        .setName("Animation leash of screenshot rotation")        .build();// 3. 获取WMS传递过来的屏幕快照(或直接捕获)if (change.getSnapshot() != null) {// Shell Transitions模式下WMS已经提前捕获好快照,直接复用        mScreenshotLayer = change.getSnapshot();        t.reparent(mScreenshotLayer, mAnimLeash);    } else {//  fallback 直接捕获当前屏幕内容        ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(mSurfaceControl)            .setCaptureSecureLayers(true)            .setAllowProtected(true)            .setSourceCrop(new Rect(00, mStartWidth, mStartHeight))            .build();        ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer = ScreenCapture.captureLayers(args);// 4. 创建截图图层(RotationLayer)        mScreenshotLayer = new SurfaceControl.Builder()            .setParent(mAnimLeash)            .setBLASTLayer()            .setName("RotationLayer")            .build();// 5. 将截图 Buffer 设置到图层上并显示        TransitionAnimation.configureScreenshotLayer(t, mScreenshotLayer, screenshotBuffer);        t.show(mScreenshotLayer);    }// 6. 初始化旋转变换矩阵,把截图从新坐标映射回旧视觉位置    setScreenshotTransform(t);    t.apply();}

3.3 setRotation 与矩阵变换

setRotation 方法负责对截图内容进行 SurfaceControl 的矩阵变换。

delta 的计算方式

delta = deltaRotation(endRotation, startRotation)      = (endRotation - startRotation + 4) % 4

例如从 ROTATION_0 旋转到 ROTATION_1(90度):

  • 参数传递是反过来的:old=0, new=1
  • delta = (0 – 1 + 4) % 4 = 3,即 Surface.ROTATION_270

为什么是反过来的? 因为 setRotation 的目的是将截图从新坐标系”转回”到旧坐标系的视觉效果。矩阵使用数学旋转角度(逆时针为正),而 Android 的 ROTATION 值是顺时针定义的。

坐标系变换:Matrix 以屏幕坐标原点(左上角)进行旋转。当 delta 为 270度(数学上的 -90度)时,画面旋转后还需要向下平移才能正确显示在屏幕上。90度更新后坐标原点继续按照左上角。


四、阶段三:SystemUI 跨进程回调

冻屏完成后,通过 startRemoteRotation 跨进程通知 SystemUI:

// DisplayRotation.javaprivatevoidstartRemoteRotation(int fromRotation, int toRotation){    mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange(        fromRotation, toRotation, null/* newDisplayAreaInfo */,        (transaction) -> continueRotation(toRotation, transaction)    );}

SystemUI 处理完旋转相关的 UI 调整(如状态栏、导航栏的重新布局)后,通过回调触发 continueRotation

// DisplayRotation.javaprivatevoidcontinueRotation(int targetRotation, WindowContainerTransaction t){if (targetRotation != mRotation) return// 过期的回调直接丢弃    mService.mAtmService.deferWindowLayout();try {        mDisplayContent.sendNewConfiguration(); // 进入 Configuration 派发if (t != null) {            mService.mAtmService.mWindowOrganizerController.applyTransaction(t);        }    } finally {        mService.mAtmService.continueWindowLayout();    }}

五、阶段四:Configuration 派发

这是整个旋转流程中最复杂的部分,涉及 Configuration 的逐层派发。

5.1 完整调用链

continueRotation  └─ sendNewConfiguration                              // DisplayContent.java       └─ updateDisplayOverrideConfigurationLocked            ├─ computeScreenConfiguration            │    ├─ updateDisplayAndOrientation         // 更新 displayInfo,高宽互换            │    ├─ config.windowConfiguration.setBounds // 设置新的宽高角度            │    └─ computeScreenAppConfiguration       // 处理 inset(状态栏、刘海等)            └─ updateGlobalConfigurationLocked          // ATMS                 ├─ mTempConfig.updateFrom(values)      // 检测变化                 ├─ WindowProcessController.onConfigurationChanged                 │    └─ ConfigurationChangeItem.obtain  // 通知 App 进程                 └─ mRootWindowContainer.onConfigurationChanged                      └─ WindowContainer.onConfigurationChanged                           └─ dispatchConfigurationToChild                                └─ DisplayContent.onRequestedOverrideConfigurationChanged                                     ├─ applyRotation                                     │    └─ setRotation  // ScreenRotationAnimation                                     └─ super.onRequestedOverrideConfigurationChanged                                          └─ onResize     // 各子节点响应尺寸变化

5.2 关键节点说明

**computeScreenConfiguration**:在这里对 displayInfo 进行真正的更新。检查是否有旋转,如果有则让高宽互换,并赋值给 mDisplayInfo

**onResize**:各子节点(如 NavigationBar 的 WindowState)响应尺寸变化,被加入 mResizingWindows 列表。后续 performSurfacePlacementNoTrace 中的 handleResizingWindows 会处理这些窗口。

**applyRotation**:在 DisplayContent.onRequestedOverrideConfigurationChanged 中调用,对 ScreenRotationAnimation 执行 setRotation,更新截图图层的变换矩阵。


六、阶段五:Activity 的冻结与重建

6.1 判断是否需要 Relaunch

// ActivityRecord.javaprivatebooleanshouldRelaunchLocked(int changes, Configuration changesConfig){// 获取 Activity 在 Manifest 中声明的 configChangesint configChanged = info.getRealConfigChanged();// 与当前的 change 进行比较// 如果声明忽略的多了,就不需要 relaunchreturn (changes & (~configChanged)) != 0;}

6.2 冻结与重建流程

ensureActivityConfiguration                    // ActivityRecord.java  ├─ shouldRelaunchLocked                      // 判断是否需要 relaunch  │    └─ info.getRealConfigChanged            // 检查 Manifest 中的 configChanges  ├─ startFreezingScreenLocked                 // 冻结 ActivityRecord  │    └─ mWmService.mAppsFreezingScreen++     // 计数+1,WMS解冻时需要归零  └─ relaunchActivityLocked                    // 重建 Activity       ├─ ActivityRelaunchItem.obtain          // 触发 onDestroy → onCreate       └─ PauseActivityItem.obtain

重要:ActivityRecord 的冻结和 WMS 的冻结是两个不同的概念。mAppsFreezingScreen 是一个计数器,WMS 的全局解冻需要等待所有 ActivityRecord 都解冻(计数归零)后才能执行。

6.3 ActivityRecord 的解冻

ActivityRecord 的解冻由动画系统触发:

animate                                        // WindowAnimator.java  └─ checkAppWindowsReadyToShow               // WindowContainer.java,遍历子节点       └─ checkAppWindowsReadyToShow          // ActivityRecord.java            └─ stopFreezingScreen             // 条件:allDrawn == true

当 ActivityRecord 的所有窗口都绘制完成(allDrawn = true)时,才会触发解冻。


七、阶段六:解冻与旋转动画

7.1 WMS 解冻触发

当新的 Window 执行到 performSurfacePlacement 时,开始解冻流程:

performSurfacePlacement                        // WindowSurfacePlacer.java  └─ performSurfacePlacementNoTrace            // RootWindowContainer.java       └─ stopFreezingDisplayLocked            // WindowManagerService.java            // 条件:mOrientationChangeComplete == true            └─ doStopFreezingDisplayLocked                 // 条件:screenRotationAnimation != null && hasScreenshot()                 └─ screenRotationAnimation.dismiss                      └─ startAnimation

7.2 旋转动画的选择与创建

在 buildAnimation 方法中,根据 delta 值选择对应的动画资源:

// ScreenRotationAnimation.java - buildAnimationint delta = deltaRotation(mEndRotation, mStartRotation);switch (delta) { /* Counter-Clockwise Rotations */case Surface.ROTATION_0:        mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,                R.anim.screen_rotate_0_exit);        mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,                R.anim.rotation_animation_enter);break;case Surface.ROTATION_90:        mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,                R.anim.screen_rotate_plus_90_exit);        mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,                R.anim.screen_rotate_plus_90_enter);break;case Surface.ROTATION_180:        mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,                R.anim.screen_rotate_180_exit);        mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,                R.anim.screen_rotate_180_enter);break;case Surface.ROTATION_270:        mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,                R.anim.screen_rotate_minus_90_exit);        mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,                R.anim.screen_rotate_minus_90_enter);break;}

7.3 动画含义详解(以逆时针转90度为例)

当屏幕逆时针转90度时,delta 为 Surface.ROTATION_270,xml 名为 minus90(数学上减90度):

动画
挂载位置
行为描述
Exit 动画

(mRotateExitAnimation)
RotationLayer 的父节点
截图画面从向左倒的状态顺时针转90度摆正,然后淡出消失。旋转中心为屏幕中心点,角度从0到90度(非数学角度)
Enter 动画

(mRotateEnterAnimation)
WindowedMagnification 0:31
新的横屏画面从向左倒的状态顺时针旋转摆正。角度从-90到0度。有 startOffset 和 alpha 偏移,使得视觉上稍晚出现

两个动画几乎同时被创建,但 Enter 动画通过 startOffset 实现了时间上的延迟效果——在较长时间内 alpha 为0,造成”先退后进”的视觉效果。

7.4 动画执行机制

旋转动画通过 SurfaceAnimator 驱动:

startAnimation  ├─ startDisplayRotation                      // 对 DisplayContent 旋转  │    └─ SurfaceAnimator.startAnimation  │         ├─ new LocalAnimationAdapter  │         └─ startAnimation  │              └─ createAnimationLeash       // 创建动画 Leash,获取 parent  └─ startScreenshotRotationAnimation          // 截图层的旋转动画       └─ buildSurfaceAnimation

历史演进:以前动画都由 WindowAnimator.animate 驱动,但引入 Leash 机制后(为了优化性能和解耦),动画由 SurfaceAnimationRunner 接管。


八、特殊场景:configChanges 避免重建

8.1 声明方式

在 AndroidManifest.xml 中为 Activity 声明:

<activityandroid:configChanges="orientation|screenSize" />

8.2 效果

声明后,旋转时:

  • ❌ 不会触发 relaunchActivityLocked
  • ❌ 不会有 onDestroy 和 onCreate 回调
  • ✅ 界面仍然会更新(通过 relayoutWindow
  • ✅ 会收到 onConfigurationChanged 回调

8.3 relayoutWindow 的触发路径

有三条路径可以触发 relayoutWindow

路径1:ActivityConfigurationChangeItem(最重要)  → Configuration 变化通知路径2:notifyInsets  → Insets 变化通知路径3:handleResized  performSurfacePlacementNoTrace             // RootWindowContainer    └─ handleResizingWindows         └─ reportResized              └─ resized                     // ViewRootImpl.java                   └─ dispatchResized                        └─ relayout

九、调试技巧

9.1 开启旋转相关日志

adb shell wm logging enable-text WM_DEBUG_ORIENTATION WM_DEBUG_STATES WM_DEBUG_CONFIGURATIONadb logcat -c; adb logcat -s WindowManager > rotation_log.txt

9.2 放慢旋转动画

开发者选项中的动画缩放(10倍)对横竖屏旋转动画无效。需要在源码中修改:

在 ScreenRotationAnimation.buildAnimation 中,动画初始化后添加:

mRotateExitAnimation.scaleCurrentDuration(10);mRotateEnterAnimation.scaleCurrentDuration(10);

9.3 查看动画 Leash 创建

在 SurfaceAnimator.createAnimationLeash 中添加堆栈打印,过滤 type=screen_rotation 即可定位旋转动画的 Leash 创建时机和层级关系。


十、完整时序总结

将整个流程按时间顺序串联:

【时间流程1:传感器 → 冻屏 → 通知SystemUI】OrientationListener.onProposedRotationChanged     // 传感器变化  └─ WMS.updateRotation       └─ DisplayRotation.updateRotationUnchecked            ├─ rotationForOrientation              // 计算新角度            ├─ startFreezingDisplay                // 冻屏            │    └─ new ScreenRotationAnimation    // 截图 + 创建顶层Layer            │         ├─ captureLayers             // 截图            │         ├─ 创建 RotationLayer        // 盖住其他画面            │         └─ setRotation               // 设置矩阵变换            └─ startRemoteRotation                 // 通知 SystemUI                 └─ onRotateDisplay                // 跨进程调用【时间流程2:SystemUI回调 → Configuration派发 → Activity重建】mRemoteRotationCallback.continueRotateDisplay  └─ continueRotation       └─ sendNewConfiguration            └─ updateDisplayOverrideConfigurationLocked                 ├─ computeScreenConfiguration     // 更新 displayInfo,高宽互换                 └─ updateGlobalConfigurationLocked                      ├─ onConfigurationChanged    // 逐层派发                      │    ├─ applyRotation        // 应用旋转                      │    └─ onResize             // 各节点响应尺寸变化                      └─ ensureActivityConfiguration                           ├─ startFreezingScreenLocked  // 冻结 ActivityRecord                           └─ relaunchActivityLocked     // 重建 Activity【时间流程3:解冻 → 旋转动画】performSurfacePlacement  └─ stopFreezingDisplayLocked                     // WMS 解冻       └─ screenRotationAnimation.dismiss            └─ startAnimation                      // 播放旋转动画                 ├─ startDisplayRotation            // Display 旋转动画                 └─ startScreenshotRotationAnimation // 截图旋转动画animate (WindowAnimator)  └─ checkAppWindowsReadyToShow       └─ ActivityRecord.stopFreezingScreen        // ActivityRecord 解冻

十一、总结

Android 横竖屏旋转是一个涉及传感器WMSSystemUIATMSSurfaceFlinger 等多个模块协作的复杂流程。其核心设计思想是:

  1. 冻屏保护:用截图图层盖住真实画面,避免用户看到中间态的混乱布局
  2. 异步协调:通过跨进程回调与 SystemUI 协作,确保系统 UI 先完成调整
  3. 逐层派发:Configuration 沿 RootWindowContainer → DisplayContent → Task → ActivityRecord 的层级结构树逐层传递
  4. 双重冻结:WMS 冻结(全局)和 ActivityRecord 冻结(单个 Activity)是独立的,WMS 解冻需要等待所有 ActivityRecord 解冻
  5. 动画过渡:解冻时通过 Exit/Enter 两个动画实现平滑的视觉过渡,Leash 机制实现了动画与窗口的解耦

理解这个流程,对于排查旋转相关的 Bug(如旋转卡死、动画异常、布局错乱、黑屏闪烁等)有重要的指导意义。


参考源码路径

  • frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
  • frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
  • frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
  • frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
  • frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java
  • frameworks/base/services/core/java/com/android/server/wm/WindowSurfacePlacer.java