一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析

背景

在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 核心侧一手包办:

  1. DisplayRotation.updateRotationUnchecked() 计算新 rotation;
  2. mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE 冻结屏幕;
  3. prepareNormalRotationAnimation()(截图 + 旋转动画)或 prepareSeamlessRotation()
  4. 核心侧 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()DefaultTransitionHandlerDefaultTransitionHandler.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.ScreenRotationAnimationScreenRotationAnimation.java)由三部分组成(类注释 ScreenRotationAnimation.java:53):

  1. 截图(screenshot)

    • 优先用 change.getSnapshot()(WM 已截好的图);
    • 否则用 ScreenCapture.captureLayers() 现场截图(ScreenRotationAnimation.java:141);
    • 截图被 reparent 到 mAnimLeashsetEffectLayer)上。
  2. 新内容(windows on display)

    • 设备已旋转到新方向,动画先把新内容"反旋"回旧方向,再旋转到新方向(enter animation)。
  3. 背景色(background color)

    • 计算旋转前后布局的亮度 mStartLuma/mEndLuma,在底层做灰度过渡,避免露出空白。

buildAnimation()ScreenRotationAnimation.java:247)根据 deltaRotationROTATION_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

原文地址:

https://mp.weixin.qq.com/s/C627_A_TkF1yqxtyTKZ92A

相关推荐
美狐美颜SDK开放平台2 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc3 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077005 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼6 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
liangshanbo12156 小时前
React 性能优化实战
性能优化·react
AFinalStone7 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen7 小时前
属性动画原理与高级动画实现
android·kotlin·canvas