AOSP15 WMS/AMS系统开发 - 远程动画 (ShellAnimation) 源码深度分析

52 阅读16分钟

基于Android 15 (AOSP)源码,完整梳理远程动画的注册、收集、请求、启动、执行、完成与清理全流程。

本文以 Shell Transitions 为主要分析路径(Android 15 默认启用),Legacy App Transition 作为对比简要保留。两者共享 Leash/SurfaceControl 底层机制。


目录

  1. 概述
  2. Shell Transitions vs Legacy App Transition 对比
  3. 核心类关系图
  4. 阶段一:TransitionPlayer 注册
  5. 阶段二:Transition 创建与收集
  6. 阶段三:请求 Shell 启动 (requestStartTransition)
  7. 阶段四:Shell 确认启动与 Leash 构建
  8. 阶段五:onTransitionReady — 动画委托给 Shell
  9. 阶段六:远程进程执行动画
  10. 阶段七:finishTransition — 完成与清理
  11. 异常与取消处理
  12. 关键数据结构
  13. 完整时序图

1. 概述

1.1 什么是 Shell Transitions 远程动画

Android 12 引入、Android 15 全面启用的 Shell Transitions 体系,将窗口转场动画的编排权从 system_server 转移到 Shell 进程(Launcher/SystemUI)。WMS 只负责收集窗口变化,Shell 负责决定如何播放动画——可以自己播,也可以通过 RemoteTransition 委托给第三方进程。

1.2 核心设计思想

  • WMS-Core/Shell 分离:Core(system_server)管理窗口状态、收集参与者;Shell 决定动画策略、播放动画
  • Leash 机制:为每个参与转场的 WindowContainer 创建 SurfaceControl Leash,Shell/远程进程通过操作 Leash 实现动画
  • BLASTSync 同步:通过 BLASTSyncEngine 确保所有参与者在动画开始前完成首帧绘制
  • 并行 Transition:支持多个 Transition 同时在不同 track 上播放

1.3 四阶段模型

Request → Collect → Play → Finish
  ↓         ↓        ↓       ↓
Shell决定   Core收集   Shell/远程播放  Core清理

2. Shell Transitions vs Legacy App Transition 对比

2.1 分歧点源码

两个系统在同一处代码产生分支——DisplayContent.java

if (mTransitionController.isShellTransitionsEnabled()) {
    // Shell 路径:检查是否正在收集
    if (!mTransitionController.isCollecting(r)) return false;
} else {
    // Legacy 路径:检查 AppTransition 是否就绪
    if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) return false;
}

isShellTransitionsEnabled() 实现(TransitionController.java):

boolean isShellTransitionsEnabled() {
    return !mTransitionPlayers.isEmpty();  // 只要 Shell 注册了 Player 就启用
}

当 Shell 启用时,Legacy 的 AppTransition.prepareAppTransition() 直接短路AppTransition.java):

boolean prepareAppTransition(@TransitionType int transit, @TransitionFlags int flags) {
    if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
        return false;  // ← Legacy 完全跳过
    }
    // Legacy 逻辑...
}

2.2 API 对照

维度Shell TransitionsLegacy App Transition
注册接口WindowOrganizer.registerTransitionPlayer(ITransitionPlayer)AppTransition.overridePendingAppTransitionRemote()
触发端TransitionController 收集变更后通知 ShellAppTransitionController.handleAppTransitionReady()
动画接口ITransitionPlayer.onTransitionReady()IRemoteAnimationRunner.onAnimationStart()
远程委托IRemoteTransition.startAnimation()IRemoteAnimationRunner.onAnimationStart()
动画目标TransitionInfo(层级结构)RemoteAnimationTarget[](扁平数组)
完成回调IWindowOrganizerController.finishTransition()IRemoteAnimationFinishedCallback.onAnimationFinished()
超时机制BLASTSync + Transition 自身超时AppTransition.TIMEOUT_MS = 10000
并行支持多 Track 并行单一 Transition
启用条件Shell 注册 ITransitionPlayer默认

2.3 调用链对比

Shell Transitions(Launcher 启动应用)

ActivityStarter.execute()
  → TransitionController.collect(activity)                    // 收集
  → TransitionController.requestStartTransition()             // 请求
      → ITransitionPlayer.requestStartTransition(token, req)  // Binder → Shell[Shell 调用 startTransition]Transition.start() → 构建 TransitionInfo + Leash
  → ITransitionPlayer.onTransitionReady(token, info, t, finishT) // Binder → Shell[Shell 播放动画 或 委托 IRemoteTransition]
  → IWindowOrganizerController.finishTransition(token, wct)   // Binder → Core
  → TransitionController.finishTransition() → Transition.finishTransition()

Legacy App Transition(对比)

RootWindowContainer.checkAppTransitionReady()
  → AppTransitionController.handleAppTransitionReady()
      → AppTransition.goodToGo()
          → RemoteAnimationController.goodToGo()
              → IRemoteAnimationRunner.onAnimationStart()     // Binder → 远程
  → IRemoteAnimationFinishedCallback.onAnimationFinished()    // Binder → Core
      → RemoteAnimationController.onAnimationFinished()

3. 核心类关系图

