Android 横竖屏旋转全流程源码深度解析
本文基于 Android 15 AOSP 源码,从传感器感知到旋转动画播放,完整剖析屏幕旋转的每一个环节。涉及
DisplayRotation、ScreenRotationAnimation、ActivityRecord、WindowManagerService等核心类。
一、全局流程概览
一句话总结:传感器变化 → 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
@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:各子节点(如 NavigationBar 的 WindowState)响应尺寸变化,被加入 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 - ❌ 不会有
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_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 横竖屏旋转是一个涉及传感器、WMS、SystemUI、ATMS、SurfaceFlinger 等多个模块协作的复杂流程。其核心设计思想是:
- 冻屏保护:用截图图层盖住真实画面,避免用户看到中间态的混乱布局
- 异步协调:通过跨进程回调与 SystemUI 协作,确保系统 UI 先完成调整
- 逐层派发:Configuration 沿
RootWindowContainer → DisplayContent → Task → ActivityRecord的层级结构树逐层传递 - 双重冻结:WMS 冻结(全局)和 ActivityRecord 冻结(单个 Activity)是独立的,WMS 解冻需要等待所有 ActivityRecord 解冻
- 动画过渡:解冻时通过 Exit/Enter 两个动画实现平滑的视觉过渡,Leash 机制实现了动画与窗口的解耦
理解这个流程,对于排查旋转相关的 Bug(如旋转卡死、动画异常、布局错乱、黑屏闪烁等)有重要的指导意义。
参考源码路径:
frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.javaframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.javaframeworks/base/services/core/java/com/android/server/wm/ActivityRecord.javaframeworks/base/services/core/java/com/android/server/wm/WindowManagerService.javaframeworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.javaframeworks/base/services/core/java/com/android/server/wm/WindowSurfacePlacer.java