6、WMS(WindowManagerService)与视图体系:窗口管理、SurfaceFlinger、View绘制流程
说实话,Android 的窗口系统是我当年啃得最久的一块。你想想看,一个手机屏幕上同时跑着那么多 App,有的在前台,有的在后台,有的还弹个对话框——这些窗口是怎么叠在一起的?谁先画谁后画?触摸事件又怎么找到正确的窗口?
嗯,这背后就是 WMS 和 SurfaceFlinger 在干活。今天我就带你把这套体系拆开看看。
6.1 窗口管理:WMS 的核心职责
WMS(WindowManagerService)是系统服务,跑在 system_server 进程里。它的活儿说白了就三件:
- 窗口的添加与移除——谁想显示,谁要消失,都得经过它
- 窗口的层级排序——Z-order 怎么排,谁在上面谁在下面
- 输入事件的分发——触摸点下去,WMS 告诉 InputDispatcher 该发给谁
我在项目中遇到过一个问题:一个悬浮窗盖住了系统的状态栏,用户怎么点都点不到通知中心。查了半天,发现是那个 App 用了 TYPE_APPLICATION_OVERLAY 但没设置正确的 flags。嗯,这里要注意——窗口类型决定了它的层级范围,不是你想盖谁就盖谁的。
窗口类型与层级范围
| 类型 | 层级范围 | 典型用途 |
|---|---|---|
| TYPE_APPLICATION | 1 ~ 99 | 普通 Activity 窗口 |
| TYPE_SUB_PANEL | 1000 ~ 1999 | 子窗口(如 PopupWindow) |
| TYPE_SYSTEM_ALERT | 2000 ~ 2999 | 系统级悬浮窗 |
| TYPE_STATUS_BAR | FIRST_SYSTEM_UI 以上 | 状态栏、导航栏 |
WMS 内部维护了一个 WindowState 的列表,每个 WindowState 对应一个窗口。每次添加或移除窗口,WMS 都会重新计算 Z-order,然后通知 SurfaceFlinger 更新图层。
6.2 SurfaceFlinger:图层的合成者
SurfaceFlinger 是 Android 的图形合成服务。它不负责画图,只负责把各个 App 画好的图层合成到一起,然后送给显示硬件。
我习惯把 SurfaceFlinger 比作一个「拼图工人」——每个 App 把自己的那块拼图(Surface)画好,SurfaceFlinger 按照 WMS 给的顺序把它们叠起来,最后输出一帧完整的画面。
核心流程是这样的:
- App 通过
Surface把绘制内容提交到 BufferQueue - SurfaceFlinger 从 BufferQueue 取出 Buffer
- 根据 WMS 提供的 Z-order 信息,用 OpenGL ES 或 HWC 合成
- 输出到 Framebuffer 或 Display
个人经验:我曾经调试过一个掉帧问题,发现是某个 App 的 Surface 尺寸和屏幕尺寸不一致,导致 SurfaceFlinger 每次都要做缩放合成,性能开销很大。后来强制 App 使用匹配的尺寸,帧率就稳了。
6.3 View 绘制流程:从 measure 到 draw
View 的绘制流程是 Android 开发者最熟悉的,但很多人只停留在 API 层面。我建议你从底层理解它——说白了,就是三个步骤:
- measure:量尺寸。父 View 告诉子 View 你能占多大空间
- layout:摆位置。确定每个 View 的 left、top、right、bottom
- draw:画出来。把内容画到 Canvas 上
这三个步骤由 ViewRootImpl 触发。ViewRootImpl 是连接 WMS 和 View 树的桥梁。当 WMS 告诉 ViewRootImpl「你可以画了」,ViewRootImpl 就会调用 performTraversals(),依次执行 measure、layout、draw。
// ViewRootImpl 中的核心方法(简化版)
private void performTraversals() {
// 1. 测量
int desiredWindowWidth = ...;
int desiredWindowHeight = ...;
host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
// 2. 布局
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
// 3. 绘制
mSurface.lockCanvas(dirty);
host.draw(canvas);
mSurface.unlockCanvasAndPost(canvas);
}
你想想看,每次界面刷新,这套流程都要跑一遍。如果 View 树太深,或者某个 View 的 onDraw 太慢,就会掉帧。
避坑指南:我曾经在 onMeasure 里做了大量计算,导致 measure 阶段耗时超过 16ms。结果就是界面卡顿。记住:onMeasure 和 onDraw 里不要做耗时操作,能预计算的提前算好。
6.4 三者如何协作?
WMS、SurfaceFlinger、View 绘制流程不是各自为战的。它们是一条流水线:
- 用户打开一个 Activity,AMS 通知 WMS 创建窗口
- WMS 分配一个 Surface,交给 ViewRootImpl
- ViewRootImpl 触发 measure/layout/draw,把内容画到 Surface 上
- Surface 通过 BufferQueue 把数据传给 SurfaceFlinger
- SurfaceFlinger 合成所有图层,输出到屏幕
嗯,这里有个关键点:Surface 本身不存储像素数据,它只是一个句柄。真正的数据在 BufferQueue 的 Buffer 里。SurfaceFlinger 从 BufferQueue 取 Buffer,合成后显示。
6.5 一些底层细节
最后聊几个我踩过的坑:
- Surface 的创建时机:WMS 在
relayoutWindow()中创建 Surface。如果 App 频繁 relayout,就会频繁创建销毁 Surface,导致性能抖动。 - BufferQueue 的容量:默认是 3 个 Buffer。如果 App 生产速度大于消费速度,就会丢帧。我曾经在相机预览中遇到过这个问题,后来通过调整 Buffer 数量解决的。
- 硬件合成器 HWC:SurfaceFlinger 会优先用 HWC 合成,省电且高效。但如果图层有透明叠加或变换,就会 fallback 到 GPU 合成。嗯,这里要注意——尽量减少透明图层,能提升合成效率。
我的习惯:调试窗口相关问题时,我一般先看 dumpsys window 的输出,里面能看到所有窗口的层级、可见性、Surface 信息。然后再用 dumpsys SurfaceFlinger 看图层合成情况。这两个命令基本能定位 90% 的问题。
好了,这一章的内容就到这里。WMS 和 SurfaceFlinger 的配合是 Android 图形系统的基石,理解了它们,你再看 View 的绘制流程就会有种「原来如此」的感觉。
公众号:蓝海资料掘金营,微信deep3321