┌───────────────────────────────────────────────────────────────────────┐
                        TransitionController                           
                                                                       
  mTransitionPlayers: ArrayList<TransitionPlayerRecord>                
       └─ ITransitionPlayer (Shell 注册)                                
                                                                       
  mCollectingTransition: Transition     当前正在收集的 Transition       
  mPlayingTransitions: ArrayList<Transition>   正在播放的列表           
                                                                       
  createTransition()  new Transition(type, flags, this, syncEngine)   
  requestStartTransition()  ITransitionPlayer.requestStartTransition()│
  finishTransition()  Transition.finishTransition()                   
└────────────────────────┬──────────────────────────────────────────────┘
                          创建/管理
                         
┌──────────────────────────────────────────────────────────────────────┐
                          Transition                                  
                                                                      
  mType: int (TRANSIT_OPEN/CLOSE/CHANGE...)                           
  mSyncId: int (BLASTSyncEngine ID)                                   
  mState: STATE_COLLECTING  STARTED  PLAYING  FINISHED             
                                                                      
  mParticipants: ArraySet<WindowContainer>   参与者集合                
  mChanges: ArrayMap<WindowContainer, ChangeInfo>   变更记录          
  mTargets: ArrayList<ChangeInfo>   最终动画目标(提升后)               
                                                                      
  mStartTransaction: Transaction    动画前 Surface 状态                
  mFinishTransaction: Transaction   动画后 Surface 归位                
                                                                      
  collect(wc)  收集参与者                                              
  start()  构建动画信息                                                
  finishTransition()  清理归位                                        
└──────────────────────────────────────────────────────────────────────┘
                          Binder IPC
                         
┌──────────────────────────────────────────────────────────────────────┐
              ITransitionPlayer (Shell 进程实现)                       
                                                                      
  requestStartTransition(token, TransitionRequestInfo)                
       Shell 决定如何处理(可委托 RemoteTransition)                     
                                                                      
  onTransitionReady(token, TransitionInfo, startT, finishT)           
       收到完整变化信息,开始播放动画                                     
                                                                      
  ┌─ TransitionInfo ─────────────────────────────────────┐            
    type, flags                                                     
    changes: List<Change> (层级结构)                                 
      └─ each Change:                                               
         leash: SurfaceControl                                      
         mode: OPEN/CLOSE/CHANGE...                                 
         startAbsBounds, endAbsBounds                               
         snapshot: SurfaceControl (起始快照)                         
    roots: List<Root> (每 Display 一个根 Leash)                      
  └──────────────────────────────────────────────────────┘            
                                                                      
  ┌─ 可委托 IRemoteTransition ────────────────────────────┐            
    startAnimation(token, info, t, finishCallback)                  
    mergeAnimation(token, info, t, mergeTarget, cb)                 
    onTransitionConsumed(token, aborted)                            
  └──────────────────────────────────────────────────────┘            
└──────────────────────────────────────────────────────────────────────┘

4. 阶段一:TransitionPlayer 注册

Shell(通常为 SystemUI)在启动时通过 WindowOrganizer.registerTransitionPlayer() 注册。

4.1 公共 API 入口

源码: WindowOrganizer.java

