14、WMS 中的动画机制:窗口切换动画、启动动画的原理
动画这东西,说白了就是让用户觉得「嗯,系统没卡,还在干活」。在 Android 里,窗口动画是 WMS 和 SurfaceFlinger 联手搞的一出大戏。我早年刚接触 WMS 时,以为动画就是 View 动画那一套,后来才发现——窗口动画完全是另一套体系,它操作的是 Surface,而不是 View。
14.1 窗口动画的本质
窗口动画,本质上是对 Surface 的移动、缩放、旋转、透明度变化。WMS 不直接画动画,它只负责「安排」动画,真正渲染交给 SurfaceFlinger。
你想想看,一个 Activity 启动时,它的窗口还没完全准备好,这时候如果直接显示,用户会看到白屏或者闪一下。所以 WMS 会先让窗口「以透明状态出现」,然后通过动画让它「淡入」或「缩放进入」。这就是启动动画的由来。
WindowState 级别的,每个窗口可以有自己的动画策略。
14.2 动画的参与者
参与窗口动画的主要角色有这么几个:
- WMS:负责决定「要不要播动画」、「播什么动画」、「什么时候结束」。
- DisplayContent:管理屏幕上的窗口列表,动画期间会调整窗口的 Z-order。
- WindowStateAnimator:每个窗口都有一个,负责跟踪该窗口的动画状态。
- SurfaceAnimator:真正驱动动画的类,它持有
AnimationAdapter。 - SurfaceFlinger:接收 WMS 下发的动画参数,完成实际渲染。
我记得有一次排查一个「窗口切换卡顿」的问题,发现是 WindowStateAnimator 的状态没及时更新,导致动画结束后 Surface 还保留着透明值。嗯,这种坑很隐蔽。
14.3 窗口切换动画的流程
窗口切换,比如从 A Activity 跳转到 B Activity,WMS 会同时处理两个窗口的动画:
- 旧窗口退出:A 窗口执行退出动画(比如缩小 + 淡出)。
- 新窗口进入:B 窗口执行进入动画(比如放大 + 淡入)。
- 同步:两个动画在时间上重叠,形成「切换」的视觉效果。
具体代码路径大致是:
// WMS 中处理窗口切换的核心方法
void applyAnimationLocked(WindowState win, int transit, boolean isEntrance) {
// transit 表示动画类型:TRANSIT_ACTIVITY_OPEN、TRANSIT_ACTIVITY_CLOSE 等
AnimationAdapter adapter = loadAnimation(win, transit, isEntrance);
if (adapter != null) {
win.mSurfaceAnimator.startAnimation(adapter);
}
}
这里有个细节:transit 参数决定了动画的类型。比如 TRANSIT_ACTIVITY_OPEN 对应启动动画,TRANSIT_ACTIVITY_CLOSE 对应退出动画。WMS 会根据当前窗口栈的状态自动选择合适的 transit。
14.4 启动动画的原理
启动动画,说白了就是「窗口从无到有」的过程。WMS 在收到 START_ACTIVITY 的消息后,会做以下事情:
- 创建新的
ActivityRecord和WindowState。 - 将新窗口添加到
DisplayContent的窗口列表中。 - 设置窗口的初始状态为
DRAW_PENDING,此时窗口不可见。 - 当窗口内容绘制完成后,触发
performShowLocked,开始播放启动动画。
启动动画的默认效果是「缩放 + 淡入」。这个动画定义在 frameworks/base/core/res/res/anim/ 目录下,比如 activity_open_enter.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha android:fromAlpha="0.0" android:toAlpha="1.0"
android:duration="300" />
<scale android:fromXScale="0.8" android:toXScale="1.0"
android:fromYScale="0.8" android:toYScale="1.0"
android:pivotX="50%" android:pivotY="50%"
android:duration="300" />
</set>
嗯,这里要注意:这些动画资源是给 WindowAnimationSpec 用的,它会被解析成 AnimationAdapter,然后交给 SurfaceAnimator 驱动。
14.5 动画的驱动方式
WMS 中的动画有两种驱动方式:
| 驱动方式 | 说明 | 适用场景 |
|---|---|---|
| 基于 Choreographer | 每帧回调更新动画参数 | 标准窗口动画 |
| 基于 SurfaceFlinger 的 vsync | 直接下发动画参数到 SF | 高帧率动画、转场动画 |
我个人习惯用第一种方式调试,因为它可以在 doAnimationFrame 中打日志,看到每一帧的动画进度。第二种方式性能更好,但调试起来比较麻烦,因为动画逻辑跑在 SurfaceFlinger 进程里。
14.6 动画与窗口状态的关系
动画期间,窗口的状态会经历一系列变化:
HAS_DRAWN:窗口内容已绘制完成。READY_TO_SHOW:窗口可以显示了。ANIMATING:动画正在播放。ANIMATION_DONE:动画结束,窗口完全可见。
WMS 会在每一帧检查这些状态,决定是否继续动画。如果窗口在动画期间被销毁(比如用户按了返回键),WMS 会立即终止动画,并执行清理逻辑。
BufferQueue has been abandoned。我曾经在快速切换 Activity 时遇到过这个问题,解决方案是在动画结束前,确保 Surface 的引用计数不为 0。
14.7 动画的同步与协调
多个窗口同时播放动画时,WMS 需要协调它们的进度。比如「A 退出」和「B 进入」两个动画,WMS 会确保它们在同一帧开始,并且持续时间一致,这样用户看起来才流畅。
协调的核心是 AppTransition 类。它维护了一个动画队列,WMS 会调用 goodToGo() 检查是否所有窗口都准备好了。如果某个窗口还没绘制完成,动画会延迟开始。
// AppTransition 中的关键方法
boolean goodToGo() {
// 检查所有参与动画的窗口是否都 ready
for (WindowState win : mAnimatingWindows) {
if (!win.isReadyToShow()) {
return false;
}
}
return true;
}
嗯,这里有个小技巧:如果你发现窗口切换动画有「延迟开始」的现象,多半是某个窗口的 isReadyToShow() 返回了 false。可以看看是不是 relayoutWindow 没及时调用。
14.8 动画的结束与清理
动画结束后,WMS 会做几件事:
- 通知
WindowStateAnimator更新状态为ANIMATION_DONE。 - 如果动画是「退出」类型,会隐藏或移除窗口的 Surface。
- 触发
onAnimationFinished回调,让上层(比如 ActivityManager)知道动画结束了。
清理工作很重要。如果忘记释放动画资源,会导致内存泄漏。我见过一个 case:某个第三方 ROM 在动画结束后没有正确清理 AnimationAdapter,导致每次切换窗口都多占一点内存,最后系统 OOM 重启。
14.9 知识体系图
下面这张图展示了 WMS 动画机制的核心结构:
14.10 总结
WMS 的动画机制,说白了就是一套「状态机 + 帧驱动」的体系。WMS 负责决策,SurfaceAnimator 负责驱动,SurfaceFlinger 负责渲染。窗口切换动画和启动动画的核心区别在于 transit 参数和动画资源的选取。
我个人觉得,理解动画机制的关键在于搞清楚「谁在什么时候做什么」。WMS 不画动画,它只是动画的导演。真正表演的是 SurfaceFlinger。你只要记住这一点,再看代码就不会迷路。
frameworks/base/core/res/res/anim/ 下的默认动画文件,然后通过 WindowAnimationSpec 来定制。不要直接修改 WMS 的动画逻辑,那里面坑太多。
公众号:蓝海资料掘金营,微信deep3321