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_APPLICATION1 ~ 99普通 Activity 窗口
TYPE_SUB_PANEL1000 ~ 1999子窗口(如 PopupWindow)
TYPE_SYSTEM_ALERT2000 ~ 2999系统级悬浮窗
TYPE_STATUS_BARFIRST_SYSTEM_UI 以上状态栏、导航栏

WMS 内部维护了一个 WindowState 的列表,每个 WindowState 对应一个窗口。每次添加或移除窗口,WMS 都会重新计算 Z-order,然后通知 SurfaceFlinger 更新图层。

6.2 SurfaceFlinger:图层的合成者

SurfaceFlinger 是 Android 的图形合成服务。它不负责画图,只负责把各个 App 画好的图层合成到一起,然后送给显示硬件。

我习惯把 SurfaceFlinger 比作一个「拼图工人」——每个 App 把自己的那块拼图(Surface)画好,SurfaceFlinger 按照 WMS 给的顺序把它们叠起来,最后输出一帧完整的画面。

核心流程是这样的:

  1. App 通过 Surface 把绘制内容提交到 BufferQueue
  2. SurfaceFlinger 从 BufferQueue 取出 Buffer
  3. 根据 WMS 提供的 Z-order 信息,用 OpenGL ES 或 HWC 合成
  4. 输出到 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 绘制流程不是各自为战的。它们是一条流水线:

  1. 用户打开一个 Activity,AMS 通知 WMS 创建窗口
  2. WMS 分配一个 Surface,交给 ViewRootImpl
  3. ViewRootImpl 触发 measure/layout/draw,把内容画到 Surface 上
  4. Surface 通过 BufferQueue 把数据传给 SurfaceFlinger
  5. SurfaceFlinger 合成所有图层,输出到屏幕

嗯,这里有个关键点:Surface 本身不存储像素数据,它只是一个句柄。真正的数据在 BufferQueue 的 Buffer 里。SurfaceFlinger 从 BufferQueue 取 Buffer,合成后显示。

WMS · SurfaceFlinger · View 绘制 协作流程 WMS 窗口管理 · Z-order ViewRootImpl measure/layout/draw SurfaceFlinger 图层合成 分配Surface BufferQueue WMS 维护窗口列表 → 通知 SurfaceFlinger 更新 Z-order ViewRootImpl 绘制到 Surface → 提交 Buffer → SurfaceFlinger 合成 → 显示 Z-order 合成 屏幕显示

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