窗口动画机制:WindowAnimationSpec、AnimationAdapter、窗口启动/退出动画的执行流程

各位同学,今天我们来聊聊窗口动画。说实话,窗口动画这块内容,在 Android 系统里属于那种「看着简单,挖下去全是坑」的模块。我当年第一次接触 WindowManagerService 的动画逻辑时,心里想的是:「不就是个补间动画吗?」结果被现实狠狠教育了一顿。

窗口动画不是简单的 View 动画。它涉及跨进程通信、Surface 管理、同步机制,还有一堆你平时根本不会注意到的细节。嗯,咱们今天就把这块硬骨头啃下来。

窗口动画的核心角色

在 WMS 里,窗口动画主要涉及三个关键角色:WindowAnimationSpecAnimationAdapter,以及执行动画的 AppTransition 机制。我习惯把它们比作「剧本、导演和制片人」的关系。

  • WindowAnimationSpec:定义动画的「剧本」。它描述了动画的持续时间、插值器、变换矩阵等参数。
  • AnimationAdapter:负责「导演」工作。它把 WindowAnimationSpec 翻译成 SurfaceFlinger 能理解的指令,并控制动画的播放、暂停、取消。
  • AppTransition:整个动画流程的「制片人」。它协调窗口的启动/退出时机,确保动画与窗口状态切换同步。

核心要点:窗口动画不是在应用进程里跑的,而是在 SystemServer 进程的 WMS 中,通过 SurfaceFlinger 合成。这意味着动画的每一帧都要跨进程传递,性能开销比你想象的大得多。

WindowAnimationSpec 深度解析

先看 WindowAnimationSpec 的源码结构。我挑重点说,不贴完整代码,咱们看关键部分:

// frameworks/base/services/core/java/com/android/server/wm/WindowAnimationSpec.java
class WindowAnimationSpec {
    Animation mAnimation;           // 补间动画对象
    long mDuration;                 // 动画时长
    Interpolator mInterpolator;     // 插值器
    Rect mStackBounds;              // 栈边界(分屏场景下有用)
    boolean mIsAppAnimation;        // 是否是应用动画
}

这里有个细节,mAnimation 是 Android 框架层的 Animation 对象,但它最终会被转换成 SurfaceFlinger 能理解的 Transformation。说白了,WMS 这边定义的是「高层的动画意图」,底层渲染由 SurfaceFlinger 完成。

我在项目中遇到过一个问题:某个窗口的退出动画总是卡顿。查了半天,发现是 mDuration 设置得太短,只有 100ms。SurfaceFlinger 还没来得及合成完,动画就结束了,导致最后一帧被跳过。嗯,这里建议动画时长不要低于 200ms,否则容易出视觉瑕疵。

AnimationAdapter 的工作原理

AnimationAdapter 是个接口,它的实现类负责把动画参数传递给 SurfaceFlinger。咱们看看它的核心方法:

// frameworks/base/services/core/java/com/android/server/wm/AnimationAdapter.java
interface AnimationAdapter {
    void startAnimation(SurfaceControl.Transaction t, SurfaceControl parent, 
                        SurfaceControl child, boolean isVisible);
    void onAnimationEnd(SurfaceControl.Transaction t);
    long getDurationHint();
    long getStatusBarAnimationsDuration();
    void dump(PrintWriter pw, String prefix);
}

注意 startAnimation 方法里的 SurfaceControl.Transaction。这个 Transaction 是 WMS 和 SurfaceFlinger 通信的桥梁。每一帧动画的变化,都通过 Transaction 提交给 SurfaceFlinger 去合成。

你想想看,如果动画每帧都创建一个新的 Transaction,那性能得多差?所以 Android 做了优化:动画开始前,先创建一个 Transaction,然后在动画循环中不断复用这个 Transaction。这个优化我记得是在 Android 8.0 引入的,之前版本确实有性能问题。

避坑指南:我曾经在调试一个第三方 ROM 时发现,窗口动画在低端机上特别卡。后来定位到是 AnimationAdapter 的 getDurationHint() 返回了 0,导致 SurfaceFlinger 认为动画不需要执行,直接跳过了。所以,永远不要返回 0,哪怕你的动画是瞬时的,也要给个最小值。

窗口启动/退出动画的执行流程

好,现在咱们把整个流程串起来。窗口启动和退出动画,本质上是对称的,但细节上有差异。我画了一张流程图,方便你理解:

窗口启动/退出动画执行流程 AppTransition 发起请求 查找 WindowAnimationSpec 创建 AnimationAdapter 启动动画:startAnimation() 退出动画:startAnimation() SurfaceFlinger 合成动画帧 SurfaceFlinger 合成动画帧 onAnimationEnd() 回调

整个流程其实可以概括为四个阶段:

  1. 触发阶段:当窗口状态变化(比如启动 Activity 或按返回键),AppTransition 会记录这次过渡请求。
  2. 准备阶段:WMS 根据窗口的 WindowAnimationSpec 查找对应的动画参数。这里有个关键点——启动动画和退出动画的 Spec 是分开配置的,分别对应 mLaunchAnimationmExitAnimation
  3. 执行阶段:AnimationAdapter 被创建,然后调用 startAnimation()。这个方法内部会创建一个 SurfaceControl.Transaction,并设置初始的变换矩阵、透明度等。
  4. 结束阶段:动画播放完毕后,onAnimationEnd() 被调用。这里会清理资源,并通知 WMS 窗口状态已经稳定。

注意:启动动画和退出动画有一个重要的区别——启动动画是「从不可见到可见」,所以初始状态通常是透明或缩小的;退出动画是「从可见到不可见」,最终状态是透明或缩小的。这个方向搞反了,动画就会「闪一下」或者「跳一下」。

实际项目中的坑

我记得有一次,产品经理要求实现一个「窗口从底部滑入」的效果。我直接用了系统默认的 R.anim.slide_in_bottom,结果发现动画执行时,窗口的背景是黑色的,而且滑入过程中能看到窗口边缘的锯齿。

排查后发现两个问题:

  • 黑色背景是因为 WindowAnimationSpec 没有设置 mBackgroundColor,SurfaceFlinger 默认用黑色填充。解决方案是显式设置背景色为透明。
  • 锯齿问题是因为动画的插值器用了 LinearInterpolator,导致每帧的位移量不均匀。换成 DecelerateInterpolator 后,效果平滑多了。

所以,我建议你在自定义窗口动画时,一定要关注这三个参数:插值器、背景色、动画时长。这三个参数调好了,动画效果基本不会差。

总结

窗口动画机制,说白了就是 WMS 和 SurfaceFlinger 之间的一场「双人舞」。WindowAnimationSpec 定义舞步,AnimationAdapter 负责领舞,AppTransition 控制节奏。你只要理解了这三个角色的分工,以及它们之间的通信方式,窗口动画这块就没什么秘密了。

嗯,今天的内容就到这里。记住,动画不是炫技,而是为了提升用户体验。别为了动画而动画,有时候「无动画」反而是最好的动画。

一句话总结:窗口动画的核心是「跨进程的补间动画」,WMS 定义意图,SurfaceFlinger 负责渲染,中间通过 Transaction 传递每一帧的变化。


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