背景
在aosp14引入了ShellTransition后,系统中很多的动画过程都引入到ShellTransition中,比如点击app启动Activity,分屏等,前面这块内容已经在马哥的WMS高级进阶ShellTransition课程中有进行讲解,今天要给大家补充是一个屏幕旋转的场景是如何升级到ShellTransition的。

安卓14-15屏幕旋转核心变化
高版本(AOSP14、15以后)手机屏幕旋转已经迁移到 ShellTransition 体系,不再使用老版本的那种
具体表现为两层:
屏幕旋转动画本身 (设备物理旋转、TRANSIT_CHANGE display change) | 由 Shell(SystemUI)的 DefaultTransitionHandler 播放 Shell 侧的 ScreenRotationAnimation | ✅ 默认开启(只要 Shell 注册了 transition player)
为什么要切换到 ShellTransition
回忆老版本(AOSP13 及以前)的旋转流程由 WindowManagerService 核心侧一手包办:
DisplayRotation.updateRotationUnchecked()计算新 rotation;mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE冻结屏幕;prepareNormalRotationAnimation()(截图 + 旋转动画)或prepareSeamlessRotation();- 核心侧
com.android.server.wm.ScreenRotationAnimation负责截图层 + 旋转矩阵 + 淡入淡出。
这套方案的问题:
- 动画与业务逻辑耦合在 WM 核心,SystemUI 无法自定义旋转动效;
- 截图/冻结/解冻时序复杂,容易与 app 冷启动、无缝旋转(seamless rotation)冲突;
- 折叠屏、桌面模式等新形态设备需要更灵活的显示切换,legacy 路径难以扩展。
ShellTransition 把"转场/旋转动画"下放给 Shell(SystemUI)扮演 TransitionPlayer,WM 核心只负责同步(BLASTSyncEngine) 与状态收集(Transition) ,动画实现完全由 Shell 决定。旋转因此统一纳入 TRANSIT_CHANGE 类型转场。
Winscope中查看整个屏幕旋转流程
屏幕旋转前

屏幕旋转时候

可以看出屏幕在准备旋转前就已经创建好了对应的RotationLayer,这个是一个屏幕的截图Layer

RotationLayer是一个独立的图层,而且层级很高,整个分析流程也可以以这个RotationLayer为线索进行分析整个屏幕旋转新流程。
frameworks/base/services/core/java/com/android/server/wm/Transition.java
cpp
final String name = isDisplayRotation ? "RotationLayer" : "transition snapshot: " + wc;
SurfaceControl snapshotSurface = wc.makeAnimationLeash()
.setName(name)
.setOpaque(wc.fillsParent())
.setParent(wc.getSurfaceControl())
.setSecure(screenshotBuffer.containsSecureLayers())
.setCallsite("Transition.ScreenshotSync")
.setBLASTLayer()
.build();
mFrozen.add(wc);
final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc));
changeInfo.mSnapshot = snapshotSurface;
if (changeInfo.mRotation != wc.mDisplayContent.getRotation()) {
// This isn't cheap, so only do it for rotation change.
changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma(
buffer, screenshotBuffer.getColorSpace(), wc.mSurfaceControl);
}
SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get();
TransitionAnimation.configureScreenshotLayer(t, snapshotSurface, screenshotBuffer);
t.show(snapshotSurface);