@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
public void registerTransitionPlayer(@NonNull ITransitionPlayer player) {
    try {
        getWindowOrganizerController().registerTransitionPlayer(player);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

4.2 WindowOrganizerController 注册

源码: WindowOrganizerController.java

public void registerTransitionPlayer(ITransitionPlayer player) {
    enforceTaskPermission("registerTransitionPlayer()");
    final int callerPid = Binder.getCallingPid();
    final int callerUid = Binder.getCallingUid();
    final long ident = Binder.clearCallingIdentity();
    try {
        synchronized (mGlobalLock) {
            final WindowProcessController wpc =
                    mService.getProcessController(callerPid, callerUid);
            mTransitionController.registerTransitionPlayer(player, wpc);
        }
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
}

4.3 TransitionController 注册

源码: TransitionController.java

void registerTransitionPlayer(@Nullable ITransitionPlayer player,
        @Nullable WindowProcessController playerProc) {
    if (!mTransitionPlayers.isEmpty()) {
        flushRunningTransitions();  // 已有 Player,先刷完正在运行的
    }
    mTransitionPlayers.add(new TransitionPlayerRecord(player, playerProc));
}

注册完成后,isShellTransitionsEnabled() 返回 true,所有窗口转场走 Shell 路径。


5. 阶段二:Transition 创建与收集

以 Launcher 启动应用为例,追踪完整流程。

5.1 Transition 创建 — createAndStartCollecting

源码: ActivityStarter.java

Transition 在 startActivityUnchecked 之前就创建了:

// ActivityStarter 中,execute() → startActivityUnchecked() 调用链
final Transition newTransition = r.mTransitionController.isShellTransitionsEnabled()
        ? r.mTransitionController.createAndStartCollecting(TRANSIT_OPEN) : null;
final boolean isIndependent = newTransition != null;
final Transition transition = isIndependent ? newTransition
        : mService.getTransitionController().getCollectingTransition();

源码: TransitionController.java

createAndStartCollecting 封装了 Transition 创建和收集启动:

Transition createAndStartCollecting(int type) {
    if (mTransitionPlayers.isEmpty()) return null;
    // ... 并行/队列检查 ...
    Transition transit = new Transition(type, 0 /* flags */, this, mSyncEngine);
    moveToCollecting(transit);  // 设为 mCollectingTransition,开始收集
    return transit;
}

5.2 取出 RemoteTransition

源码: ActivityStarter.java

execute() 方法内部(内层 startActivityInner 的外层):

RemoteTransition remoteTransition = r.takeRemoteTransition();

RemoteTransition(非 RemoteAnimationAdapter)是 Shell 路径的远程动画载体,包含 IRemoteTransitionIApplicationThread

5.3 收集参与者 — collect

源码: ActivityStarter.java

同样在 execute() 方法内部,调用 startActivityInner 之前收集:

mService.deferWindowLayout();
r.mTransitionController.collect(r);  // ★ 收集启动的 Activity
try {
    result = startActivityInner(r, sourceRecord, ...);
} finally {
    startedActivityRootTask = handleStartResult(r, options, result,
            isIndependentLaunch, remoteTransition, transition);
}

衔接说明createAndStartCollecting (5.1) 创建 Transition 并进入 COLLECTING 状态 → collect (5.3) 将 Activity 加入参与者集合 → handleStartResult 中调用 requestStartTransition(进入阶段6)

5.4 Transition.collect — 收集实现

源码: Transition.java

void collect(@NonNull WindowContainer wc) {
    if (mState < STATE_COLLECTING) {
        throw new IllegalStateException("Transition hasn't started collecting.");
    }
    if (!isCollecting()) return;

    snapshotStartState(getAnimatableParent(wc));  // 快照起始状态
    if (mParticipants.contains(wc)) return;

    // 加入 BLASTSync 等待首帧绘制
    if (!isInTransientHide(wc)) {
        mSyncEngine.addToSyncSet(mSyncId, wc);
    }

    // 记录变更信息
    ChangeInfo info = mChanges.get(wc);
    if (info == null) {
        info = new ChangeInfo(wc);
        updateTransientFlags(info);
        mChanges.put(wc, info);
    }
    mParticipants.add(wc);
    recordDisplay(wc.getDisplayContent());

    if (info.mShowWallpaper) {
        wc.mDisplayContent.mWallpaperController.collectTopWallpapers(this);
    }
}

收集了什么

  • WindowContainer(Task、TaskFragment、Activity、WindowToken)
  • 父层级信息(用于后续目标提升)
  • Display 信息
  • 壁纸(如果需要)
  • 起始状态快照(bounds、visibility、rotation 等)

6. 阶段三:请求 Shell 启动 (requestStartTransition)

衔接:阶段5 handleStartResult() 完成启动后,调用本阶段的 requestStartTransition() 通知 Shell。

6.1 ActivityStarter 发起请求

源码: ActivityStarter.javahandleStartResult() 方法内)

if (isIndependentLaunch && transition != null) {
    transitionController.requestStartTransition(transition,
            mTargetTask == null ? started.getTask() : mTargetTask,
            remoteTransition, null /* displayChange */);
}

6.2 TransitionController 构建请求

源码: TransitionController.java

Transition requestStartTransition(@NonNull Transition transition,
        @Nullable Task startTask,
        @Nullable RemoteTransition remoteTransition,
        @Nullable TransitionRequestInfo.DisplayChange displayChange) {
    // ...
    ActivityManager.RunningTaskInfo startTaskInfo = null;
    if (startTask != null) {
        startTaskInfo = startTask.getTaskInfo();
    }

    final TransitionRequestInfo request = new TransitionRequestInfo(transition.mType,
            startTaskInfo, pipChange, remoteTransition, displayChange,
            transition.getFlags(), transition.getSyncId());

    // ★ 通过 Binder 通知 Shell
    mTransitionPlayers.getLast().mPlayer.requestStartTransition(
            transition.getToken(), request);

    if (remoteTransition != null) {
        transition.setRemoteAnimationApp(remoteTransition.getAppThread());
    }
    return transition;
}

6.3 ITransitionPlayer.requestStartTransition

源码: ITransitionPlayer.aidl

void requestStartTransition(in IBinder transitionToken,
        in TransitionRequestInfo request);

Shell 收到后,可以:

  1. 立即调用 IWindowOrganizerController.startTransition() 确认开始
  2. 或者稍后确认(允许 Shell 做准备)
  3. 选择使用内置动画或 RemoteTransition 委托

7. 阶段四:Shell 确认启动与 Leash 构建

衔接:阶段6 requestStartTransition() 通过 Binder 通知 Shell → Shell 调用 IWindowOrganizerController.startTransition(token, wct) 确认 → Core 调用 Transition.start() → BLASTSync 等待所有参与者首帧绘制完成 → 进入 PLAYING 状态构建 TransitionInfo 和 Leash → 通过 onTransitionReady() 传递给 Shell(阶段8)。

Shell 调用 startTransition() 后,Core 进入播放准备阶段。

7.1 Transition.start — 状态转换

源码: Transition.java

void start() {
    if (mState < STATE_COLLECTING) {
        throw new IllegalStateException("Can't start Transition which isn't collecting.");
    }
    mState = STATE_STARTED;
    applyReady();
    mController.updateAnimatingState();
}

7.2 构建 TransitionInfo — 目标提升与 Leash 创建

当所有参与者完成首帧绘制后,Transition 进入 PLAYING 状态,构建 TransitionInfo

getLeashSurface — 获取/创建 LeashTransition.java):

private static SurfaceControl getLeashSurface(WindowContainer wc,
        @Nullable SurfaceControl.Transaction t) {
    final DisplayContent asDC = wc.asDisplayContent();
    if (asDC != null) {
        return asDC.getWindowingLayer();
    }
    if (!wc.mTransitionController.useShellTransitionsRotation()) {
        final WindowToken asToken = wc.asWindowToken();
        if (asToken != null) {
            final SurfaceControl leash = t != null
                    ? asToken.getOrCreateFixedRotationLeash(t)
                    : asToken.getFixedRotationLeash();
            if (leash != null) return leash;
        }
    }
    return wc.getSurfaceControl();
}

calculateTransitionRoots — 创建根 LeashTransition.java):

