Shell Transition 机制详解

Shell Transition 机制详解

面向 WMS 开发者的完整指南

基于 android 16 代码库分析,覆盖 Shell Transition 架构。 关键源码路径:frameworks/base/services/core/java/com/android/server/wm/、 frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/


一、为什么要理解 Shell Transition

1.1 传统 App Transition 的局限

在 Shell Transition 出现之前,WMS 自己管理动画(AppTransition),存在三个问题:

  1. WMS 耦合过重:动画逻辑与窗口管理逻辑混在一起,改动画需要改 WMS 核心代码
  2. 扩展性差:难以支持折叠屏展开/折叠、分屏到自由窗口切换等复杂场景
  3. 多 Surface 同步困难:多个窗口(旧 Activity + 新 Activity + Task 容器)动画时,各自 buffer 到达 SurfaceFlinger 的时间不可控,容易出现闪烁、撕裂

1.2 Shell Transition 的核心思想

WMS 只负责窗口状态管理,Shell(SystemUI)负责动画播放。 两者通过 Binder IPC 通信。

关键 AIDL 接口 ITransitionPlayer(frameworks/base/core/java/android/window/ITransitionPlayer.aidl):

java 复制代码
oneway interface ITransitionPlayer {
    // WMS → Shell:通知 Shell 启动过渡
    void requestStartTransition(in IBinder transitionToken, in TransitionRequestInfo request);

    // WMS → Shell:通知 Shell 动画可以开始(所有参与者已绘好)
    void onTransitionReady(in IBinder transitionToken, in TransitionInfo info,
            in SurfaceControl.Transaction t, in SurfaceControl.Transaction finishT);
}

Shell 通过 WindowOrganizerController(frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java)回告 WMS。


二、架构概览

2.1 进程边界与核心类

scss 复制代码
┌──────────────────────────┐              ┌──────────────────────────────┐
│   WMS (system_server)    │              │   Shell (SystemUI 进程)       │
├──────────────────────────┤              ├──────────────────────────────┤
│ TransitionController     │──request───→│ Transitions                  │
│   .requestStartTransition│  StartTrans │   .requestStartTransition()   │
│   .moveToCollecting      │              │     ├─ dispatchRequest()      │
│   .moveToPlaying         │              │     └─ mOrganizer.startTransition()│
│                          │              │         ↓ (Binder 回告)       │
│ Transition (状态机)       │              │                              │
│   .onTransactionReady    │──onTrans──→│   TransitionHandler 链:     │
│   .finishTransition      │  itionReady │   ┌──────────────────────┐   │
│                          │              │   │DefaultTransitionHandler│  │
│ BLASTSyncEngine          │              │   │RemoteTransitionHandler │  │
│   .onSurfacePlacement    │              │   │FoldTransitionHandler   │  │
│                          │              │   └──────────────────────┘   │
│ WindowOrganizerController│←──finish───│                              │
│   .startTransition       │  Transition│   Observer 链:               │
│   .finishTransition      │  (Binder)  │   HomeTransitionObserver       │
│                          │              │   FocusTransitionObserver      │
└──────────────────────────┘              │ .finishTransitionWithState() │
                                          └──────────────────────────────┘

2.2 关键类速查

类 进程 核心职责 源码路径
TransitionController WMS 过渡的创建、收集、播放、完成全流程编排 .../wm/TransitionController.java
Transition WMS 单个 transition 的状态机 + BLAST 同步回调入口 .../wm/Transition.java
BLASTSyncEngine WMS 等待所有参与窗口绘制完成,合并事务 .../wm/BLASTSyncEngine.java
Transitions Shell Shell 端过渡总控,分发动画到 Handler 链 .../transition/Transitions.java
TransitionPlayerImpl Shell ITransitionPlayer.Stub 实现,接收 WMS 通知 .../transition/Transitions.java (inner)
DefaultTransitionHandler Shell 系统默认动画处理器 .../transition/DefaultTransitionHandler.java
WindowOrganizerController WMS Shell→WMS 的 Binder 服务端,处理 startTransition() / finishTransition() .../wm/WindowOrganizerController.java

三、完整流程:从点击到动画结束

3.1 时序图

