2、合成基础概念:BufferQueue机制、GraphicBuffer与ANativeWindow、Layer与Display的抽象

好,我们正式开始聊 SurfaceFlinger 的合成基础。

这一章,说白了就是打地基。你想想看,一个 Android 手机屏幕上的画面,是怎么从 App 一路跑到显示器的?中间经过了哪些环节?每个环节又抽象成了什么?

我个人习惯,在啃源码之前,先把这些概念模型理清楚。不然你一头扎进 SurfaceFlinger.cpp 里,看到 BufferQueue、GraphicBuffer、Layer、Display 这些词满天飞,很容易懵。

2.1 BufferQueue 机制:生产与消费的桥梁

先聊 BufferQueue。这是 Android 图形系统里最核心的机制之一,没有它,App 和 SurfaceFlinger 根本没法协作。

BufferQueue 是个什么东西?说白了,就是一个生产者-消费者模型。生产者是 App 的渲染线程,消费者是 SurfaceFlinger。App 画好一帧画面,放进 BufferQueue;SurfaceFlinger 从 BufferQueue 里取走这一帧,拿去合成。

嗯,这里要注意:BufferQueue 不是简单的队列。它内部管理着一组 GraphicBuffer,并且有严格的同步机制。

核心要点:BufferQueue 解决了「谁生产、谁消费、什么时候生产、什么时候消费」的问题。它通过 dequeueBufferqueueBufferacquireBufferreleaseBuffer 四个核心方法,把生产者和消费者解耦。

我在项目中遇到过一个问题:App 侧卡顿,但 SurfaceFlinger 这边却显示 BufferQueue 里一直有 Buffer 没被消费。后来排查发现,是 App 调用了 dequeueBuffer 之后,忘记调用 queueBuffer 了。嗯,这种低级错误,一旦出现,画面就直接卡死。

BufferQueue 的典型工作流程是这样的:

  1. App 调用 dequeueBuffer,从 BufferQueue 里取出一块空闲的 GraphicBuffer。
  2. App 往这块 GraphicBuffer 里渲染画面(通过 GPU 或 CPU)。
  3. App 调用 queueBuffer,把填好数据的 Buffer 还给 BufferQueue,并标记为「待消费」。
  4. SurfaceFlinger 在 VSync 信号到来时,调用 acquireBuffer,取走这块 Buffer。
  5. SurfaceFlinger 合成完毕后,调用 releaseBuffer,把 Buffer 放回空闲池。

你可能会问:为什么需要多个 Buffer?一个不行吗?

当然不行。如果只有一个 Buffer,App 在渲染的时候,SurfaceFlinger 就不能读,反之亦然。这就成了串行,帧率直接砍半。所以 Android 通常用 2 个或 3 个 Buffer 来做「双缓冲」或「三缓冲」,让生产和消费可以流水线并行。

避坑指南:我曾经调试过一个诡异的问题:画面撕裂。后来发现是 BufferQueue 的 allowAllocation 标志没设对,导致 Buffer 数量不够,SurfaceFlinger 和 App 在抢同一个 Buffer。嗯,这种问题,用 dumpsys SurfaceFlinger 一看 BufferQueue 状态就明白了。

2.2 GraphicBuffer 与 ANativeWindow:内存的抽象

聊完 BufferQueue,我们来看看它里面装的是什么——GraphicBuffer。

GraphicBuffer 是 Android 图形内存的抽象。它封装了一块共享内存,这块内存可以被 CPU 访问,也可以被 GPU 访问,甚至可以被显示硬件直接读取。说白了,它就是一块「谁都能用的内存」。

我建议你把 GraphicBuffer 理解成一个「数据容器」。它里面存的是像素数据,格式可能是 RGBA_8888、RGB_565、YUV420 等等。SurfaceFlinger 合成的时候,就是从各个 Layer 的 GraphicBuffer 里读出像素,然后混合到一起。

GraphicBuffer 的核心属性包括:

