2. 图形显示基础

BufferQueue 是 SurfaceFlinger 里最核心的数据管道。说白了,它就是一块「缓冲区中转站」——一边有人往里面塞数据,另一边有人取走数据去渲染。我刚开始接触 Android 图形系统时,觉得这玩意儿不就是个队列嘛,后来踩了几个坑才明白,它的状态机和同步机制才是真正的精髓。

2.1 生产者-消费者模型

BufferQueue 的设计思路很经典:生产者(比如某个 App 的 UI 线程)往队列里提交 Buffer,消费者(SurfaceFlinger 的合成线程)从队列里取出 Buffer 进行合成显示。两者通过 BufferQueue 解耦,互不阻塞。

核心角色:

  • 生产者(Producer):通常是 App 端的 Surface,通过 dequeueBuffer() 获取空闲 Buffer,填充数据后调用 queueBuffer() 归还。
  • 消费者(Consumer):SurfaceFlinger 端的 Layer,通过 acquireBuffer() 获取已填充的 Buffer,处理完后调用 releaseBuffer() 释放。
  • BufferQueue 核心:管理 Buffer 的分配、状态转换和同步。

我遇到过一个问题:某个第三方 App 频繁创建和销毁 Surface,导致 BufferQueue 里的 Buffer 数量忽多忽少,最后 SurfaceFlinger 分配不出新 Buffer 了。嗯,这里要注意,BufferQueue 的 maxBufferCount 是硬上限,默认通常是 3 个。

2.2 BufferQueue 的创建与生命周期

BufferQueue 的创建流程,我习惯把它分成三步:

  1. App 请求 Surface:App 通过 SurfaceFlinger 的 createSurface() 接口,请求创建一个 Layer。
  2. SurfaceFlinger 创建 BufferQueue:内部会 new 一个 BufferQueue 对象,同时创建生产端(IGraphicBufferProducer)和消费端(IGraphicBufferConsumer)的 Binder 代理。
  3. 两端建立连接:App 拿到生产端代理,SurfaceFlinger 持有消费端代理,两者通过 Binder 通信。
阶段 关键操作 涉及类
创建 BufferQueue::createBufferQueue() BufferQueue, IGraphicBufferProducer, IGraphicBufferConsumer
连接 生产者 connect(),消费者 consumerConnect() BufferQueueProducer, BufferQueueConsumer
使用 dequeue/queue/acquire/release 循环 BufferSlot, GraphicBuffer
销毁 生产者 disconnect(),消费者 consumerDisconnect() BufferQueueCore

生命周期里有个容易忽略的点:BufferQueue 是强引用计数的。生产者和消费者都持有 BufferQueueCore 的智能指针,只有两端都断开连接,BufferQueue 才会真正释放。我曾经调试过一个内存泄漏问题,就是 App 进程被杀后,生产者端没有正确调用 disconnect(),导致 BufferQueue 一直残留在 SurfaceFlinger 进程里。

2.3 Buffer 的状态机与同步机制

Buffer 的状态机是 BufferQueue 最精巧的设计。每个 Buffer 在任意时刻都处于以下 4 种状态之一:

  • FREE:空闲状态,生产者可以 dequeue 使用。
  • DEQUEUED:生产者已取出,正在填充数据。
  • QUEUED:生产者已填充完毕,放入队列等待消费者处理。
  • ACQUIRED:消费者已取出,正在使用(比如合成显示)。

状态转换口诀:

FREE → DEQUEUED(生产者取出)→ QUEUED(生产者归还)→ ACQUIRED(消费者取出)→ FREE(消费者释放)

注意:ACQUIRED 状态也可以直接回到 DEQUEUED,这属于 Buffer 的「取消获取」操作,但实际场景很少用。

同步机制的核心是 Fence(同步栅栏)。每个 Buffer 在状态转换时都会携带一个 Fence 对象,用来保证 GPU 和 CPU 的同步。举个例子:

// 生产者填充完 Buffer 后,会关联一个 releaseFence
status_t err = mProducer->queueBuffer(bufferSlot, input, &output);
// output.releaseFence 表示 GPU 何时完成写入

// 消费者获取 Buffer 时,需要等待 acquireFence
status_t err = mConsumer->acquireBuffer(&item, 0);
// item.mFence 表示 GPU 何时可以安全读取

我踩过的一个坑:在某个车机项目上,GPU 驱动有 bug,导致 Fence 信号永远不触发,Buffer 卡在 ACQUIRED 状态,整个显示链路就僵死了。后来我们加了一个 Fence 超时机制,超过 1 秒就强制释放 Buffer。

注意:Buffer 状态机是线程安全的,内部通过 Mutex 和 Condition 实现。但如果你在生产者或消费者线程里直接操作 BufferSlot 数组,一定要加锁,否则会出现「双倍 dequeue」或「Buffer 丢失」的诡异问题。

2.4 BufferQueue 核心流程图

下面这张图是我自己总结的 BufferQueue 完整流程,包含了状态转换和 Fence 同步点:

BufferQueue 核心流程图 BufferQueue 核心流程与状态机 生产者 (App) 消费者 (SF) BufferQueue 核心 管理 BufferSlot 数组 FREE DEQUEUED QUEUED ACQUIRED dequeueBuffer / queueBuffer acquireBuffer / releaseBuffer dequeue queue acquire release (带 Fence 同步) Fence 同步点 GPU 完成写入后才允许读取

这张图里我特意把 Fence 同步点标出来了。你想想看,如果没有 Fence,生产者还在往 Buffer 里写数据,消费者就拿来显示了,那画面就会出现撕裂(tearing)。

2.5 避坑指南

我曾经遇到过的几个典型问题:

  • Buffer 泄漏:生产者 dequeue 了 Buffer 但没 queue 回去,或者消费者 acquire 了没 release,导致 Buffer 永远卡在某个状态。解决方案:在 BufferQueue 里加一个「僵尸 Buffer 检测」定时器。
  • Fence 死等:GPU 驱动异常导致 Fence 信号丢失。建议:在 acquireBuffer 和 queueBuffer 的调用处加超时保护。
  • Buffer 分配失败:当所有 Buffer 都被 dequeue 或 acquire 时,新的 dequeue 请求会阻塞。如果主线程阻塞,就会导致 App 掉帧。我建议把 dequeueBuffer 放到后台线程去做。

嗯,BufferQueue 的内容就讲到这里。记住一句话:状态机是骨架,Fence 是灵魂。搞懂了这两点,BufferQueue 的源码读起来就会顺畅很多。