sequenceDiagram participant App as Launcher/Activity participant TC as TransitionController participant T as Transition participant WOC as WindowOrganizerController participant BSE as BLASTSyncEngine participant Shell as Transitions(SystemUI) Note over App,Shell: 阶段 1: 创建 + 请求 App->>TC: startActivity() 触发窗口状态变化 TC->>TC: createTransition(TRANSIT_OPEN) TC->>TC: moveToCollecting(transition) TC->>T: mCollectingTransition = this, mState = STATE_COLLECTING TC->>Shell: requestStartTransition(token, request) [Binder] Note over App,Shell: 阶段 2: Handler 认领 + 同步回告 startTransition rect rgba(200,200,255,0.2) Note right of Shell: 这三步在 requestStartTransition() 方法<br/>内部同步完成,不是分阶段 Shell->>Shell: dispatchRequest() → Handler 链认领 Shell->>Shell: mOrganizer.startTransition(token, wct) [Binder 回告] WOC->>T: transition.start() → STATE_STARTED WOC->>WOC: applyTransaction(wct) end Note over App,Shell: 阶段 3: BLAST 等待 + 窗口绘制完成 App->>App: ViewRootImpl.performTraversals() 绘制 App->>TC: Binder → WindowState.finishDrawing(t) BSE->>BSE: onSurfacePlacement() 检查所有成员 BSE->>T: tryFinish() → 所有成员就绪 → onTransactionReady() T->>T: calculateTargets() + calculateTransitionInfo() T->>T: assignTrack() + moveToPlaying() T->>Shell: onTransitionReady(token, info, startT, finishT) [Binder] Note over App,Shell: 阶段 4: Shell 播放动画 Shell->>Shell: dispatchReady() → playTransition() Shell->>Shell: handler.startAnimation() loop 逐帧 Shell->>Shell: SurfaceControl.Transaction 提交变换 end Note over App,Shell: 阶段 5: 完成 + 清理 Shell->>WOC: organizer.finishTransitionWithState(token, wct) [Binder] WOC->>TC: finishTransition(chain) TC->>T: finishTransition(chain) T->>T: 提交不可见容器, 清理 starting window

3.2 阶段详解

阶段 1:创建 Transition 并请求 Shell 启动

入口 :Activity 启动导致窗口层级变化(ActivityRecord.setVisible() → collectExistenceChange() 等)

scss 复制代码
startActivity()
  → ActivityRecord.setVisible() / removeApp()
    → mTransitionController.collect(this)              // 收集参与者
    → mTransitionController.requestTransitionIfNeeded()
      → createTransition(type, flags)                  // 创建 Transition
      → moveToCollecting(transition)                   // 进入收集状态
      → requestStartTransition(transition, ...)        // 通知 Shell
        → mPlayer.requestStartTransition(token, request)  // Binder 调用
阶段 2:Handler 认领 + 同步回告 startTransition

这是整个流程中最容易误解的环节 。startTransition 不是 Shell "处理完" 之后再发起的独立回告,而是 requestStartTransition() 方法内部同步执行的最后一步。

Shell 侧完整流程 (Transitions.requestStartTransition(), 第 1860 行):

java 复制代码
void requestStartTransition(@NonNull IBinder transitionToken,
        @Nullable TransitionRequestInfo request) {
    // 步骤 1: 创建 ActiveTransition 放入 mKnownTransitions
    final ActiveTransition active = new ActiveTransition(transitionToken);
    mKnownTransitions.put(transitionToken, active);

    // 步骤 2: Handler 链认领
    Pair<TransitionHandler, WindowContainerTransaction> requestResult =
            dispatchRequestWithTracing(transitionToken, request, /* skip= */ null);
    active.mHandler = requestResult.first;
    wct = requestResult.second;

    // 步骤 3: 显示变化分发(如旋转)
    if (request.getDisplayChange() != null) {
        mDisplayController.onDisplayChangeRequested(wct, ...);
    }

    // 步骤 4: 同步回告 WMS 正式启动(同一方法内!)
    mOrganizer.startTransition(transitionToken, wct);   // ← 直接调用,不延迟
}

WMS 侧 (WindowOrganizerController.startTransition(), 第 367 行):

java 复制代码
public void startTransition(@NonNull IBinder transitionToken,
        @Nullable WindowContainerTransaction t) {
    // 通过 token 找到 WMS 侧的 Transition 对象
    Transition transition = Transition.fromBinder(transitionToken);

    // 检查 transition 是否还在收集状态
    if (!transition.isCollecting() && !transition.isForcePlaying()) {
        // Shell 响应超时,WM 状态可能已不正确
        Slog.e(TAG, "Trying to start a transition that isn't collecting...");
    }

    // 关键:调用 transition.start() → STATE_COLLECTING → STATE_STARTED
    final ActionChain chain = mService.mChainTracker.start("startTransit", transition);
    transition.start();                                 // ← 状态切换

    // 应用 Shell 返回的 WCT(如有)
    applyTransaction(wct, -1 /* syncId */, chain, caller);
}

transition.start() 做了什么 (Transition.java):

