Android 横竖屏旋转全流程源码深度解析
本文基于 Android 15 AOSP 源码,从传感器感知到旋转动画播放,完整剖析屏幕旋转的每一个环节。涉及
DisplayRotation、ScreenRotationAnimation、ActivityRecord、WindowManagerService等核心类。
一、全局流程概览
一句话总结:传感器变化 → WMS冻屏截图 → 通知SystemUI → Configuration派发 → Activity重建 → 解冻播放旋转动画。
整个横竖屏旋转可以划分为以下六个阶段:
|
|
|
|
|---|---|---|
|
|
onProposedRotationChanged |
DisplayRotation.OrientationListener |
|
|
startFreezingDisplay
|
ScreenRotationAnimation |
|
|
onRotateDisplay
|
DisplayRotationController |
|
|
onConfigurationChanged
|
RootWindowContainer
DisplayContent |
|
|
relaunchActivityLocked
|
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 > 0) returnfalse;// 是否在 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(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.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 动画
|
|
|
| Enter 动画
|
|
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 横竖屏旋转是一个涉及传感器、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.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
夜雨聆风