14、WMS 中的动画机制:窗口切换动画、启动动画的原理

动画这东西,说白了就是让用户觉得「嗯,系统没卡,还在干活」。在 Android 里,窗口动画是 WMS 和 SurfaceFlinger 联手搞的一出大戏。我早年刚接触 WMS 时,以为动画就是 View 动画那一套,后来才发现——窗口动画完全是另一套体系,它操作的是 Surface,而不是 View。

14.1 窗口动画的本质

窗口动画,本质上是对 Surface 的移动、缩放、旋转、透明度变化。WMS 不直接画动画,它只负责「安排」动画,真正渲染交给 SurfaceFlinger。

你想想看,一个 Activity 启动时,它的窗口还没完全准备好,这时候如果直接显示,用户会看到白屏或者闪一下。所以 WMS 会先让窗口「以透明状态出现」,然后通过动画让它「淡入」或「缩放进入」。这就是启动动画的由来。

核心要点:WMS 中的动画是 WindowState 级别的,每个窗口可以有自己的动画策略。

14.2 动画的参与者

参与窗口动画的主要角色有这么几个:

  • WMS:负责决定「要不要播动画」、「播什么动画」、「什么时候结束」。
  • DisplayContent:管理屏幕上的窗口列表,动画期间会调整窗口的 Z-order。
  • WindowStateAnimator:每个窗口都有一个,负责跟踪该窗口的动画状态。
  • SurfaceAnimator:真正驱动动画的类,它持有 AnimationAdapter
  • SurfaceFlinger:接收 WMS 下发的动画参数,完成实际渲染。

我记得有一次排查一个「窗口切换卡顿」的问题,发现是 WindowStateAnimator 的状态没及时更新,导致动画结束后 Surface 还保留着透明值。嗯,这种坑很隐蔽。

14.3 窗口切换动画的流程

窗口切换,比如从 A Activity 跳转到 B Activity,WMS 会同时处理两个窗口的动画:

  1. 旧窗口退出:A 窗口执行退出动画(比如缩小 + 淡出)。
  2. 新窗口进入:B 窗口执行进入动画(比如放大 + 淡入)。
  3. 同步:两个动画在时间上重叠,形成「切换」的视觉效果。

具体代码路径大致是:

// 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。

避坑指南:我曾经遇到过一个问题——自定义动画时,transit 传错了值,导致窗口启动时播了退出动画,看起来就像窗口「闪退」了一样。后来我养成了习惯:每次调试动画,先打印 transit 值确认。

14.4 启动动画的原理

启动动画,说白了就是「窗口从无到有」的过程。WMS 在收到 START_ACTIVITY 的消息后,会做以下事情:

  • 创建新的 ActivityRecordWindowState
  • 将新窗口添加到 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 会立即终止动画,并执行清理逻辑。

注意:如果在动画期间窗口的 Surface 被提前释放,会导致 SurfaceFlinger 报错 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 动画机制的核心结构:

WMS 动画机制核心结构 WMS DisplayContent WindowState (A) WindowState (B) WindowStateAnimator WindowStateAnimator SurfaceAnimator WMS 调度层 窗口管理 窗口实例 动画状态 动画驱动

14.10 总结

WMS 的动画机制,说白了就是一套「状态机 + 帧驱动」的体系。WMS 负责决策,SurfaceAnimator 负责驱动,SurfaceFlinger 负责渲染。窗口切换动画和启动动画的核心区别在于 transit 参数和动画资源的选取。

我个人觉得,理解动画机制的关键在于搞清楚「谁在什么时候做什么」。WMS 不画动画,它只是动画的导演。真正表演的是 SurfaceFlinger。你只要记住这一点,再看代码就不会迷路。

最后一个小建议:如果你要自定义窗口动画,建议先看看 frameworks/base/core/res/res/anim/ 下的默认动画文件,然后通过 WindowAnimationSpec 来定制。不要直接修改 WMS 的动画逻辑,那里面坑太多。

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