java 复制代码
void start() {
    if (mState != STATE_COLLECTING) return;
    mState = STATE_STARTED;   // ← 核心:进入 STARTED 状态
    // 此时 BLAST 同步引擎开始等待所有参与窗口绘制完成
    // 但还没有调用 setAllReady()!
}

注意 :startTransition 只把状态从 STATE_COLLECTING 推进到 STATE_STARTED,start() 本身只调用 applyReady(),不调用 setAllReady()。后续推进到播放并非依赖 setAllReady()------setAllReady() 属于另一条独立路径(如 playNow()、直接由 Shell 创建的 transition 中的 WindowOrganizerController.setAllReadyIfNeeded()),不在 BLAST 同步完成 → onTransactionReady 这条主流程内。

阶段 3:BLAST 同步 + 回调 Shell

这是整个机制的核心,详见第四节。

阶段 4:Shell 播放动画
scss 复制代码
Transitions.onTransitionReady(token, info, startT, finishT)
  → dispatchReady(active)
    → processReadyQueue()
      → playTransition(active)
        → active.mHandler.startAnimation(token, info, startT, finishT, onFinish)
        → DefaultSurfaceAnimator.buildSurfaceAnimation()
        → ValueAnimator 逐帧 → SurfaceControl.Transaction.apply()
阶段 5:动画结束,WMS 清理
css 复制代码
Shell: onAnimationFinished → onFinish(token, wct)
  → mOrganizer.finishTransitionWithState(token, wct, null, state)

WMS: WindowOrganizerController.finishTransitionWithState()
  → TransitionController.finishTransition(chain)
  → Transition.finishTransition(chain)
    → 从 mPlayingTransitions 移除
    → 提交不可见容器、清理 starting window、通知监听器

四、BLAST 同步机制深度解析

4.1 BLAST 解决的问题

多个窗口(旧 Activity、新 Activity、Task 容器、Wallpaper 等)各自绘制完成后,buffer 到达 SurfaceFlinger 的时间不可控。如果各自提交,会出现闪烁、撕裂。

BLAST(Buffer-Limited Asynchronous Surface Transactions)的做法是:等待所有窗口都准备好,合并所有事务,统一提交。

4.2 三个核心概念

4.2.1 SyncState(每个 WindowContainer 的同步状态)
java 复制代码
SYNC_STATE_NONE             = 0   // 不参与 sync,Surface 操作写入 mPendingTransaction
SYNC_STATE_WAITING_FOR_DRAW = 1   // 已加入 sync,正在等 App 绘制完成
SYNC_STATE_READY            = 2   // 已就绪(自身 OK,但子窗口可能还在绘制)

当 prepareSync() 被调用时,整个子树的 mSyncState 被设为 SYNC_STATE_READY。

4.2.2 SyncGroup(SyncGroup)

一个 SyncGroup 代表一组需要同步的窗口。它维护:

  • mSyncId:唯一标识
  • mRootMembers:参与的 WindowContainer 列表
  • mDependencies:依赖的其他 SyncGroup(处理重叠场景)
  • mOrphanTransaction:合并所有成员的事务
  • mListener:回调 onTransactionReady() 的监听器(即 Transition 自己)
4.2.3 Sync Transaction(每个容器的隔离事务)

WindowContainer 有两个事务字段:

mSyncTransaction mPendingTransaction
用途 sync 模式下的隔离事务 非 sync 模式下的默认事务
何时写入 mSyncState != SYNC_STATE_NONE 时,getSyncTransaction() 返回它 否则,getPendingTransaction() 返回它
何时合并 finishSync() 递归合并到 SyncGroup.mOrphanTransaction 最终合并到 DisplayContent 的 pending

关键代码 (WindowContainer.getSyncTransaction()):

java 复制代码
public Transaction getSyncTransaction() {
    if (mSyncTransactionCommitCallbackDepth > 0) {
        return mSyncTransaction;
    }
    if (mSyncState != SYNC_STATE_NONE) {
        // sync 模式下,所有 Surface 操作写入隔离事务
        return mSyncTransaction;
    }
    return getPendingTransaction();  // 非 sync 模式,走默认路径
}

4.3 同步流程

scss 复制代码
┌────────────────────────────────────────────────────────────────┐
│ 1. WMS 调用 prepareSync()                                      │
│    递归设置所有参与 WindowContainer 的 mSyncState = READY     │
│    → 此后 getSyncTransaction() 返回 mSyncTransaction          │
└────────────────────────────────────────────────────────────────┘
                          ↓
┌────────────────────────────────────────────────────────────────┐
│ 2. 所有 Surface 操作(show/hide/reparent/setPosition/setLayer)│
│    通过 getSyncTransaction() → 写入各容器的 mSyncTransaction   │
│    隔离!不会和 transition 之外的操作混在一起                   │
└────────────────────────────────────────────────────────────────┘
                          ↓
