基于Android 15 (AOSP)源码,完整梳理远程动画的注册、收集、请求、启动、执行、完成与清理全流程。
本文以 Shell Transitions 为主要分析路径(Android 15 默认启用),Legacy App Transition 作为对比简要保留。两者共享 Leash/SurfaceControl 底层机制。
目录
- 概述
- [Shell Transitions vs Legacy App Transition 对比](#Shell Transitions vs Legacy App Transition 对比 "#2-shell-transitions-vs-legacy-app-transition-%E5%AF%B9%E6%AF%94")
- 核心类关系图
- [阶段一:TransitionPlayer 注册](#阶段一:TransitionPlayer 注册 "#4-%E9%98%B6%E6%AE%B5%E4%B8%80transitionplayer-%E6%B3%A8%E5%86%8C")
- [阶段二:Transition 创建与收集](#阶段二:Transition 创建与收集 "#5-%E9%98%B6%E6%AE%B5%E4%BA%8Ctransition-%E5%88%9B%E5%BB%BA%E4%B8%8E%E6%94%B6%E9%9B%86")
- [阶段三:请求 Shell 启动 (requestStartTransition)](#阶段三:请求 Shell 启动 (requestStartTransition) "#6-%E9%98%B6%E6%AE%B5%E4%B8%89%E8%AF%B7%E6%B1%82-shell-%E5%90%AF%E5%8A%A8-requeststarttransition")
- [阶段四:Shell 确认启动与 Leash 构建](#阶段四:Shell 确认启动与 Leash 构建 "#7-%E9%98%B6%E6%AE%B5%E5%9B%9Bshell-%E7%A1%AE%E8%AE%A4%E5%90%AF%E5%8A%A8%E4%B8%8E-leash-%E6%9E%84%E5%BB%BA")
- [阶段五:onTransitionReady --- 动画委托给 Shell](#阶段五:onTransitionReady — 动画委托给 Shell "#8-%E9%98%B6%E6%AE%B5%E4%BA%94ontransitionready--%E5%8A%A8%E7%94%BB%E5%A7%94%E6%89%98%E7%BB%99-shell")
- 阶段六:远程进程执行动画
- [阶段七:finishTransition --- 完成与清理](#阶段七:finishTransition — 完成与清理 "#10-%E9%98%B6%E6%AE%B5%E4%B8%83finishtransition--%E5%AE%8C%E6%88%90%E4%B8%8E%E6%B8%85%E7%90%86")
- 异常与取消处理
- 关键数据结构
- 完整时序图
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 创建
SurfaceControlLeash,Shell/远程进程通过操作 Leash 实现动画 - BLASTSync 同步 :通过
BLASTSyncEngine确保所有参与者在动画开始前完成首帧绘制 - 并行 Transition:支持多个 Transition 同时在不同 track 上播放
1.3 四阶段模型
sql
Request → Collect → Play → Finish
↓ ↓ ↓ ↓
Shell决定 Core收集 Shell/远程播放 Core清理
2. Shell Transitions vs Legacy App Transition 对比
2.1 分歧点源码
两个系统在同一处代码产生分支------DisplayContent.java:
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):
java
boolean isShellTransitionsEnabled() {
return !mTransitionPlayers.isEmpty(); // 只要 Shell 注册了 Player 就启用
}
当 Shell 启用时,Legacy 的 AppTransition.prepareAppTransition() 直接短路 (AppTransition.java):
java
boolean prepareAppTransition(@TransitionType int transit, @TransitionFlags int flags) {
if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
return false; // ← Legacy 完全跳过
}
// Legacy 逻辑...
}
2.2 API 对照
| 维度 | Shell Transitions | Legacy App Transition |
|---|---|---|
| 注册接口 | WindowOrganizer.registerTransitionPlayer(ITransitionPlayer) |
AppTransition.overridePendingAppTransitionRemote() |
| 触发端 | TransitionController 收集变更后通知 Shell |
AppTransitionController.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 启动应用):
scss
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(对比):
scss
RootWindowContainer.checkAppTransitionReady()
→ AppTransitionController.handleAppTransitionReady()
→ AppTransition.goodToGo()
→ RemoteAnimationController.goodToGo()
→ IRemoteAnimationRunner.onAnimationStart() // Binder → 远程
→ IRemoteAnimationFinishedCallback.onAnimationFinished() // Binder → Core
→ RemoteAnimationController.onAnimationFinished()
3. 核心类关系图
yaml
┌───────────────────────────────────────────────────────────────────────┐
│ 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
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
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
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 之前就创建了:
java
// 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 创建和收集启动:
java
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 的外层):
java
RemoteTransition remoteTransition = r.takeRemoteTransition();
RemoteTransition(非 RemoteAnimationAdapter)是 Shell 路径的远程动画载体,包含 IRemoteTransition 和 IApplicationThread。
5.3 收集参与者 --- collect
源码 : ActivityStarter.java
同样在 execute() 方法内部,调用 startActivityInner 之前收集:
java
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
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.java(handleStartResult() 方法内)
java
if (isIndependentLaunch && transition != null) {
transitionController.requestStartTransition(transition,
mTargetTask == null ? started.getTask() : mTargetTask,
remoteTransition, null /* displayChange */);
}
6.2 TransitionController 构建请求
源码 : TransitionController.java
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
java
void requestStartTransition(in IBinder transitionToken,
in TransitionRequestInfo request);
Shell 收到后,可以:
- 立即调用
IWindowOrganizerController.startTransition()确认开始 - 或者稍后确认(允许 Shell 做准备)
- 选择使用内置动画或
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
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 --- 获取/创建 Leash (Transition.java):
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 --- 创建根 Leash (Transition.java):
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):
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
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 的四个参数:
| 参数 | 类型 | 说明 |
|---|---|---|
transitionToken |
IBinder | 标识这个 Transition |
info |
TransitionInfo | 所有变化信息 + Leash |
startT |
Transaction | 动画前需要应用的 Surface 状态 |
finishT |
Transaction | 动画后需要应用的归位操作 |
8.2 ITransitionPlayer.onTransitionReady
源码 : ITransitionPlayer.aidl
java
void onTransitionReady(in IBinder transitionToken, in TransitionInfo info,
in SurfaceControl.Transaction t, in SurfaceControl.Transaction finishT);
Shell 收到后:
- 应用
startTransaction(设置 Leash 的初始位置、可见性等) - 播放动画(内置动画 或 委托
IRemoteTransition) - 动画完成后应用
finishTransaction(Surface 归位) - 调用
finishTransition()通知 Core
9. 阶段六:远程进程执行动画
9.1 Shell 委托给远程进程
当 TransitionRequestInfo 中包含 RemoteTransition 时,Shell 可以将动画委托给远程进程(如 Launcher)。
9.2 IRemoteTransition 接口
源码 : IRemoteTransition.aidl
java
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
java
interface IRemoteTransitionFinishedCallback {
void onTransitionFinished(in WindowContainerTransaction wct,
in SurfaceControl.Transaction sct);
}
远程进程动画完成后调用,可附带 WindowContainerTransaction(修改窗口状态)和额外的 Surface Transaction。
9.4 远程进程的典型实现
java
// 远程进程(如 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 关键字段
| 字段 | 类型 | 说明 |
|---|---|---|
mContainer |
WindowContainerToken | 标识参与容器 |
mLeash |
SurfaceControl | 动画 Leash --- 远程进程操作此对象 |
mMode |
int | TRANSIT_OPEN/TRANSIT_CLOSE/TRANSIT_CHANGE... |
mStartAbsBounds |
Rect | 起始绝对坐标 |
mEndAbsBounds |
Rect | 结束绝对坐标 |
mSnapshot |
SurfaceControl | 起始状态快照 |
mTaskInfo |
RunningTaskInfo | Task 信息(如果是 Task) |
mFlags |
int | 标志位(半透明、壁纸等) |
10. 阶段七:finishTransition --- 完成与清理
10.1 Shell 通知 Core 完成
Shell 调用 IWindowOrganizerController.finishTransition(token, wct)。
10.2 WindowOrganizerController.finishTransition
源码 : WindowOrganizerController.java
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
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
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
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:
java
// 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 | 正在收集参与者 |
| STARTED | Shell 已确认,等待首帧绘制 |
| PLAYING | 动画播放中 |
| FINISHED | 动画完成,清理中 |
| ABORT | 已中止 |
12.2 TransitionInfo 结构
yaml
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
yaml
RemoteTransition
├── mRemoteTransition: IRemoteTransition ← 远程动画接口
├── mAppThread: IApplicationThread ← 用于提升进程优先级
└── mDebugName: String ← 调试名称
12.4 TransitionRequestInfo
yaml
TransitionRequestInfo
├── mType: int ← 过渡类型
├── mTriggerTask: RunningTaskInfo ← 触发 Task
├── mRemoteTransition: RemoteTransition ← 远程动画(可选)
├── mPipChange: PipChange ← PiP 信息
├── mDisplayChange: DisplayChange ← Display 变化(旋转等)
├── mFlags: int ← 过渡标志
└── mDebugId: int ← 调试 ID
13. 完整时序图
13.1 Shell Transitions 正常流程
scss
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 异常流程
css
场景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 生命周期总结
css
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 = 10000 |
RemoteAnimationController.java |
Legacy 核心类:
RemoteAnimationController--- 生命周期管理(10s 超时、死亡监听)RemoteAnimationRecord/AdapterWrapper--- 连接 SurfaceAnimator 和 RemoteAnimationTargetRemoteAnimationTarget--- 扁平数组(每个 Activity 一个)IRemoteAnimationRunner---onAnimationStart(targets[])/onAnimationCancelled()
附录C:核心 QA
Q1: Shell Transitions 启用后,Legacy 还能触发吗?
不能。 Shell 启用后,Legacy 路径被完全短路:
AppTransition.prepareAppTransition()返回 false(AppTransition.java),不注册任何 Legacy 过渡- 因此
AppTransition.isReady()永远不会变为 true,handleAppTransitionReady()不会被调用 - 即使 Activity Embedding 中
overrideWithTaskFragmentRemoteAnimation()调用了overridePendingAppTransitionRemote(),由于prepareAppTransition未被调用,isTransitionSet()为 false,setReady()也不会触发
在 Shell 模式下,所有窗口转场(包括 Activity Embedding)都通过 TransitionController 收集和处理。
Q2: Shell 路径和 Legacy 路径共享哪些机制?
- Leash 机制 :两者都通过
SurfaceControlLeash 实现,但创建方式不同。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[](扁平数组) |
| 完成回调 | IRemoteTransitionFinishedCallback |
IRemoteAnimationFinishedCallback |
| 附加能力 | mergeAnimation(), takeOverAnimation() |
仅 start/cancel |
| 使用者 | Shell 委托 | Core 直接调用 |
Q4: Shell Transitions 相比 Legacy 的设计差异?
- 层级感知 :
TransitionInfo保留完整的窗口层级关系,动画可以基于 Task/TaskFragment 整体操作;Legacy 的RemoteAnimationTarget[]是扁平数组 - 并行支持:多 Track 允许多个 Transition 同时播放;Legacy 同一时间只能有一个
- 编排灵活性:Shell 可以 veto、修改或委托 Transition,而 Legacy 由 Core 单方面决定
- 合并能力 :
IRemoteTransition.mergeAnimation()允许新动画合并到正在播放的动画中;Legacy 不支持 - 同步机制 :Shell 使用 BLASTSync 精确等待首帧;Legacy 使用
AppTransition.isReady()轮询
Q5: Leash 机制是什么?为什么要引入 Leash?
Leash 是 WMS 为每个参与动画的 WindowContainer 创建的一个中间 SurfaceControl 层。动画期间,原始窗口 Surface 被 reparent 到 Leash 下,远程进程只操作 Leash(alpha、scale、translation 等),不直接接触原始 Surface。
引入原因:
- 安全隔离:原始 Surface 控制权始终在 WMS 手中,远程进程无法恶意操作或拒绝释放
- 互不干扰:动画过程中 Activity 仍通过 Choreographer 正常绘制新帧,两者互不干扰
- 快速复位 :动画结束后一行
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.java 在 onTransitionReady 之前构建,随 TransitionInfo 一起发送给 Shell。Shell 需要在正确的时机 apply 它们。
Q9: RemoteTransition 的 mergeAnimation 有什么用?
当一个新的 Transition 需要播放,但当前已有一个相同类型的动画正在播放时,Shell 可以选择将新 Transition 合并到现有动画中,而不是打断重新开始。
典型场景:用户快速连续启动两个 Activity,第二个启动动画可以合并到第一个中,提供更流畅的视觉体验。IRemoteTransition.mergeAnimation() 就是 Shell 用来委托这个合并操作的接口。
Q10: Shell 进程死亡后 WMS 怎么处理?
TransitionController 通过 DeathRecipient 监听 Shell 进程:
- Shell 死亡 →
binderDied()回调触发 TransitionController清空mTransitionPlayersisShellTransitionsEnabled()返回 false- 正在播放的 Transition 通过
flushRunningTransitions()强制完成(apply cleanup transaction) - 后续窗口转场没有动画,直接切换
- Shell 重启后重新调用
registerTransitionPlayer()恢复正常
Q11: 如何自定义 App 启动动画?
Launcher 可以通过以下方式自定义启动动画:
java
// 通过 ActivityOptions 传入 RemoteTransition(Shell 模式)
RemoteTransition transition = new RemoteTransition(
myRemoteTransitionImpl, // 实现 IRemoteTransition 接口
appThread,
"MyCustomLaunchAnimation"
);
ActivityOptions options = ActivityOptions.makeRemoteAnimation(transition);
startActivity(intent, options.toBundle());
动画实现中,通过 TransitionInfo.getChanges() 获取所有变化的 Leash,然后用 ValueAnimator 或 SpringAnimation 驱动 alpha/scale/translation 等属性,完成后调用 finishCallback.onTransitionFinished()。
Q12: 为什么动画期间要禁用输入事件?
防止 点击劫持(Tapjacking)。动画期间,窗口处于不受当前应用直接控制的 Leash 中,恶意远程动画可能在转场期间伪造 UI 界面,诱骗用户点击。
- Shell Transitions 路径 :WMS 通过
ActivityRecordInputSink在finishTransition()时更新 input-sink 状态 - Legacy 路径 :通过
setDropInputForAnimation(true)丢弃所有输入事件,动画结束后恢复