Android 14 ShellTransitions动画参与者收集机制深度解析
在Android 14的窗口管理系统中,ShellTransitions框架扮演着关键角色,它负责协调和管理应用切换、Activity启动等场景下的过渡动画。理解动画参与者的收集机制,对于开发者优化应用启动性能、定制系统动画效果具有重要意义。本文将深入剖析从Activity启动到App切换过程中,系统如何识别、筛选和组织动画参与者,揭示WindowContainer同步与状态管理的核心逻辑。
1. ShellTransitions框架概览
ShellTransitions是Android 14引入的全新窗口过渡动画框架,它重构了传统的动画处理流程,采用更加模块化和可扩展的设计。与早期版本相比,最大的变化在于将动画逻辑从WindowManagerService中解耦,交由独立的Shell组件处理。
框架的核心工作流程可以分为三个阶段:
- 收集阶段(COLLECTING):识别并注册所有参与动画的窗口容器
- 准备阶段(PREPARING):等待所有参与者完成绘制等准备工作
- 执行阶段(STARTED):实际执行动画效果
在这个过程中,三个关键数据结构协同工作:
- SyncGroup:管理需要同步的窗口容器集合
- ChangeInfo:记录窗口容器在动画前后的状态变化
- Transition:封装完整的动画流程和参与者信息
// 伪代码展示Transition基本结构 class Transition { int mState; // COLLECTING/PREPARING/STARTED ArraySet<WindowContainer> mParticipants; Map<WindowContainer, ChangeInfo> mChanges; SyncGroup mSyncGroup; }2. 动画参与者收集触发时机
动画参与者的收集并非随意进行,而是严格遵循特定的触发条件和执行路径。系统通过TransitionController来协调整个收集过程。
2.1 主要触发场景
在实际使用中,以下几种用户操作会启动动画参与者收集:
- 应用启动:点击Launcher图标启动新应用
- 应用切换:通过最近任务键或手势导航切换应用
- 返回桌面:按Home键返回Launcher
- 分屏操作:进入或退出分屏模式
- 窗口模式变更:全屏/自由窗口切换
2.2 核心调用链路
无论哪种场景,收集过程的代码执行路径都遵循相似的模式:
ActivityStarter.startActivityUnchecked() → TransitionController.createAndStartCollecting() → TransitionController.collect() → Transition.collect()这个调用链的关键在于:
createAndStartCollecting()初始化Transition对象并设置状态为COLLECTINGcollect()方法实际执行参与者注册逻辑
状态检查机制:Transition.collect()首先会验证当前状态,只有处于COLLECTING或STARTED状态的Transition才能接收新的参与者。这种设计确保了动画生命周期的严谨性。
// 状态检查逻辑示例 if (mState < STATE_COLLECTING) { throw new IllegalStateException("Transition not collecting"); } if (mState > STATE_STARTED) { return; // 太晚不再收集 }3. 参与者筛选与同步机制
不是所有WindowContainer都会成为动画参与者。系统通过一套精细的筛选逻辑确定哪些容器需要参与同步和动画。
3.1 needSync决策树
决定是否将WindowContainer加入SyncGroup的关键条件是needSync:
needSync = !(windowContainer is WallpaperWindowToken) && (displayContent已包含在参与者中 || !windowContainer被Recents遮挡)这个条件确保了:
- 壁纸窗口有特殊处理
- 只有可见或即将可见的窗口才会参与同步
- 避免收集被临时遮挡的窗口
3.2 BLAST同步引擎工作流程
当needSync为true时,系统会通过BLASTSyncEngine管理窗口容器的同步状态:
- 添加到SyncSet:将WindowContainer与特定Transition关联
- 设置同步状态:
- 非WindowState容器(如Task、ActivityRecord)直接标记为READY
- WindowState需要等待绘制完成才会转为READY
// SyncGroup添加参与者的核心逻辑 void addToSync(WindowContainer wc) { if (wc.getSyncGroup() == null) { mRootMembers.add(wc); wc.setSyncGroup(this); } wc.prepareSync(); }3.3 同步状态机
WindowContainer的同步状态遵循严格的转换规则:
| 状态 | 含义 | 转换条件 |
|---|---|---|
| SYNC_STATE_NONE | 未参与同步 | 初始状态 |
| SYNC_STATE_WAITING_FOR_DRAW | 等待绘制完成 | WindowState被添加时 |
| SYNC_STATE_READY | 准备就绪 | 绘制完成或非WindowState容器 |
对于开发者而言,理解这个状态机有助于诊断动画卡顿问题。例如,如果某个WindowState长时间停留在WAITING_FOR_DRAW状态,通常意味着该窗口的绘制出现了问题。
4. 参与者信息记录与变更追踪
除了同步管理外,Transition框架还详细记录每个参与者的状态变化,这是实现高质量动画的基础。
4.1 ChangeInfo的作用机制
ChangeInfo对象保存了WindowContainer在动画开始前的关键状态,用于后续计算动画参数。其工作流程如下:
- 收集阶段:创建ChangeInfo快照
- 变更阶段:窗口实际发生变化
- 准备阶段:对比前后状态计算动画参数
// ChangeInfo创建示例 ChangeInfo info = new ChangeInfo(windowContainer); mChanges.put(windowContainer, info); // 后续比较变化 void onTransactionReady() { ChangeInfo before = mChanges.get(windowContainer); WindowContainer current = getCurrentState(); // 比较before和current计算动画 }4.2 多层级参与者收集
系统不仅收集直接操作的WindowContainer,还会向上遍历其层级结构,收集可能需要参与动画的父容器:
- Activity启动:收集新ActivityRecord及其Task
- App切换:收集前后应用的Task及其TaskDisplayArea
- 显示变更:可能涉及DisplayContent级别的调整
这种设计确保了复杂场景下(如分屏、自由窗口)动画的完整性。
4.3 三种参与者集合对比
理解不同集合的包含关系和用途对调试动画问题很有帮助:
| 集合 | 包含内容 | 用途 |
|---|---|---|
| mParticipants | 所有直接操作的WindowContainer | 动画目标识别 |
| mRootMembers | 需要通过BLAST同步的容器 | 绘制同步管理 |
| mChanges | 直接参与者+可能受影响的父容器 | 状态变化追踪 |
典型情况下:mRootMembers ⊆ mParticipants ⊆ mChanges.keySet()
5. 实际场景下的参与者收集分析
通过具体用例可以更直观地理解收集机制的实际运作。
5.1 应用启动场景
从Launcher启动新应用的流程最为典型:
- 初始状态:只有Launcher Task存在
- 创建阶段:新建ActivityRecord和Task
- 收集过程:
- 新ActivityRecord被加入mParticipants
- 由于是新创建,其父Task尚未关联,不触发needSync
- 仅为ActivityRecord创建ChangeInfo
// 伪代码展示启动场景 ActivityRecord newActivity = createActivity(); transition.collect(newActivity); // newActivity.getParent() == null → 仅基础收集5.2 应用切换场景
从应用A切换到应用B的流程更为复杂:
- 收集应用A的Task:
- 加入mParticipants和mRootMembers
- 向上收集TaskDisplayArea和DisplayContent的ChangeInfo
- 收集应用B的Task:
- 同样记录多层级信息
- 结果:
- mParticipants包含两个Task
- mChanges包含从Task到DisplayContent的完整层级
// 伪代码展示应用切换 Task taskA = getCurrentTask(); Task taskB = getNextTask(); transition.collect(taskA); // 收集层级结构 transition.collect(taskB); // 收集层级结构5.3 分屏操作场景
分屏模式下的参与者收集展示了框架处理复杂场景的能力:
- 进入分屏:
- 收集主窗口和分屏窗口的Task
- 收集共享的TaskDisplayArea
- 特殊处理分割线控件
- 调整分屏比例:
- 需要重新收集受影响窗口
- 计算新旧尺寸的ChangeInfo
这种场景下,mChanges通常会包含更多层级的WindowContainer,因为分屏操作会影响整个显示区域的布局。
6. 性能优化与调试技巧
理解参与者收集机制后,开发者可以更好地优化应用动画性能。
6.1 常见性能瓶颈
根据收集流程,动画延迟通常源于:
- 绘制延迟:WindowState长时间处于WAITING_FOR_DRAW
- 优化:减少主线程工作,避免过度绘制
- 同步等待:多个WindowContainer就绪时间不一致
- 优化:简化视图层次,提前初始化资源
- 过度收集:不必要的大型容器被纳入动画
- 优化:合理设置窗口属性,避免全局重排
6.2 调试工具与方法
Android提供多种工具观察参与者收集:
# 查看当前Transition状态 adb shell dumpsys window transitions # 跟踪特定WindowContainer adb shell dumpsys window windows | grep -A10 "WindowContainer"关键检查点:
- 参与者的同步状态是否按预期变化
- ChangeInfo记录的前后状态差异
- 收集过程中是否有异常跳过或重复
6.3 最佳实践建议
- 启动优化:减少Application和首屏Activity的初始化工作
- 过渡提示:使用setActivityTransitionInfo提供准确的共享元素信息
- 窗口属性:正确设置FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS等标志
- 避免突变:在动画期间保持视图结构稳定
在实际项目中,我曾遇到一个案例:应用在启动时因加载大量数据导致动画卡顿。通过分析发现,主窗口的绘制准备时间过长,处于WAITING_FOR_DRAW状态超过300ms。解决方案是将数据加载移到后台线程,并优先完成必要的视图初始化,最终将动画延迟降低到可接受范围。