┌────────────────────────────────────────────────────────────────┐
│ 3. App 绘制完成 → WindowState.finishDrawing(postDrawT)         │
│    → onSyncFinishedDrawing() → mSyncState = READY              │
│    → DrawHandler 将事务合并到 mSyncTransaction                  │
└────────────────────────────────────────────────────────────────┘
                          ↓
┌────────────────────────────────────────────────────────────────┐
│ 4. WMS surface placement → onSurfacePlacement()               │
│    遍历所有活跃 SyncGroup,调用 tryFinish():                  │
│    - mReady == true?                                          │
│    - mDependencies 为空?                                       │
│    - 所有 mRootMembers 都 isSyncFinished()?                    │
│    → 全部满足 → finishNow()                                   │
└────────────────────────────────────────────────────────────────┘
                          ↓
────────────────────────────────────────────────────────────────┐
│ 5. finishNow()                                                  │
│    - 递归 finishSync() 合并所有 mSyncTransaction               │
│    - merged = mWm.mTransactionFactory.get()                     │
│    - merged.merge(mOrphanTransaction)                          │
│    - mListener.onTransactionReady(syncId, merged)              │
│    - → Transition.onTransactionReady(syncId, mergedTransaction)│
└────────────────────────────────────────────────────────────────┘

4.4 Transition 的回调

Transition 实现了 BLASTSyncEngine.TransactionReadyListener 接口。onTransactionReady() 是核心回调(Transition.java:2427):

java 复制代码
public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
    // 1. 提交可见 Activity 和 Wallpaper 的窗口可见性
    commitVisibleActivities(transaction);
    commitVisibleWallpapers(transaction);

    // 2. 计算动画目标(从参与者提升为可动画的祖先)
    mTargets = calculateTargets(mParticipants, mChanges, mType, mFlags);

    // 3. 计算 TransitionInfo(给 Shell 的数据包)
    TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);

    // 4. 分配轨道(决定哪些 transition 可以并行播放)
    mController.assignTrack(this, info);

    // 5. 进入播放状态
    mController.moveToPlaying(this);

    // 6. 通过 Binder 回调 Shell
    mController.getTransitionPlayer().onTransitionReady(
        mToken, info, transaction /* startT */, mFinishTransaction);
}

4.5 Sync Method 的选择

java 复制代码
// TransitionController.java:116-118
static final int SYNC_METHOD =
    SystemProperties.getBoolean("persist.wm.debug.shell_transit_blast", false)
        ? BLASTSyncEngine.METHOD_BLAST : BLASTSyncEngine.METHOD_NONE;
  • METHOD_NONE:App 自己内部绘制报告,不做 BLAST 级别的 buffer 同步
  • METHOD_BLAST:完整的 BLAST 同步,等待所有窗口的 buffer 到达

五、WMS → Shell 的两次通知详解

5.1 第一次:requestStartTransition

调用方 : TransitionController.requestStartTransition() (TransitionController.java:901) Binder 接口 : ITransitionPlayer.requestStartTransition(token, request)

参数 1: token

  • Transition.mToken,在 createTransition() 时由 new Binder() 创建
  • 整个 transition 的唯一标识

参数 2: request (TransitionRequestInfo)

java 复制代码
new TransitionRequestInfo(
    transition.mType,           // 过渡类型: TRANSIT_OPEN / CLOSE / TO_FRONT / TO_BACK / CHANGE 等
    triggerTask,                // 触发此 transition 的 Task(RunningTaskInfo,对应 mTriggerTask/getTriggerTask())
    pipChange,                  // 画中画变化信息(如有)
    remoteTransition,           // App 注册的远程动画(IRemoteTransition)
    displayChange,              // 显示变化(如旋转时的 bounds/rotation 变化)
    transition.getFlags()       // 过渡标志位
)

注:TransitionRequestInfo 没有 getSyncId() 字段;BLAST sync ID 由 Transition 自身持有,不通过 request 传给 Shell。

关键点 :此时 Shell 还没有收到任何窗口信息!request 只告诉 Shell "要发生什么类型的过渡",Shell 据此决定用哪个 TransitionHandler 来认领。

5.2 第二次:onTransitionReady

调用方 : Transition.onTransactionReady() 内部 (Transition.java:2767) Binder 接口 : ITransitionPlayer.onTransitionReady(token, info, startT, finishT)

参数 2: info (TransitionInfo)

  • type + flags
  • Roots: 过渡涉及的顶层容器(Task/DisplayArea)
  • Changes: 每个参与容器的详细变化信息(mode、startAbsBounds、parent、leash Surface、flags 等)

