一起学 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(),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):

  1. 截图(screenshot)

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

    • 设备已旋转到新方向,动画先把新内容"反旋"回旧方向,再旋转到新方向(enter animation)。
  3. 背景色(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

原文地址:

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

相关推荐
掉进电商坑三年没爬出来的东叔2 小时前
Vibe Coding实战三:把AI做的小工具部署上云,手机电脑随时用
人工智能·智能手机
我不会插花弄玉2 小时前
7.函数【由浅入深-MySQL】
android·数据库·mysql
vilya3 小时前
手机端 Agent 怎么动起来:Kotlin 与 Python 之间的桥
android·agent
用户11481867894843 小时前
面试官:大屏项目里十几个模块同时渲染上万条数据,怎么做到不卡?
前端·性能优化
高晶3 小时前
Android 新手实战:从零搭建 WebRTC 在线直播系统
android·程序员
alexhilton3 小时前
为什么你的 LazyColumn 仍然卡顿
android·kotlin·android jetpack
>Andre<5 小时前
系统架构全景
android
wulechun6 小时前
深度解析weiweifan/Big-Data-Resources:构建从基础理论到实战面试的全栈大数据学习体系指南
智能手机
李永奉7 小时前
日常使用的锂电池上,新、旧锂电池的差异分析
单片机·嵌入式硬件·物联网·智能手机·电脑
大雷神8 小时前
从一道24点到同屏双人派对:用鸿蒙开发助手起步,复用已有App开发手机元服务
华为·智能手机·harmonyos