属性 说明
width / height 图像的宽高(像素单位)
format 像素格式,如 HAL_PIXEL_FORMAT_RGBA_8888
usage 使用标志,如 GRALLOC_USAGE_HW_RENDER(GPU 渲染)或 GRALLOC_USAGE_HW_COMPOSER(硬件合成)
handle 底层的内存句柄,由 Gralloc 模块分配

那 ANativeWindow 又是什么?

ANativeWindow 是一个更上层的抽象。它代表了一个「可以往上面画东西的窗口」。在 App 侧,你拿到的 Surface 对象,本质上就是一个 ANativeWindow。App 通过 ANativeWindow 来 dequeueBuffer、queueBuffer,背后操作的就是 BufferQueue。

嗯,这里有个关键点:ANativeWindow 是生产者的视角。App 通过它来生产画面。而 SurfaceFlinger 那边,拿到的是 ConsumerBase,是消费者的视角。两者通过 BufferQueue 连接。

一句话总结:GraphicBuffer 是数据本身,ANativeWindow 是生产数据的接口,BufferQueue 是传递数据的通道。

2.3 Layer 与 Display 的抽象

好,现在画面从 App 生产出来了,通过 BufferQueue 传到了 SurfaceFlinger。接下来 SurfaceFlinger 要做什么?合成。

合成之前,它需要知道两件事:

  • 要合成哪些画面?——这就是 Layer。
  • 合成到哪里去?——这就是 Display。

Layer 是 SurfaceFlinger 对「一个显示层」的抽象。每个 App 窗口、状态栏、导航栏、壁纸,在 SurfaceFlinger 里都对应一个 Layer。Layer 里面保存了:

  • 它对应的 BufferQueue 消费者端(ConsumerBase)
  • 它的位置、大小、透明度、裁剪区域等几何属性
  • 它的合成方式(GPU 合成还是硬件合成)
  • 它的 Z-order(层叠顺序)

我记得有一次调试一个「窗口显示位置不对」的问题。App 明明画在左上角,但屏幕上显示在右下角。后来发现是 Layer 的 setPosition 被某个系统服务错误地修改了。嗯,这种问题,直接看 dumpsys SurfaceFlinger 里每个 Layer 的 poscrop 就能定位。

Display 是 SurfaceFlinger 对「一个物理或虚拟屏幕」的抽象。常见的 Display 有:

  • 内置屏幕(Primary Display)
  • 外接屏幕(如 HDMI,External Display)
  • 虚拟屏幕(Virtual Display,用于屏幕录制或投屏)

每个 Display 都有自己的属性:分辨率、刷新率、旋转角度、HDR 支持等。SurfaceFlinger 在合成时,会把所有 Layer 按照 Z-order 排序,然后混合到 Display 对应的 Framebuffer 里。

注意:Layer 和 Display 是多对多的关系吗?不是。一个 Layer 只能属于一个 Display。但一个 Display 可以包含多个 Layer。嗯,这个要记清楚,不然看代码里 mDisplaysmCurrentState.layersSortedByZ 的关系时会绕晕。

最后,我提一个我自己的理解方式:

你可以把 Layer 想象成「一张透明的胶片」,上面画了画面。Display 就是「投影仪的幕布」。SurfaceFlinger 的工作,就是把一堆胶片叠在一起,然后投影到幕布上。叠的顺序(Z-order)、每张胶片的位置(pos)、透明度(alpha),都决定了最终画面长什么样。

嗯,这个比喻虽然简单,但很管用。后面我们分析合成流程时,你脑子里一直想着这个画面就行。

个人经验:我曾经在分析一个「屏幕录制黑屏」的问题时,发现是 Virtual Display 的 Layer 没有正确关联到 App 的 Surface。原因在于 Virtual Display 的 onFrameAvailable 回调没有被触发。嗯,这种问题,归根结底是对 Layer 和 Display 的绑定关系理解不够深。

好,这一章的基础概念就聊到这里。下一章,我们开始真正进入 SurfaceFlinger 的合成流程,看看它到底是怎么把这些 Layer 变成一帧画面的。