参数 3: startT (SurfaceControl.Transaction)

  • 即 BLAST 回调 onTransactionReady(syncId, transaction) 的参数
  • 包含所有参与窗口第一帧绘制完成后的 Surface 状态(show/hide、位置、大小、parent 关系等)
  • Shell 通过 setupAnimHierarchy() 在此基础上建立动画层级

参数 4: finishT (SurfaceControl.Transaction)

  • 由 mTransactionFactory.get() 新建
  • 记录动画结束后窗口需要恢复的最终状态

六、startT 和 finishT 详解

6.1 startT 的来源

直接来源 : Transition.onTransactionReady(int syncId, SurfaceControl.Transaction transaction) 的参数

深层来源 : BLASTSyncEngine.SyncGroup.finishNow():

java 复制代码
SurfaceControl.Transaction merged = mWm.mTransactionFactory.get();
if (mOrphanTransaction != null) {
    merged.merge(mOrphanTransaction);  // 合并所有参与者的 mSyncTransaction
}
// → 回调 → Transition.onTransactionReady(syncId, merged)

mOrphanTransaction 是 finishSync() 递归合并的结果:

java 复制代码
void finishSync(Transaction outMergedTransaction, ...) {
    outMergedTransaction.merge(mSyncTransaction);  // 合并自身
    for (child : children) {
        child.finishSync(outMergedTransaction, group, cancel);  // 递归子节点
    }
}

6.2 什么时候 apply

Transaction Apply 时机 谁执行
startT 不直接 apply。Shell 的 DefaultSurfaceAnimator 用它作为基础,每帧通过 SurfaceControl.Transaction 提交新的变换给 SurfaceFlinger Shell,逐帧
finishT 动画全部播放结束后,Shell 调用 onFinish() → WMS finishTransition() 中 apply WMS(最终阶段)

七、Transition 状态机

7.1 状态定义

scss 复制代码
STATE_PENDING      → 刚创建,等待开始
STATE_COLLECTING   → 正在收集参与者(collect() 调用中)
STATE_STARTED      → Shell 回告 startTransition() 后,BLAST 开始等待所有窗口绘制
STATE_PLAYING      → onTransactionReady() 后,Shell 已收到通知开始动画
STATE_FINISHED     → Shell 动画完成,WMS 已清理
STATE_ABORT        → transition 被中止(超时、SystemUI 崩溃等)

关键方法:

  • moveToCollecting() → 进入 STATE_COLLECTING
  • start() → 进入 STATE_STARTED(由 WindowOrganizerController.startTransition() 调用)
  • setAllReady() → 标记所有就绪条件已满足,触发 applyReady()(独立路径,如 playNow() / setAllReadyIfNeeded(),非 BLAST→onTransactionReady 主流程必经)
  • onTransactionReady() → 进入 STATE_PLAYING,通知 Shell
  • finishTransition() → 进入 STATE_FINISHED,清理 WM 状态
  • abort() → 进入 STATE_ABORT

状态流转完整路径:

css 复制代码
STATE_COLLECTING
    └── WMS: moveToCollecting() → 开始 collect() 参与者
    └── WMS: requestStartTransition() → Binder → Shell
    └── Shell: dispatchRequest() → Handler 认领
    └── Shell: mOrganizer.startTransition() → Binder → WMS  (← 同一方法内同步调用!)
    └── WMS: WindowOrganizerController.startTransition()
    └── WMS: transition.start() → STATE_STARTED
    └── WMS: applyTransaction(wct)
    └── BLAST: 等待所有参与窗口绘制完成
    └── BLAST: onSurfacePlacement() → tryFinish() → 全部就绪
    └── BLAST: finishNow() → onTransactionReady() 回调
    └── Transition: onTransactionReady() → calculateTargets() → moveToPlaying() → STATE_PLAYING
    └── WMS: ITransitionPlayer.onTransitionReady() → Binder → Shell
    └── Shell: playTransition() → 播放动画
    └── Shell: onFinish() → finishTransitionWithState() → Binder → WMS
    └── WMS: finishTransition() → STATE_FINISHED

7.2 STATE_STARTED 的特殊意义

STATE_STARTED 是理解整个 pipeline 的关键。isCollecting() 方法对以下两个状态都返回 true:

java 复制代码
boolean isCollecting() {
    return mState == STATE_COLLECTING || mState == STATE_STARTED;
}

这意味着 进入 STATE_STARTED 后,transition 仍然被认为是 "collecting" 状态 ,mCollectingTransition 仍然指向它。这是为了防止另一个 transition 抢占 mCollectingTransition 位置(除非满足"伪并行"条件,见第八节)。

STATE_STARTED 的另一个关键查询方法是 isPopulated():