static void calculateTransitionRoots(@NonNull TransitionInfo outInfo,
        ArrayList<ChangeInfo> sortedTargets,
        @NonNull SurfaceControl.Transaction startT) {
    for (int i = 0; i < sortedTargets.size(); ++i) {
        final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
        if (isWallpaper(wc)) continue;
        final DisplayContent dc = wc.getDisplayContent();
        if (dc == null) continue;
        final int endDisplayId = dc.getDisplayId();

        if (outInfo.findRootIndex(endDisplayId) >= 0) continue;

        WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, wc);
        WindowContainer leashReference = wc;
        while (leashReference.getParent() != ancestor) {
            leashReference = leashReference.getParent();
        }

        // ★ 创建根 Leash
        final SurfaceControl rootLeash = leashReference.makeAnimationLeash()
                .setName("Transition Root: " + leashReference.getName())
                .setCallsite("Transition.calculateTransitionRoots")
                .build();
        startT.setLayer(rootLeash, leashReference.getLastLayer());
        outInfo.addRootLeash(endDisplayId, rootLeash,
                ancestor.getBounds().left, ancestor.getBounds().top);
    }
}

每个 Change 的 Leash 构建Transition.java):

final TransitionInfo.Change change = new TransitionInfo.Change(
        target.mRemoteToken != null
                ? target.mRemoteToken.toWindowContainerToken() : null,
        getLeashSurface(target, startT));

8. 阶段五:onTransitionReady — 动画委托给 Shell

8.1 Core 调用 Shell

源码: Transition.java

// 先构建 finish/cleanup Transaction(无论是否有 Player 都需要)
buildFinishTransaction(mFinishTransaction, info, participantDisplays);
mCleanupTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
buildCleanupTransaction(mCleanupTransaction, info);

if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
    mController.getTransitionPlayer().onTransitionReady(
            mToken, info, transaction, mFinishTransaction);
}

传递给 Shell 的四个参数:

参数类型说明
transitionTokenIBinder标识这个 Transition
infoTransitionInfo所有变化信息 + Leash
startTTransaction动画前需要应用的 Surface 状态
finishTTransaction动画后需要应用的归位操作

8.2 ITransitionPlayer.onTransitionReady

源码: ITransitionPlayer.aidl

void onTransitionReady(in IBinder transitionToken, in TransitionInfo info,
        in SurfaceControl.Transaction t, in SurfaceControl.Transaction finishT);

