窗口动画机制:WindowAnimationSpec、AnimationAdapter、窗口启动/退出动画的执行流程
各位同学,今天我们来聊聊窗口动画。说实话,窗口动画这块内容,在 Android 系统里属于那种「看着简单,挖下去全是坑」的模块。我当年第一次接触 WindowManagerService 的动画逻辑时,心里想的是:「不就是个补间动画吗?」结果被现实狠狠教育了一顿。
窗口动画不是简单的 View 动画。它涉及跨进程通信、Surface 管理、同步机制,还有一堆你平时根本不会注意到的细节。嗯,咱们今天就把这块硬骨头啃下来。
窗口动画的核心角色
在 WMS 里,窗口动画主要涉及三个关键角色:WindowAnimationSpec、AnimationAdapter,以及执行动画的 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,哪怕你的动画是瞬时的,也要给个最小值。
窗口启动/退出动画的执行流程
好,现在咱们把整个流程串起来。窗口启动和退出动画,本质上是对称的,但细节上有差异。我画了一张流程图,方便你理解:
整个流程其实可以概括为四个阶段:
- 触发阶段:当窗口状态变化(比如启动 Activity 或按返回键),AppTransition 会记录这次过渡请求。
- 准备阶段:WMS 根据窗口的
WindowAnimationSpec查找对应的动画参数。这里有个关键点——启动动画和退出动画的 Spec 是分开配置的,分别对应mLaunchAnimation和mExitAnimation。 - 执行阶段:AnimationAdapter 被创建,然后调用
startAnimation()。这个方法内部会创建一个SurfaceControl.Transaction,并设置初始的变换矩阵、透明度等。 - 结束阶段:动画播放完毕后,
onAnimationEnd()被调用。这里会清理资源,并通知 WMS 窗口状态已经稳定。
注意:启动动画和退出动画有一个重要的区别——启动动画是「从不可见到可见」,所以初始状态通常是透明或缩小的;退出动画是「从可见到不可见」,最终状态是透明或缩小的。这个方向搞反了,动画就会「闪一下」或者「跳一下」。
实际项目中的坑
我记得有一次,产品经理要求实现一个「窗口从底部滑入」的效果。我直接用了系统默认的 R.anim.slide_in_bottom,结果发现动画执行时,窗口的背景是黑色的,而且滑入过程中能看到窗口边缘的锯齿。
排查后发现两个问题:
- 黑色背景是因为
WindowAnimationSpec没有设置mBackgroundColor,SurfaceFlinger 默认用黑色填充。解决方案是显式设置背景色为透明。 - 锯齿问题是因为动画的插值器用了
LinearInterpolator,导致每帧的位移量不均匀。换成DecelerateInterpolator后,效果平滑多了。
所以,我建议你在自定义窗口动画时,一定要关注这三个参数:插值器、背景色、动画时长。这三个参数调好了,动画效果基本不会差。
总结
窗口动画机制,说白了就是 WMS 和 SurfaceFlinger 之间的一场「双人舞」。WindowAnimationSpec 定义舞步,AnimationAdapter 负责领舞,AppTransition 控制节奏。你只要理解了这三个角色的分工,以及它们之间的通信方式,窗口动画这块就没什么秘密了。
嗯,今天的内容就到这里。记住,动画不是炫技,而是为了提升用户体验。别为了动画而动画,有时候「无动画」反而是最好的动画。
一句话总结:窗口动画的核心是「跨进程的补间动画」,WMS 定义意图,SurfaceFlinger 负责渲染,中间通过 Transaction 传递每一帧的变化。