java 复制代码
boolean isPopulated() {
    return mState >= STATE_STARTED && allReady();
}

只有当 isPopulated() 为 true 时(即 transition 已经进入 STATE_STARTED 且所有参与者已就绪),才允许新的 transition 与它"并行"------此时旧的 transition 才会被移入 mWaitingTransitions。

7.3 状态查询速查表

方法 返回 true 的条件 用途
isCollecting() STATE_COLLECTING 或 STATE_STARTED 判断是否还在收集阶段
isPopulated() state >= STATE_STARTED && allReady() 判断是否可以被新的 transition 替代
isPlaying() STATE_PLAYING 判断动画是否正在播放
allReady() 所有参与者绘制完成、BLAST 同步完成 内部判断是否满足 onTransactionReady 条件

八、TransitionController 的三个队列与"伪并行"收集

8.1 三个核心数据结构

TransitionController 维护三个 transition 容器,管理 transition 从创建到播放的全生命周期:

java 复制代码
// 正在收集参与者的 transition。所有 WM 变化都会归入这个 transition。
private Transition mCollectingTransition = null;

// 已 populated(收集完成)但仍在等 BLAST 同步绘制完成的 transition
final ArrayList<Transition> mWaitingTransitions = new ArrayList<>();

// 排队等待开始收集的 transition(连并行收集条件都不满足)
final ArrayList<QueuedTransition> mQueuedTransitions = new ArrayList<>();
队列 含义 transition 状态 触发条件
mCollectingTransition 当前正在收集参与者 STATE_COLLECTING 或 STATE_STARTED 无活跃 transition 时直接成为
mWaitingTransitions 已 populated,等 BLAST 同步 STATE_STARTED,isPopulated()=true 并行收集时旧的被移入
mQueuedTransitions 排队等待开始收集 还未创建 Transition 对象 不能并行收集时被排入

关键区别 :mWaitingTransitions 中的 transition 已经完成收集 (不再收集参与者),只是在等 BLAST 同步完成。它和 mQueuedTransitions 的本质区别是后者还没有开始收集。

8.2 新 transition 申请的决策流程

当业务申请新 transition 时,TransitionController 按以下优先级处理:

  1. 如果 queue 不为空 → 直接排队
  2. 如果没有 active sync 或没有 collecting transition → 直接 moveToCollecting()
  3. 如果正在 collecting 且满足并行条件 (canStartCollectingNow())→ 旧的移入 mWaitingTransitions,新的成为 mCollectingTransition
  4. 如果不满足并行条件 → 排入 mQueuedTransitions 等待

8.3 什么是"伪并行"收集

"并行收集"并不意味着两个 transition 同时在做 collect()。任何时候 mCollectingTransition 只有一个。

所谓的"并行",实际是 不同 transition 处于 pipeline 的不同阶段:

less 复制代码
旧的 transition:  已 populated,等 BLAST 同步(不再收集参与者)
新的 transition:  正在收集参与者

旧的已经收集完了,只是在等 BLAST 绘制完成,所以让出 mCollectingTransition 位置给新的。真正的并行点是让两个 transition 的 BLAST sync 可以并发等待绘制。

8.4 并行收集的前置条件

canStartCollectingNow() 检查三个条件:

  1. 当前 collecting 已 populated (isPopulated()=true,即 state >= STARTED 且所有参与者就绪)
  2. 新 transition 与当前 collecting 独立 (getCanBeIndependent() 返回 true)
  3. 新 transition 与所有 waiting transitions 都独立

任一条件不满足,新 transition 就会被排入 mQueuedTransitions。

8.5 getCanBeIndependent() 的独立性判断

场景 是否独立 原因
同 display 上的窗口变化 否 参与者可能重叠,动画需要同屏协调
不同 display 可能 物理隔离,动画互不影响
recents + 普通 app launch 可能 recents 是 PARALLEL_TYPE_RECENTS 特殊类型
TRANSIT_PARTIAL_ENTER_SPLIT_SCREEN 是 部分分屏被允许并行
默认情况 否 默认返回 false

关键结论 :同 display 上的窗口变化(如分屏→自由窗口+全屏)getCanBeIndependent() 返回 false,只能排队或合并到一个 transition 中 。这就是为什么 TRANSIT_SPLIT_TO_FREEFORM_AND_FULL 是一个 transition 统一处理,Shell 统一播放动画,而不是两个 transition 分别播放。

8.6 队列的接力机制

三个队列之间存在"接力"关系:

scss 复制代码
mQueuedTransitions → mCollectingTransition → mWaitingTransitions → mPlayingTransitions
       ↑                      ↑
       └─── tryStartCollectFromQueue() ─────┘