旋转过程中会跳转RotationLayer的旋转角度等。
涉及的模块与核心类
Core 侧(services/core/java/com/android/server/wm/)
| 类 | 职责 |
|---|---|
DisplayRotation |
计算 rotation、updateRotationUnchecked() 入口,区分 Shell/legacy 分支 |
DisplayContent |
requestChangeTransition() / collectDisplayChange(),构造 DisplayChange |
TransitionController |
管理 transition player 列表、创建/分发转场、requestStartDisplayTransition() |
Transition |
转场对象,携带 ChangeInfo(含 FLAG_SEAMLESS_ROTATION) |
AsyncRotationController |
处理非 activity 窗口(导航栏、状态栏等)在旋转期间的外观 |
RemoteDisplayChangeController |
通知远端(Shell)显示变化的握手通道 |
ScreenRotationAnimation(核心侧) |
仅作 fallback,无 Shell player 时才用 |
Shell 侧(libs/WindowManager/Shell/src/com/android/wm/shell/)
| 类 | 职责 |
|---|---|
transition/Transitions |
Shell 侧转场总控,接收 WM 的 startTransition 请求,分发 DisplayChange |
transition/DefaultTransitionHandler |
默认动画实现,负责 TRANSIT_CHANGE 的旋转动画 |
transition/ScreenRotationAnimation(Shell 侧) |
截图 + 旋转 + 交叉淡入淡出的旋转动画实现 |
common/DisplayController |
onDisplayChangeRequested():更新 DisplayLayout、通知 rotation 监听器 |
common/DisplayLayout |
维护每个 display 的 layout,rotateTo() / navigationBarCanMove() / allowSeamlessRotationDespiteNavBarMoving() |
完整旋转流程(时序)
触发源:传感器(WindowOrientationListener)或 app 调用 setRequestedOrientation()。
传感器/app请求
│
▼
DisplayRotation.updateOrientation() / updateRotationAndSendNewConfigIfChanged()
│
▼
updateRotationUnchecked() [DisplayRotation.java:523]
│ 计算新 rotation,若 oldRotation == rotation 直接返回
│
├── isShellTransitionsEnabled() == true ──────────────┐ ← AOSP15 默认走这里
│ │
│ ├─ 未在 collecting:
│ │ new DisplayChange(displayId, old, new)
│ │ requestChangeTransition(
│ │ CONFIG_WINDOW_CONFIGURATION, change) │ [DisplayRotation.java:632]
│ │
│ └─ 已在 collecting:
│ collectDisplayChange(transition)
│ startRemoteRotation(old, new)
│
└── 否则(legacy,无 Shell player)───────────────────┘
mWindowsFreezingScreen = ACTIVE
prepareNormalRotationAnimation() / prepareSeamlessRotation()
5.1 Core 侧:发起 TRANSIT_CHANGE
DisplayContent.requestChangeTransition()(DisplayContent.java:3509):
java
void requestChangeTransition(int changes, DisplayChange displayChange) {
final Transition t = controller.requestStartDisplayTransition(
TRANSIT_CHANGE, 0, this, null, displayChange);
t.collect(this);
...
if (isRotationChanging()) {
// 判断是否 seamless
final boolean seamless = mDisplayRotation.shouldRotateSeamlessly(
displayChange.getStartRotation(), displayChange.getEndRotation(), false);
if (seamless) {
t.onSeamlessRotating(this);
}
startAsyncRotation(false);
}
t.setKnownConfigChanges(this, changes);
}
TransitionController.requestStartDisplayTransition()(TransitionController.java:726)→ requestStartTransition() 把 DisplayChange 打包进 TransitionRequestInfo,通过 mWindowOrganizerController.startTransition() 发往 Shell。
5.2 Shell 侧:接收并分发 DisplayChange
Transitions.requestStartTransition()(Transitions.java:1194):
java
if (request.getDisplayChange() != null) {
TransitionRequestInfo.DisplayChange change = request.getDisplayChange();
if (change.getStartRotation() != change.getEndRotation()
|| (change.getStartAbsBounds() != null
&& !change.getStartAbsBounds().equals(change.getEndAbsBounds()))) {
if (wct == null) wct = new WindowContainerTransaction();
mDisplayController.onDisplayChangeRequested(wct, change.getDisplayId(),
change.getStartAbsBounds(), change.getEndAbsBounds(),
change.getStartRotation(), change.getEndRotation());
}
}
DisplayController.onDisplayChangeRequested()(DisplayController.java:197):
mDisplayLayout.rotateTo(resources, toRotation):更新 DisplayLayout;- 通知
DisplayChangeController的各 rotation 监听器(SystemUI 各模块据此调整布局)。
5.3 Shell 侧:播放旋转动画
WM 收集完所有变化后回调 Shell 的 startAnimation(),DefaultTransitionHandler(DefaultTransitionHandler.java:294)识别到 TRANSIT_CHANGE + FLAG_IS_DISPLAY 的 change,进入 startRotationAnimation()(DefaultTransitionHandler.java:701):
java
private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
TransitionInfo.Change change, TransitionInfo info, int animHint, ...) {
final int rootIdx = TransitionUtil.rootIndexFor(change, info);
final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext,
mTransactionPool, startTransaction, change,
info.getRoot(rootIdx).getLeash(), animHint);
// 由 fade-out 截图 + fade-in 真实内容 + 背景色 三个子动画组成
anim.buildAnimation(animGroup, finishCallback, mTransitionAnimationScaleSetting, mMainExecutor);
...
}
5.4 完成
动画结束后 finishCallback.onTransitionFinished(wct),Core 侧 Transition 收尾,AsyncRotationController.onTransitionFinished() 淡入尚未就绪的窗口,最终 sendNewConfiguration() 完成配置分发。
Shell 侧旋转动画实现细节
com.android.wm.shell.transition.ScreenRotationAnimation(ScreenRotationAnimation.java)由三部分组成(类注释 ScreenRotationAnimation.java:53):
-
截图(screenshot)
- 优先用
change.getSnapshot()(WM 已截好的图); - 否则用
ScreenCapture.captureLayers()现场截图(ScreenRotationAnimation.java:141); - 截图被 reparent 到
mAnimLeash(setEffectLayer)上。
- 优先用
-
新内容(windows on display)
- 设备已旋转到新方向,动画先把新内容"反旋"回旧方向,再旋转到新方向(enter animation)。
-
背景色(background color)
- 计算旋转前后布局的亮度
mStartLuma/mEndLuma,在底层做灰度过渡,避免露出空白。
- 计算旋转前后布局的亮度
buildAnimation()(ScreenRotationAnimation.java:247)根据 deltaRotation(ROTATION_0/90/180/270)选择对应的 screen_rotate_*_exit/enter 动画资源;customRotate(crossfade/jumpcut)则走 rotation_animation_xfade_exit/rotation_animation_jump_exit 等。
与 AOSP13 的对比
| 维度 | AOSP13(legacy) | AOSP14/15(ShellTransition) |
|---|---|---|
| 动画实现方 | WM 核心侧 com.android.server.wm.ScreenRotationAnimation |
Shell 侧 com.android.wm.shell.transition.ScreenRotationAnimation |
| 冻结机制 | mWindowsFreezingScreen = ACTIVE + startFreezingDisplay() |
BLASTSyncEngine 同步 + screenshot leash |
| 动画定制 | 难以定制(核心硬编码) | SystemUI 可替换 TransitionHandler |
| 旋转入口 | prepareNormalRotationAnimation() / prepareSeamlessRotation() |
requestChangeTransition(TRANSIT_CHANGE, DisplayChange) |
| 非 app 窗口 | 核心侧 seamless/freeze 逻辑 | AsyncRotationController 统一处理 |
| 老类是否保留 | --- | com.android.server.wm.ScreenRotationAnimation 仍保留作 fallback |
原文地址: