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

17 阅读9分钟

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

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


一、全局流程概览

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

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

阶段关键操作核心类
① 传感器感知onProposedRotationChangedDisplayRotation.OrientationListener
② WMS冻屏startFreezingDisplay,截图创建顶层LayerScreenRotationAnimation
③ 通知SystemUIonRotateDisplay 跨进程调用DisplayRotationController
④ Configuration派发onConfigurationChanged 逐层派发RootWindowContainerDisplayContent
⑤ Activity重建relaunchActivityLocked(onDestroy→onCreate)ActivityRecord
⑥ 解冻与动画stopFreezingDisplayLocked,播放旋转动画SurfaceAnimatorScreenRotationAnimation

下面逐一展开分析。


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

2.1 OrientationListener 回调

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

// DisplayRotation.java - OrientationListener
@Override
public void onProposedRotationChanged(@Surface.Rotation int rotation) {
    // 发送交互功耗提升,优化重绘性能
    mService.mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
    dispatchProposedRotation(rotation);

    if (isRotationChoiceAllowed(rotation)) {
        // 用户手动确认模式:显示旋转确认按钮
        mRotationChoiceShownToUserForConfirmation = rotation;
        final boolean isValid = isValidRotationChoice(rotation);
        sendProposedRotationChangeToStatusBarInternal(rotation, isValid);
    } else {
        // 自动旋转模式:直接触发旋转更新
        mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED;
        mService.updateRotation(false /* alwaysSendConfiguration */,
                false /* forceRelayout */);
    }
}

2.2 updateRotationUnchecked —— 旋转决策核心

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

// DisplayRotation.java
boolean updateRotationUnchecked(boolean forceUpdate) {
    // 1. 前置检查
    if (!forceUpdate) {
        // 是否暂停旋转更新
        if (mDeferredRotationPauseCount > 0) return false;
        // 是否在 Transition 动画中
        if (mDisplayContent.inTransition() && ...) return false;
        // 是否在最近任务动画中
        if (mDisplayContent.mFixedRotationTransitionListener.shouldDeferRotation()) return false;
    }

    // 2. 计算新的旋转角度
    final int oldRotation = mRotation;
    int rotation = rotationForOrientation(lastOrientation, oldRotation);

    // 3. 角度未变则直接返回
    if (oldRotation == rotation) return false;

    // 4. 记录新角度
    mRotation = rotation;
    mDisplayContent.setLayoutNeeded();
    mDisplayContent.mWaitingForConfig = true;

    // 5. 通知 SystemUI 开始远程旋转
    startRemoteRotation(oldRotation, mRotation);
    return true;
}

关键点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(0, 0, 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.java
private void startRemoteRotation(int fromRotation, int toRotation) {
    mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange(
        fromRotation, toRotation, null /* newDisplayAreaInfo */,
        (transaction) -> continueRotation(toRotation, transaction)
    );
}

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

// DisplayRotation.java
private void continueRotation(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:各子节点(如 NavigationBarWindowState)响应尺寸变化,被加入 mResizingWindows 列表。后续 performSurfacePlacementNoTrace 中的 handleResizingWindows 会处理这些窗口。

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


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

6.1 判断是否需要 Relaunch

// ActivityRecord.java
private boolean shouldRelaunchLocked(int changes, Configuration changesConfig) {
    // 获取 Activity 在 Manifest 中声明的 configChanges
    int configChanged = info.getRealConfigChanged();
    // 与当前的 change 进行比较
    // 如果声明忽略的多了,就不需要 relaunch
    return (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 - buildAnimation
int 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 声明:

<activity
    android:configChanges="orientation|screenSize" />

8.2 效果

声明后,旋转时:

  • ❌ 不会触发 relaunchActivityLocked
  • ❌ 不会有 onDestroyonCreate 回调
  • ✅ 界面仍然会更新(通过 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_CONFIGURATION
adb 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