关键流转点:

  • moveToPlaying() :collecting transition 进入 playing 后,从 mWaitingTransitions 取出第一个升为 collecting
  • tryStartCollectFromQueue() :当 collecting 被移走后,从 mQueuedTransitions 取出第一个尝试启动,如果仍不能并行,旧的继续移 waiting
  • onAbort():abort 的 transition 被移除后,waiting 中的第一个替补成为 collecting

九、过渡类型

9.1 AOSP 基础过渡

类型 说明
TRANSIT_OPEN 打开(窗口/容器打开)
TRANSIT_CLOSE 关闭(窗口/容器关闭)
TRANSIT_TO_FRONT 移到前台
TRANSIT_TO_BACK 移到后台
TRANSIT_CHANGE 窗口属性变化(resize 等)
TRANSIT_SLEEP 息屏

注:现代 AOSP 已不再使用 TRANSIT_ACTIVITY_OPEN/CLOSE、TRANSIT_TASK_OPEN/CLOSE、TRANSIT_ROTATE 这些旧名,统一归并为 TRANSIT_OPEN/CLOSE/TO_FRONT/TO_BACK/CHANGE。

9.2 多窗口/分屏过渡

类型 说明
TRANSIT_SPLIT_SCREEN_PAIR_OPEN 分屏配对打开
TRANSIT_SPLIT_TO_FREEFORM_AND_FULL 分屏到自由窗口 + 全屏
TRANSIT_FREEFORM_TO_MAGIC 自由窗口到 Magic 窗口

十、开发调试指南

10.1 关键 log 标签

Tag 模块 用途
TransitionController WMS 过渡的创建、收集、播放、完成
Transition WMS Transition 状态机、onTransactionReady
BLASTSyncEngine WMS 同步组的状态检查、合并
Transitions Shell Shell 端过渡生命周期
WM_DEBUG_WINDOW_TRANSITIONS ProtoLog WMS 过渡详细调试
WM_SHELL_TRANSITIONS ProtoLog Shell 过渡详细调试

10.2 常见问题定位

问题 检查点
过渡未触发 TransitionController.requestTransitionIfNeeded() 是否被调用;mTransitionPlayers 是否为空(SystemUI 是否运行)
过渡卡在 collecting 检查 Shell 是否收到 requestStartTransition 并回告了 startTransition;检查 WindowOrganizerController.startTransition() 中 transition 是否仍处于 isCollecting() 状态(Shell 超时会导致 WM 状态不正确)
过渡卡在 started 不进入 playing 检查 BLAST 同步是否正常工作;检查 onSurfacePlacement() 是否被触发;检查 tryFinish() 中 isSyncFinished() 返回值
Shell 收到但无动画 检查 dispatchReady() 中的 track 分配;检查 Handler 链是否有人认领
动画闪烁/撕裂 检查 BLAST sync 是否生效(persist.wm.debug.shell_transit_blast);检查 mSyncTransaction 是否正确隔离
过渡超时 检查 onChangeTimeout() / onReadyTimeout();确认 App 是否正常完成 performTraversals()
新 transition 未立即启动 检查 mQueuedTransitions 是否非空(被排队);检查 canStartCollectingNow() 返回 false 的原因:isPopulated() 为 false 或 getCanBeIndependent() 返回 false
过渡卡在 waiting 不进入 playing 检查 mWaitingTransitions 中的 transition 是否 isPopulated()=true;检查 moveToPlaying() 是否被调用;确认 BLAST sync 是否已超时或异常
同 display 出现两个 transition 检查是否意外设置了 PARALLEL_TYPE_RECENTS;检查 getCanBeIndependent() 是否正确返回 false;同 display 上的窗口变化应合并到一个 transition 中

10.3 常用调试命令

bash 复制代码
# 开启 BLAST 同步
adb shell setprop persist.wm.debug.shell_transit_blast true

# 开启 Shell 过渡旋转
adb shell setprop persist.wm.debug.shell_transit_rotate true

# dump 当前过渡状态
adb shell dumpsys activity service com.android.server.wm.TransitionController

# 查看 WMS 过渡日志
adb logcat -s TransitionController Transition BLASTSyncEngine

# 查看 Shell 过渡日志
adb logcat -s Transitions DefaultTransitionHandler

十一、源码速查索引