Shell 收到后:

  1. 应用 startTransaction(设置 Leash 的初始位置、可见性等)
  2. 播放动画(内置动画 或 委托 IRemoteTransition
  3. 动画完成后应用 finishTransaction(Surface 归位)
  4. 调用 finishTransition() 通知 Core

9. 阶段六:远程进程执行动画

9.1 Shell 委托给远程进程

TransitionRequestInfo 中包含 RemoteTransition 时,Shell 可以将动画委托给远程进程(如 Launcher)。

9.2 IRemoteTransition 接口

源码: IRemoteTransition.aidl

oneway interface IRemoteTransition {
    // 开始动画
    void startAnimation(in IBinder token, in TransitionInfo info,
            in SurfaceControl.Transaction t,
            in IRemoteTransitionFinishedCallback finishCallback);

    // 合并动画(新 Transition 合并到正在播放的动画中)
    void mergeAnimation(in IBinder transition, in TransitionInfo info,
            in SurfaceControl.Transaction t, in IBinder mergeTarget,
            in IRemoteTransitionFinishedCallback finishCallback);

    // 接管正在播放的动画
    void takeOverAnimation(in IBinder transition, in TransitionInfo info,
            in SurfaceControl.Transaction t,
            in IRemoteTransitionFinishedCallback finishCallback,
            in WindowAnimationState[] states);

    // 动画被其他 Handler 消费
    void onTransitionConsumed(in IBinder transition, in boolean aborted);
}

9.3 IRemoteTransitionFinishedCallback

源码: IRemoteTransitionFinishedCallback.aidl

interface IRemoteTransitionFinishedCallback {
    void onTransitionFinished(in WindowContainerTransaction wct,
            in SurfaceControl.Transaction sct);
}

远程进程动画完成后调用,可附带 WindowContainerTransaction(修改窗口状态)和额外的 Surface Transaction。

9.4 远程进程的典型实现

// 远程进程(如 Launcher)实现 IRemoteTransition
@Override
public void startAnimation(IBinder token, TransitionInfo info,
        SurfaceControl.Transaction t,
        IRemoteTransitionFinishedCallback finishCallback) {
    // 1. 应用 startTransaction
    t.apply();

    // 2. 遍历 Change 列表,为每个创建动画
    for (TransitionInfo.Change change : info.getChanges()) {
        SurfaceControl leash = change.getLeash();
        if (change.getMode() == TransitionInfo.TRANSIT_OPEN) {
            ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
            anim.setDuration(300);
            anim.addUpdateListener(a -> {
                float fraction = a.getAnimatedFraction();
                SurfaceControl.Transaction ft = new SurfaceControl.Transaction();
                ft.setAlpha(leash, fraction);
                ft.setScale(leash, 0.5f + 0.5f * fraction, 0.5f + 0.5f * fraction);
                ft.apply();
            });
            anim.start();
        }
    }

    // 3. 动画完成后通知 Shell
    finishCallback.onTransitionFinished(null, null);
}

9.5 TransitionInfo.Change 关键字段

字段类型说明
mContainerWindowContainerToken标识参与容器
mLeashSurfaceControl动画 Leash — 远程进程操作此对象
mModeintTRANSIT_OPEN/TRANSIT_CLOSE/TRANSIT_CHANGE...
mStartAbsBoundsRect起始绝对坐标
mEndAbsBoundsRect结束绝对坐标
mSnapshotSurfaceControl起始状态快照
mTaskInfoRunningTaskInfoTask 信息(如果是 Task)
mFlagsint标志位(半透明、壁纸等)

10. 阶段七:finishTransition — 完成与清理

10.1 Shell 通知 Core 完成

Shell 调用 IWindowOrganizerController.finishTransition(token, wct)

10.2 WindowOrganizerController.finishTransition

源码: WindowOrganizerController.java

public void finishTransition(@NonNull IBinder transitionToken,
        @Nullable WindowContainerTransaction t) {
    synchronized (mGlobalLock) {
        final Transition transition = Transition.fromBinder(transitionToken);
        if (t != null) {
            mTransitionController.mFinishingTransition = transition;
            applyTransaction(t, -1, chain, caller);
        }
        mTransitionController.finishTransition(chain);
        mTransitionController.mFinishingTransition = null;
    }
}

10.3 TransitionController.finishTransition

源码: TransitionController.java

void finishTransition(@NonNull ActionChain chain) {
    final Transition record = chain.mTransition;
    mPlayingTransitions.remove(record);
    updateRunningRemoteAnimation(record, false);
    record.finishTransition(chain);  // ★ 委托给 Transition 自身清理

    for (int i = mAnimatingExitWindows.size() - 1; i >= 0; i--) {
        final WindowState w = mAnimatingExitWindows.get(i);
        if (w.mAnimatingExit && w.mHasSurface && !w.inTransition()) {
            w.onExitAnimationDone();
        }
    }
    if (!inTransition()) {
        validateStates();
        mAtm.mWindowManager.onAnimationFinished();
    }
}

10.4 Transition.finishTransition — 核心清理

源码: Transition.java

void finishTransition(@NonNull ActionChain chain) {
    // 关闭 start/finish Transaction
    if (mStartTransaction != null) mStartTransaction.close();
    if (mFinishTransaction != null) mFinishTransaction.close();

    // 应用清理 Transaction(Surface 归位、层级恢复)
    if (mCleanupTransaction != null) {
        mCleanupTransaction.apply();
        mCleanupTransaction = null;
    }

    // 提交不可见的 Activity
    for (int i = 0; i < mParticipants.size(); ++i) {
        final WindowContainer<?> participant = mParticipants.valueAt(i);
        final ActivityRecord ar = participant.asActivityRecord();
        if (ar != null && !ar.isVisibleRequested()) {
            // commitVisibility、快照、PiP 处理
        }
    }
}

10.5 清理 Transaction 的构建

buildCleanupTransaction 负责:

  • 将 Leash 下的 Surface reparent 回原始父节点
  • 销毁临时 Leash
  • 恢复层级顺序
  • 清理快照 Surface

11. 异常与取消处理

11.1 Transition 超时

BLASTSyncEngine 自带超时机制。当参与者长时间未完成首帧绘制,Transition 会自动完成并播放。

11.2 Transition 中止

源码: Transition.java

void abort() {
    if (mState == STATE_ABORT) return;  // 幂等保护
    if (mState == STATE_PENDING) {
        mState = STATE_ABORT;
        return;
    }
    if (mState != STATE_COLLECTING && mState != STATE_STARTED) {
        throw new IllegalStateException("Too late to abort. state=" + mState);
    }
    mState = STATE_ABORT;
    mSyncEngine.abort(mSyncId);  // 清理 sync
    mController.dispatchLegacyAppTransitionCancelled(mTargetDisplays);
    invokeTransitionEndedListeners();
}

11.3 Shell 进程死亡

TransitionController 通过 DeathRecipient 监听 Shell:

// TransitionPlayerRecord 中
player.asBinder().linkToDeath(..., 0);

Shell 死亡后,TransitionController 清空 mTransitionPlayers,回退到 Legacy 路径或直接跳过动画。

11.4 远程动画超时

IRemoteTransition 的远程进程如果无响应,Shell 内部有超时机制强制完成。


12. 关键数据结构

12.1 Transition 状态机

STATE_PENDING → STATE_COLLECTING → STATE_STARTED → STATE_PLAYING → STATE_FINISHED
     ↓               ↓                ↓
  STATE_ABORT    STATE_ABORT      STATE_ABORT
状态说明
PENDING已创建但未开始收集
COLLECTING正在收集参与者
STARTEDShell 已确认,等待首帧绘制
PLAYING动画播放中
FINISHED动画完成,清理中
ABORT已中止

12.2 TransitionInfo 结构

TransitionInfo
├── type: int (TRANSIT_OPEN/CLOSE/CHANGE...)
├── flags: int
├── changes: List<Change>           所有变更(Z-order 自顶向下)
   └── Change
       ├── container: WindowContainerToken
       ├── leash: SurfaceControl          动画操作对象
       ├── mode: int                      OPEN/CLOSE/CHANGE
       ├── startAbsBounds: Rect
       ├── endAbsBounds: Rect
       ├── snapshot: SurfaceControl       起始快照
       ├── taskInfo: RunningTaskInfo
       └── flags: int
└── roots: List<Root>                Display 一个根
    └── Root
        ├── displayId: int
        ├── leash: SurfaceControl           Leash
        └── offset: Point

12.3 RemoteTransition

RemoteTransition
├── mRemoteTransition: IRemoteTransition     远程动画接口
├── mAppThread: IApplicationThread           用于提升进程优先级
└── mDebugName: String                       调试名称

12.4 TransitionRequestInfo

TransitionRequestInfo
├── mType: int                               过渡类型
├── mTriggerTask: RunningTaskInfo            触发 Task
├── mRemoteTransition: RemoteTransition      远程动画(可选)
├── mPipChange: PipChange                    PiP 信息
├── mDisplayChange: DisplayChange            Display 变化(旋转等)
├── mFlags: int                              过渡标志
└── mDebugId: int                            调试 ID

13. 完整时序图

13.1 Shell Transitions 正常流程

Launcher/SystemUI          ATM/WMS (Core)                    TransitionController          Shell (ITransitionPlayer)
     │                         │                                    │                            │
     │ registerTransitionPlayer│                                    │                            │
     │────────────────────────>│                                    │                            │
     │                         │ registerTransitionPlayer(player)   │                            │
     │                         │───────────────────────────────────>│                            │
     │                         │                                    │ mTransitionPlayers.add()   │
     │                         │                                    │                            │
     │ [启动 Activity]          │                                    │                            │
     │ startActivity(options)  │                                    │                            │
     │────────────────────────>│                                    │                            │
     │                         │ ActivityStarter.execute()          │                            │
     │                         │  r.takeRemoteTransition()          │                            │
     │                         │  transitionController.collect(r)   │                            │
     │                         │───────────────────────────────────>│                            │
     │                         │                                    │ Transition.collect(r)       │
     │                         │                                    │  mParticipants.add(r)       │
     │                         │                                    │  mChanges.put(r, info)      │
     │                         │                                    │                            │
     │                         │ handleStartResult()                │                            │
     │                         │  transitionController              │                            │
     │                         │   .requestStartTransition()        │                            │
     │                         │───────────────────────────────────>│                            │
     │                         │                                    │ build TransitionRequestInfo │
     │                         │                                    │                            │
     │                         │                                    │ requestStartTransition()    │
     │                         │                                    │───────────────────────────>│
     │                         │                                    │                            │
     │                         │                                    │     [Shell 决定策略]        │
     │                         │                                    │<───────────────────────────│
     │                         │                                    │ startTransition(token, wct) │
     │                         │                                    │                            │
     │                         │                                    │ Transition.start()          │
     │                         │                                    │  STATE_STARTED              │
     │                         │                                    │                            │
     │                         │         [等待参与者首帧绘制]          │                            │
     │                         │                                    │                            │
     │                         │                                    │ 构建 TransitionInfo         │
     │                         │                                    │  calculateTransitionRoots() │
     │                         │                                    │  getLeashSurface() for each │
     │                         │                                    │  buildFinishTransaction()   │
     │                         │                                    │  STATE_PLAYING              │
     │                         │                                    │                            │
     │                         │                                    │ onTransitionReady(token,    │
     │                         │                                    │   info, startT, finishT)    │
     │                         │                                    │───────────────────────────>│
     │                         │                                    │                            │
     │                         │                                    │        [应用 startT]        │
     │                         │                                    │                            │
     │                         │                                    │     [使用 RemoteTransition  │     │                         │                                    │      委托给 Launcher?]      │
     │                         │                                    │                            │
     │ IRemoteTransition       │                                    │                            │
     │  .startAnimation(...)   │                                    │                            │
     │<════════════════════════════════════════════════════════════════════════════════════════│
     │                         │                                    │                            │
     │ [操作 Leash 执行动画]     │                                    │                            │
     │                         │                                    │                            │
     │ finishCallback          │                                    │                            │
     │  .onTransitionFinished()│                                    │                            │
     │════════════════════════>│                                    │                            │
     │                         │                                    │                            │
     │                         │                                    │        [Shell 收到完成]     │
     │                         │                                    │        [应用 finishT]       │
     │                         │                                    │<───────────────────────────│
     │                         │                                    │ finishTransition(token,wct) │
     │                         │                                    │                             │
     │                         │                                    │ TransitionController        │
     │                         │                                    │  .finishTransition()        │
     │                         │                                    │  Transition.finishTransition│
     │                         │                                    │   mCleanupTransaction.apply │
     │                         │                                    │   reparent surface → parent │
     │                         │                                    │   remove leash              │
     │                         │                                    │   STATE_FINISHED            │
     │                         │                                    │                             │

13.2 异常流程

场景1: Shell 进程死亡
TransitionController (DeathRecipient)
  └─ mTransitionPlayers 清空
      └─ isShellTransitionsEnabled() = false
          └─ 后续转场回退到 Legacy 或跳过动画

场景2: 参与者首帧绘制超时
BLASTSyncEngine
  └─ timeout
      └─ Transition.start() 强制进入
          └─ onTransitionReady 即使部分未就绪也发送

场景3: 远程动画无响应
Shell 内部超时
  └─ 强制调用 finishTransition()
      └─ Core 执行清理

场景4: Transition 被中止
Transition.abort()
  └─ STATE_ABORT
      └─ 清理 sync、通知 Shell onTransitionConsumed()

附录A:Leash 生命周期总结

Shell Transitions 路径:

创建阶段:
  TransitionController.createAndStartCollecting(TRANSIT_OPEN)
    → new Transition(type, flags, controller, syncEngine)
    → moveToCollecting(transit)  // 进入 COLLECTING 状态
  ActivityStarter.execute()
    → Transition.collect(r)  // 收集 Activity 参与者
    → handleStartResult() → requestStartTransition()  // 请求 Shell

就绪阶段:
  Shell 调用 IWindowOrganizerController.startTransition()
    → Transition.start()  // STATE_STARTED
    → BLASTSync 等待所有参与者首帧绘制
    → 进入 PLAYING 状态

Leash 构建阶段:
  Transition 构建 TransitionInfo
    → calculateTransitionRoots() → makeAnimationLeash() 创建根 Leash
    → getLeashSurface() 获取/创建各 Target Leash
    → buildFinishTransaction() 构建归位 Transaction
    → buildCleanupTransaction() 构建清理 Transaction

传递阶段:
  ITransitionPlayer.onTransitionReady(token, info, startT, finishT)
    → Shell 收到 TransitionInfo
      → 可委托 IRemoteTransition.startAnimation()
        → 远程进程通过 change.getLeash() 操作 Surface

销毁阶段:
  IWindowOrganizerController.finishTransition(token, wct)
    → TransitionController.finishTransition()
      → Transition.finishTransition()
        → mCleanupTransaction.apply()
          → reparent(surface, parent)   // 子 Surface 回归原父
          → remove(leash)               // 销毁 Leash
          → onAnimationLeashLost()      // 通知清理
        → commitVisibility 处理不可见 Activity

附录B:Legacy App Transition 快速参考

Legacy 在 Shell 启用时被短路。以下为 Legacy 路径的关键节点:

阶段关键方法源码位置
注册AppTransition.overridePendingAppTransitionRemote()AppTransition.java
决策AppTransitionController.overrideWithRemoteAnimationIfSet()AppTransitionController.java
Leash 创建SurfaceAnimator.createAnimationLeash()SurfaceAnimator.java
捕获RemoteAnimationAdapterWrapper.startAnimation()RemoteAnimationController.java
启动RemoteAnimationController.goodToGo()RemoteAnimationController.java
Target 构建ActivityRecord.createRemoteAnimationTarget()ActivityRecord.java
Binder 调用IRemoteAnimationRunner.onAnimationStart()AIDL 接口
完成IRemoteAnimationFinishedCallback.onAnimationFinished()AIDL 接口
Leash 销毁SurfaceAnimator.removeLeash()SurfaceAnimator.java
超时TIMEOUT_MS = 10000RemoteAnimationController.java

Legacy 核心类:

  • RemoteAnimationController — 生命周期管理(10s 超时、死亡监听)
  • RemoteAnimationRecord/AdapterWrapper — 连接 SurfaceAnimator 和 RemoteAnimationTarget
  • RemoteAnimationTarget — 扁平数组(每个 Activity 一个)
  • IRemoteAnimationRunneronAnimationStart(targets[]) / onAnimationCancelled()

附录C:核心 QA

Q1: Shell Transitions 启用后,Legacy 还能触发吗?

不能。 Shell 启用后,Legacy 路径被完全短路:

  1. AppTransition.prepareAppTransition() 返回 false(AppTransition.java),不注册任何 Legacy 过渡
  2. 因此 AppTransition.isReady() 永远不会变为 true,handleAppTransitionReady() 不会被调用
  3. 即使 Activity Embedding 中 overrideWithTaskFragmentRemoteAnimation() 调用了 overridePendingAppTransitionRemote(),由于 prepareAppTransition 未被调用,isTransitionSet() 为 false,setReady() 也不会触发

在 Shell 模式下,所有窗口转场(包括 Activity Embedding)都通过 TransitionController 收集和处理。

Q2: Shell 路径和 Legacy 路径共享哪些机制?

  • Leash 机制:两者都通过 SurfaceControl Leash 实现,但创建方式不同。Shell 通过 Transition.calculateTransitionRoots() 直接创建;Legacy 通过 SurfaceAnimator.createAnimationLeash()
  • SurfaceControl.Transaction:两者都使用 Transaction 操作 Surface
  • ActivityRecord/Task/WindowContainer:同一套窗口层级结构

Q3: RemoteTransition 和 RemoteAnimationAdapter 有什么区别?

维度RemoteTransition (Shell)RemoteAnimationAdapter (Legacy)
接口IRemoteTransition.startAnimation()IRemoteAnimationRunner.onAnimationStart()
数据TransitionInfo(层级结构)RemoteAnimationTarget[](扁平数组)
完成回调IRemoteTransitionFinishedCallbackIRemoteAnimationFinishedCallback
附加能力mergeAnimation(), takeOverAnimation()仅 start/cancel
使用者Shell 委托Core 直接调用

Q4: Shell Transitions 相比 Legacy 的设计差异?

  1. 层级感知TransitionInfo 保留完整的窗口层级关系,动画可以基于 Task/TaskFragment 整体操作;Legacy 的 RemoteAnimationTarget[] 是扁平数组
  2. 并行支持:多 Track 允许多个 Transition 同时播放;Legacy 同一时间只能有一个
  3. 编排灵活性:Shell 可以 veto、修改或委托 Transition,而 Legacy 由 Core 单方面决定
  4. 合并能力IRemoteTransition.mergeAnimation() 允许新动画合并到正在播放的动画中;Legacy 不支持
  5. 同步机制:Shell 使用 BLASTSync 精确等待首帧;Legacy 使用 AppTransition.isReady() 轮询

Q5: Leash 机制是什么?为什么要引入 Leash?

Leash 是 WMS 为每个参与动画的 WindowContainer 创建的一个中间 SurfaceControl 层。动画期间,原始窗口 Surface 被 reparent 到 Leash 下,远程进程只操作 Leash(alpha、scale、translation 等),不直接接触原始 Surface。

引入原因:

  1. 安全隔离:原始 Surface 控制权始终在 WMS 手中,远程进程无法恶意操作或拒绝释放
  2. 互不干扰:动画过程中 Activity 仍通过 Choreographer 正常绘制新帧,两者互不干扰
  3. 快速复位:动画结束后一行 reparent(surface, parent) 即可毫秒级恢复原始层级,remove(leash) 销毁 Leash

Q6: BLASTSync 是什么?在动画中起什么作用?

BLASTSync 是 Android 的帧同步机制。在 Transition 收集阶段,每个参与者被加入 BLASTSyncEngine 的 SyncSet,系统等待所有参与者完成首帧绘制后才进入 PLAYING 状态。

这保证了 onTransitionReady 发送给 Shell 时,所有参与者的 Surface 都已有内容可显示,避免动画中出现黑屏/白屏闪烁。

Q7: Transition 的目标提升(promotion)是什么?

当同一个父容器下的多个子容器都参与同一个 Transition 时,系统会将动画目标提升到更高层级(例如从 Activity 提升到 Task)。这样做的好处:

  • 只需一个 Leash 统一控制,避免多个独立 Leash 导致的画面撕裂
  • 远程进程处理更简单,只需操作一个 Leash
  • Transition.calculateTransitionRoots() 中通过 findCommonAncestor() 找到公共祖先来决定提升层级

Q8: startTransaction 和 finishTransaction 分别做什么?

  • startTransaction:动画开始前应用。设置 Leash 的初始位置、可见性、crop 等,确保动画从正确的起始状态开始
  • finishTransaction:动画结束后应用。将 Surface 从 Leash reparent 回原始父节点,恢复正确的 Z-order 层级,重置 alpha/scale 等属性

两者都由 Transition.javaonTransitionReady 之前构建,随 TransitionInfo 一起发送给 Shell。Shell 需要在正确的时机 apply 它们。

Q9: RemoteTransition 的 mergeAnimation 有什么用?

当一个新的 Transition 需要播放,但当前已有一个相同类型的动画正在播放时,Shell 可以选择将新 Transition 合并到现有动画中,而不是打断重新开始。

典型场景:用户快速连续启动两个 Activity,第二个启动动画可以合并到第一个中,提供更流畅的视觉体验。IRemoteTransition.mergeAnimation() 就是 Shell 用来委托这个合并操作的接口。

Q10: Shell 进程死亡后 WMS 怎么处理?

TransitionController 通过 DeathRecipient 监听 Shell 进程:

  1. Shell 死亡 → binderDied() 回调触发
  2. TransitionController 清空 mTransitionPlayers
  3. isShellTransitionsEnabled() 返回 false
  4. 正在播放的 Transition 通过 flushRunningTransitions() 强制完成(apply cleanup transaction)
  5. 后续窗口转场没有动画,直接切换
  6. Shell 重启后重新调用 registerTransitionPlayer() 恢复正常

Q11: 如何自定义 App 启动动画?

Launcher 可以通过以下方式自定义启动动画:

// 通过 ActivityOptions 传入 RemoteTransition(Shell 模式)
RemoteTransition transition = new RemoteTransition(
    myRemoteTransitionImpl,  // 实现 IRemoteTransition 接口
    appThread,
    "MyCustomLaunchAnimation"
);
ActivityOptions options = ActivityOptions.makeRemoteAnimation(transition);
startActivity(intent, options.toBundle());

动画实现中,通过 TransitionInfo.getChanges() 获取所有变化的 Leash,然后用 ValueAnimatorSpringAnimation 驱动 alpha/scale/translation 等属性,完成后调用 finishCallback.onTransitionFinished()

Q12: 为什么动画期间要禁用输入事件?

防止 点击劫持(Tapjacking)。动画期间,窗口处于不受当前应用直接控制的 Leash 中,恶意远程动画可能在转场期间伪造 UI 界面,诱骗用户点击。

  • Shell Transitions 路径:WMS 通过 ActivityRecordInputSinkfinishTransition() 时更新 input-sink 状态
  • Legacy 路径:通过 setDropInputForAnimation(true) 丢弃所有输入事件,动画结束后恢复