20、Activity 转场动画:AMS如何协调WMS实现启动/退出动画

说实话,转场动画这块,我早年刚接触Framework时觉得挺简单的——不就是开个窗口、放个动画嘛。直到有一次我在优化一个第三方ROM的启动速度,发现动画卡顿的根因竟然是AMS和WMS的同步时序出了问题。嗯,从那以后,我对这套机制就再也不敢小瞧了。

今天我们就来聊聊,AMS到底是怎么拉着WMS一起,把Activity的启动和退出动画给“演”出来的

一、动画的“导演”与“舞台”

先理清两个角色:

  • AMS(ActivityManagerService):负责调度Activity的生命周期,决定什么时候该播动画、播什么动画。
  • WMS(WindowManagerService):负责窗口的显示、层级、动画执行。说白了,动画的每一帧都是WMS在背后驱动的。

AMS是导演,WMS是舞台执行。导演喊“Action”,舞台才开始动。

核心原则:AMS只负责“触发”和“回调”,动画的渲染、插值、同步全交给WMS。两者通过Binder通信,配合一个叫 WindowContainerTransaction 的东西来传递动画参数。

二、启动动画的完整链路

我们以Activity A启动Activity B为例,走一遍流程。

1. AMS发起“启动请求”

当你在代码里调 startActivity(),最终会走到AMS的 startActivityLocked()。AMS会先判断目标Activity是否需要新建进程、是否需要冷启动。然后,它会调用:

// AMS 中关键调用
mWindowManager.startActivityTransition(displayId, sourceToken, targetToken, options);

这里的 options 就是你在 overridePendingTransition() 里传的动画资源ID。AMS把这些参数打包,扔给WMS。

2. WMS准备“舞台”

WMS收到请求后,会做两件事:

  • 为B Activity创建新的 WindowState,并设置初始透明度为0(如果是淡入动画)。
  • 记录A Activity的当前窗口快照,用于退场动画。

这里有个细节:WMS不会立刻播动画。它会等AMS把B Activity的窗口真正“附加”到窗口树之后,才触发动画。为什么?因为动画需要窗口已经存在,否则你播给谁看?

3. AMS通知“窗口可见”

当B Activity的 onResume() 执行完毕,AMS会调用:

// AMS 通知 WMS 窗口已就绪
mWindowManager.setWindowVisibility(token, true);

这一步是关键。WMS收到后,开始执行预先配置好的动画。

4. WMS执行动画

WMS内部有一个 AnimationHandler,它会根据动画类型(平移、缩放、淡入淡出)计算每一帧的变换矩阵。然后通过 SurfaceFlinger 合成到屏幕上。

我个人的习惯是,把动画分为两类:

  • 系统动画:比如默认的从左向右滑入。这类动画由WMS内置的 AnimationSpec 定义。
  • 自定义动画:通过 overridePendingTransition() 传入。WMS会解析成 AnimationSet,然后逐帧执行。

避坑指南:我曾经遇到一个bug,自定义动画在低端机上掉帧严重。后来发现是动画的插值器用了 AccelerateDecelerateInterpolator,在CPU负载高时计算量太大。换成 LinearInterpolator 后流畅多了。所以,动画别太花哨,性能要紧。

三、退出动画的“反向”逻辑

退出动画和启动动画基本对称,但有一个关键区别:退出时,AMS会先通知WMS播动画,再销毁Activity

流程如下:

  1. 用户按Back键,AMS收到 finish() 请求。
  2. AMS调用 mWindowManager.startExitAnimation(token, options)
  3. WMS开始播放A Activity的退场动画(比如向右滑出)。
  4. 动画结束后,WMS回调AMS:onAnimationFinished()
  5. AMS这才真正执行 destroyActivity()

为什么要等动画结束再销毁?你想想看,如果动画还没播完就把窗口销毁了,用户会看到窗口突然消失,体验极差。所以AMS必须等WMS的“动画完成”回调。

四、AMS与WMS的同步机制

这里有个核心问题:AMS和WMS是跨进程的,怎么保证动画时序不乱?

答案是:通过 WindowContainerTransactionTransaction 的序列化

每次AMS发起动画请求,都会生成一个 WindowContainerTransaction 对象,里面包含了:

  • 目标窗口的token
  • 动画类型(启动/退出)
  • 动画参数(时长、插值器、起始位置)
  • 同步标记(是否等待动画完成)

WMS收到后,会把这个事务加入到一个队列中,按顺序执行。这样就不会出现“动画还没播完,窗口就被销毁”的乱序问题。

注意:如果AMS在动画执行期间又发了一个新的启动请求(比如用户疯狂点击),WMS会如何处理?答案是:WMS会取消当前动画,直接跳转到最终状态,然后执行新动画。这就是为什么快速点击时,动画会“闪一下”的原因。

五、一张图看懂整体流程

下面我用SVG画了一张流程图,把AMS和WMS的交互过程展示出来。你可以对照着看,会更清晰。

Activity 转场动画:AMS 与 WMS 交互流程 AMS WMS 1. startActivity() 创建WindowContainerTransaction 2. 准备窗口快照 配置动画参数 3. onResume() 完成 setWindowVisibility(true) 4. 执行动画 逐帧渲染 5. onAnimationFinished() 销毁旧窗口

六、常见问题与优化建议

问题 原因 解决方案
启动动画卡顿 AMS在动画期间做了耗时操作(如ContentProvider初始化) 将耗时操作延迟到动画结束后执行,或使用 reportFullyDrawn()
退出动画闪白屏 窗口销毁时机早于动画结束 检查 onAnimationFinished 回调是否被正确调用
自定义动画不生效 overridePendingTransition 调用时机不对 必须在 startActivity()finish() 之后立即调用

个人经验:我在做系统级优化时,发现一个提升动画流畅度的技巧——减少AMS和WMS之间的Binder调用次数。比如,把多个动画参数合并到一个 WindowContainerTransaction 中,而不是分多次发送。这样能减少IPC开销,动画帧率能提升5-10%。

七、总结

Activity转场动画,说白了就是AMS当导演、WMS当舞台的一场戏。AMS负责喊开始和结束,WMS负责把每一帧演好。两者通过Binder和事务队列来同步,确保动画不跳帧、不闪白。

嗯,这里要注意的是:不要试图在动画执行期间干预窗口状态。我曾经犯过一个错,在 onPause() 里改了窗口的透明度,结果动画和手动修改冲突,导致界面闪烁。后来我学乖了,动画期间让WMS全权负责,AMS只做“旁观者”。

好了,这一章就到这里。希望下次你看到Activity滑入滑出时,能想起AMS和WMS在背后默默配合的样子。


公众号:蓝海资料掘金营,微信deep3321