功能 文件 关键方法/行号
创建 Transition TransitionController.java createTransition() / moveToCollecting() / requestStartTransition()
Transition 状态机 Transition.java mState 字段 / setAllReady() / onTransactionReady() / finishTransition()
BLAST 同步 BLASTSyncEngine.java startSyncSet() / addToSyncSet() / onSurfacePlacement() / tryFinish() / finishNow()
隔离事务 WindowContainer.java getSyncTransaction() / prepareSync() / finishSync() / onSyncFinishedDrawing()
Shell 接收通知 + 回告 Transitions.java requestStartTransition() / startTransition() / onTransitionReady() / playTransition() / onFinish()
动画播放 DefaultSurfaceAnimator.java buildSurfaceAnimation() / AnimationAdapter
Binder 回告 WindowOrganizerController.java startTransition() / finishTransition() / finishTransitionWithState()
Binder 接口定义 ITransitionPlayer.aidl requestStartTransition() / onTransitionReady()

附录:完整调用链示例(点击 Launcher 图标启动 Activity)

scss 复制代码
Launcher.onClick()
  → startActivity(intent)
    → ActivityTaskManagerService.startActivityAsUser()
      → ActivityStack.startActivityLocked()
        → ActivityRecord.setVisible(true)
          → mTransitionController.collectVisibleChange(this)          ← 收集可见性变化
          → mTransitionController.collectExistenceChange(this)        ← 收集存在性变化
            → mTransitionController.getCollectingTransition().collect(wc)

  → ActivityStack.resumeTopActivityInnerLocked()
    → mTransitionController.requestTransitionIfNeeded(TRANSIT_OPEN, ...)
      → createTransition(type, flags)
        → mTransitionController.moveToCollecting(transition)          ← 进入收集状态
        → requestStartTransition(transition, startTask, ...)
          → mPlayer.requestStartTransition(token, request)            ← 通知 Shell (Binder)

Shell.Transitions.requestStartTransition()     ← 以下三步在同一方法内同步完成
  → dispatchRequestWithTracing() → Handler 链认领
  → mDisplayController.onDisplayChangeRequested() (如旋转)
  → mOrganizer.startTransition(token, wct)       ← 同步回告 WMS (Binder)

WindowOrganizerController.startTransition()
  → 通过 token 找到 Transition
  → transition.start()                           ← STATE_COLLECTING → STATE_STARTED
  → applyTransaction(wct)                        ← 应用 Shell 返回的 WCT

[WMS 等待所有窗口绘制完成]
  → ActivityRecord.performTraversals() → 新 Activity 绘制
    → WindowState.finishDrawing(postDrawT)
      → onSyncFinishedDrawing()
  → BLASTSyncEngine.onSurfacePlacement()
    → tryFinish() → 所有成员就绪
    → finishNow()
      → finishSync() 递归合并所有 mSyncTransaction
      → merged.merge(mOrphanTransaction)
      → mListener.onTransactionReady(syncId, merged)

Transition.onTransactionReady()
  → commitVisibleActivities(transaction)
  → mTargets = calculateTargets(...)
  → TransitionInfo info = calculateTransitionInfo(...)
  → mController.assignTrack(this, info)
  → mController.moveToPlaying(this)
  → mController.getTransitionPlayer().onTransitionReady(token, info, startT, finishT)  ← 通知 Shell

Shell.Transitions.onTransitionReady()
  → dispatchReady() → playTransition()
    → DefaultTransitionHandler.startAnimation(token, info, startT, finishT, onFinish)
    → DefaultSurfaceAnimator.buildSurfaceAnimation()
    → ValueAnimator 逐帧提交 Surface 变换

[动画播放完成]
  → Shell.onFinish(token, wct)
    → mOrganizer.finishTransitionWithState(token, wct, null, state)  ← 回告 WMS

WindowOrganizerController.finishTransitionWithState()
  → TransitionController.finishTransition(chain)
  → Transition.finishTransition(chain)
    → 从 mPlayingTransitions 移除
    → 提交不可见容器、清理 starting window、通知监听器

相关推荐
哈基米南北1 小时前
Android 启动窗口(Starting Window)技术文档
源码
哈基米南北1 小时前
PhoneWindow、DecorView 和 ViewRootImpl 的关系与工作原理
源码
哈基米南北5 天前
ViewRootImpl 事件分发责任链分析
源码
Amos_Web6 天前
Rspack 源码解析(十二):JavaScript Chunk 是如何被渲染出来的
前端·rust·源码
淡海水6 天前
13-04-面试-源码级深度追问链
数据结构·unity·面试·c#·游戏引擎·源码·il2cpp
斑鸠喳喳9 天前
可重入锁 ReentrantLock
java·源码
大胡子大叔11 天前
YOLO26(极速目标检测) + SAM3(精准掩码生成)附源码
目标检测·源码·流水线·分割·yolo26·sam3
源码宝13 天前
一套云PACS商用授权源码
源码·影像系统·云影像
Java后端的Ai之路13 天前
【AI大模型Harness】-Codex源码深度解读
人工智能·源码·ai大模型·codex·harness