BufferQueue 位于图形内容的生产者和消费者之间。应用、相机或解码器不断提交画面,SurfaceFlinger、ImageReader 等消费者再按各自的节奏取走它们。
要看清这个周转过程,可以盯住一个 Slot:Producer 如何连接、取得和提交它,Consumer 如何取得并归还它,以及 release 之后哪些对象会留到下一轮。
以下以 Android 15 为参考版本;正文中的源码链接固定到同一版本的 AOSP 实现。
一帧在 BufferQueue 中由四类生命周期不同的记录共同完成交接:BufferSlot 保留长期索引和当前资源绑定,GraphicBuffer 表示本地可管理的图形资源包装,BufferItem 记录一次提交的快照,Fence 约束前后所有者何时能安全访问。四个接口改变这些记录的状态、集合归属和同步依赖。
正文沿一个 Slot 的生命周期,跟踪 Producer 的 connect()、dequeueBuffer()、requestBuffer()、queueBuffer()、cancelBuffer(),再进入 Consumer 的 acquireBuffer()、releaseBuffer()。Shared Buffer Mode、批量接口、attach/detach、Sideband Stream、可丢弃 Item 的替换路径、HWC present Fence 和驱动内部实现不在本篇展开;容量、背压与跨进程 import 分别由同组另外两篇文章承担。
一帧走完,Item 离开队列,Slot 始终留在 mSlots
下面跟踪一个已经绑定 GraphicBuffer A、本次规格不变的 BufferSlot i。首次分配和规格变化时的重新分配留到后文说明。
BufferSlot i 始终是 BufferQueueCore::mSlots[i] 中的对象,不随接口调用进入 Producer、Consumer 或 mQueue。四个接口改变 Slot 的状态、集合归属和 Fence 去向;queueBuffer() 创建的 BufferItem 才会进入 mQueue。
同一次交接包含四个维度:
| 接口 | Slot 状态 | 集合归属 | BufferItem | Fence 交接 |
|---|---|---|---|---|
dequeueBuffer() |
FREE → DEQUEUED | mFreeBuffers → mActiveBuffers |
无 | Slot 中的 release Fence 返回 Producer |
queueBuffer() |
DEQUEUED → QUEUED | 保持在 mActiveBuffers |
创建后进入 mQueue |
Producer 的 acquire Fence 写入 Slot 和 Item |
acquireBuffer() |
QUEUED → ACQUIRED | 保持在 mActiveBuffers |
离开 Core 的 mQueue,作为输出交给 Consumer |
acquire Fence 随 Item 交给 Consumer |
releaseBuffer() |
ACQUIRED → FREE | mActiveBuffers → mFreeBuffers |
本次提交不再产生新 Item | Consumer 的 release Fence 写回 Slot |
BufferItem::mSlot 把本次提交指向 mSlots[i],BufferSlot::mGraphicBuffer 保存 Core 对这个 Slot 的当前 Buffer 绑定。Consumer 尚未缓存这组 Slot/Buffer 映射时,Item 还会携带 mGraphicBuffer;映射仍然有效时,后续 Item 只需要携带 Slot 索引和帧级信息。
共享 Core 的是实现对象,不一定是两端调用者
BufferQueue.cpp 中的 createBufferQueue() 先创建一个 BufferQueueCore,再把它传给 BufferQueueProducer 和 BufferQueueConsumer。省略空指针检查后,创建关系只剩下面几行:
cpp
sp<BufferQueueCore> core(new BufferQueueCore());
sp<IGraphicBufferProducer> producer(
new BufferQueueProducer(core, consumerIsSurfaceFlinger));
sp<IGraphicBufferConsumer> consumer(
new BufferQueueConsumer(core));
*outProducer = producer;
*outConsumer = consumer;
共享同一个 Core 的是 BufferQueue 实现侧的 BufferQueueProducer 与 BufferQueueConsumer。外部 Producer 和 Consumer 面对 IGraphicBufferProducer、IGraphicBufferConsumer 接口;调用可以发生在同一进程,也可以通过 Binder 到达实现对象。两端调用者不需要直接持有同一个 BufferQueueCore C++ 对象。
connect:先确定谁能按哪种 API 使用这条队列
Producer 真正开始申请 Slot 前,必须先经 IGraphicBufferProducer::connect() 建立契约。固定版本的 BufferQueueProducer::connect() 会拒绝已 abandon、尚无 Consumer 或已经连接 Producer API 的队列;并且只接受 EGL、CPU、MEDIA、CAMERA 四类 API。连接成功后,Core 记录当前 API,并返回默认尺寸、transform hint、待处理帧数、下一帧号和最大 Buffer 数等输出。
dequeueBuffer() 发生在已经确定 Producer API 和 Consumer 端点的单 BufferQueue 契约中。调用方可以是 Java Surface、native Surface/ANativeWindow、EGL、Camera 或 Codec;它们都是相对于当前队列的 Producer 入口。
Core 同时维护 Slot 池、提交队列和 Slot 集合
BufferQueueCore.h 中与对象周转直接相关的成员如下,片段省略连接、分配策略和监听器等字段:
cpp
class BufferQueueCore : public virtual RefBase {
// ...
mutable std::mutex mMutex;
BufferQueueDefs::SlotsType mSlots;
Fifo mQueue;
std::set<int> mFreeSlots;
std::list<int> mFreeBuffers;
std::list<int> mUnusedSlots;
std::set<int> mActiveBuffers;
mutable std::condition_variable mDequeueCondition;
};
mSlots 是固定的 Slot 数组,其他集合保存的是索引。排除这些特殊模式后,同一个索引在同一时刻只会属于下面一种集合:
| 集合 | Slot 是否参与当前缓冲池 | 是否绑定 GraphicBuffer | 普通路径中的含义 |
|---|---|---|---|
mUnusedSlots |
否 | 否 | 超出当前参与数量的 Slot 元数据 |
mFreeSlots |
是 | 否 | 可供分配新 Buffer 的 FREE Slot |
mFreeBuffers |
是 | 是 | 已有可复用 Buffer 的 FREE Slot |
mActiveBuffers |
是 | 通常是;为新 Buffer 预留 Slot 后、分配完成前可以短暂为空 | 正处于 DEQUEUED、QUEUED 或 ACQUIRED 的 Slot |
mQueue 不在这张 Slot 集合表中,因为它保存的是 BufferItem,不是 BufferSlot。一个 QUEUED Slot 的索引仍在 mActiveBuffers,同时有一条指向它的 Item 位于 mQueue。
BufferQueueProducer::waitForFreeSlotThenRelock() 选择 Slot 时优先复用 mFreeBuffers 中已经绑定 Buffer 的位置;没有可复用 Buffer 且允许分配时,才从 mFreeSlots 取得空位置:
cpp
int slot = getFreeBufferLocked();
if (slot != BufferQueueCore::INVALID_BUFFER_SLOT) {
*found = slot;
} else if (mCore->mAllowAllocation) {
*found = getFreeSlotLocked();
}
// 省略 Slot 属性检查与 Shared Buffer Mode 分支
mCore->mActiveBuffers.insert(found);
releaseBuffer() 走相反方向。Consumer 释放 Slot 后,Core 从 mActiveBuffers 删除索引,再把它放入 mFreeBuffers:
cpp
mSlots[slot].mFence = releaseFence;
mSlots[slot].mBufferState.release();
// 排除 Shared Buffer Mode 后
mCore->mActiveBuffers.erase(slot);
mCore->mFreeBuffers.push_back(slot);
release 后仍保留 Buffer 绑定,下一次 dequeue 会优先从 mFreeBuffers 取 Slot;这就是 GraphicBuffer 能够复用的直接原因。
mMutex 保护这些共享状态。没有可用 Slot,或队列中的 Buffer 已达到上限时,waitForFreeSlotThenRelock() 会在 mDequeueCondition 上等待。等待期间会暂时释放 Core 锁,让 acquire、release 或配置变化继续推进;队列缩短或 Slot 回到 free 集合后,等待者会被唤醒。完整的阻塞条件与背压行为留给后续专题。
三个对象处在三个生命周期层级
GraphicBuffer 是被管理的图形资源包装,BufferSlot 是缓冲池中的长期位置,BufferItem 是一次提交的帧级记录。
| 对象 | 主要位置 | 生命周期 | 主要职责 |
|---|---|---|---|
BufferSlot |
BufferQueueCore::mSlots 的固定数组位置 |
随 BufferQueue 长期存在并反复流转 | 保存当前 Buffer 绑定、所有权状态、缓存标志和待交接 Fence;Slot 索引来自数组位置,不是结构体内部字段 |
GraphicBuffer |
Core 侧由 BufferSlot::mGraphicBuffer 持有,两端可维护自己的 Slot 映射 |
底层分配可跨多次提交复用 | 封装 buffer handle、尺寸、格式、usage 等信息,并连接底层图形内存 |
BufferItem |
先进入 BufferQueueCore::mQueue,随后作为 acquire 输出交给 Consumer |
对应一次 queueBuffer() 提交 |
保存 Slot 索引、帧号、时间戳、裁剪、变换、Fence 等帧级语义 |
BufferSlot:64 是索引容量,参与数量动态变化
ui/BufferQueueDefs.h 将 Slot 索引空间固定为 64,gui/BufferQueueDefs.h 再把 mSlots 使用的 SlotsType 定义为同样大小的 BufferSlot 数组:
cpp
static constexpr int NUM_BUFFER_SLOTS = 64;
typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];
Core 创建时,64 个 Slot 对象一起完成初始化,状态都是 FREE,mGraphicBuffer 为空。64 是索引容量,不代表已经分配 64 块图形内存。
实际参与当前缓冲池的数量由 getMaxBufferCountLocked() 计算:
cpp
int maxBufferCount = mMaxAcquiredBufferCount +
mMaxDequeuedBufferCount +
((mAsyncMode || mDequeueBufferCannotBlock) ? 1 : 0);
return std::min(mMaxBufferCount, maxBufferCount);
构造时 acquired、dequeued 上限都为 1,async 与 non-blocking 都未开启,因此初始结果是 2:两个索引进入 mFreeSlots,其余进入 mUnusedSlots。连接方式、异步或非阻塞设置以及 Producer/Consumer 配置都可能调整参与数量,但不会超过 64。mUnusedSlots 中的索引没有绑定 Buffer,所以"2 / 64"既不是固定运行配置,也不是图形内存使用率。
BufferSlot.h 中与这条交接链相关的字段如下:
cpp
struct BufferSlot {
sp<GraphicBuffer> mGraphicBuffer;
BufferState mBufferState;
bool mRequestBufferCalled;
uint64_t mFrameNumber;
sp<Fence> mFence;
bool mAcquireCalled;
};
mGraphicBuffer 是单个强引用,因此一个 Slot 同一时刻只能不绑定 Buffer,或者绑定一个 GraphicBuffer。Slot 本身持续存在,绑定却可以被清空、替换或继续保留;同一个 Slot 在生命周期中可以先后绑定不同的底层分配。
GraphicBuffer:分配发生在 dequeue,Producer 按 flag 更新缓存
GraphicBuffer 是 buffer handle 和尺寸、格式、usage 等属性的 C++ 包装。实际像素存储位于 handle 指向的底层分配中。
Producer 调用 dequeueBuffer() 时,BufferQueueProducer 会在当前可用集合中选择 Slot;没有可用 Slot 时,普通可阻塞路径会等待条件变化。选中后,Slot 进入 mActiveBuffers,BufferState 从 FREE 投影为 DEQUEUED,之前 Consumer 留下的 release Fence 作为输出交给 Producer。这个返回只表示 Producer 已得到逻辑写入权;是否已经能覆写底层内存还要看 Fence。
随后它检查当前绑定是否满足本次请求。Buffer 为空或宽高、格式、层数、usage 不匹配时,BufferQueueProducer 清空旧绑定以及用于刷新两端缓存的标志,并返回 BUFFER_NEEDS_REALLOCATION:
cpp
bool needsReallocation = buffer == nullptr ||
buffer->needsReallocation(
width, height, format, BQ_LAYER_COUNT, usage);
if (needsReallocation) {
mSlots[found].mAcquireCalled = false;
mSlots[found].mGraphicBuffer = nullptr;
mSlots[found].mRequestBufferCalled = false;
returnFlags |= BUFFER_NEEDS_REALLOCATION;
}
随后 BufferQueueProducer 在不持有 Core 主锁的阶段创建新的 GraphicBuffer,再重新加锁写回 Slot。GraphicBuffer 构造过程调用 GraphicBufferAllocator,最终由 Gralloc allocator 完成底层缓冲区分配。Slot 自己不申请图形内存,它保存分配结果和交接状态。
requestBuffer() 也没有分配 Buffer,它只返回 Slot 中已经写入的对象:
cpp
mSlots[slot].mRequestBufferCalled = true;
*buf = mSlots[slot].mGraphicBuffer;
Surface::dequeueBuffer() 展示了一个真实 Producer 调用方如何完成协议:
cpp
sp<GraphicBuffer>& gbuf(mSlots[buf].buffer);
if ((result & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) ||
gbuf == nullptr) {
result = mGraphicBufferProducer->requestBuffer(buf, &gbuf);
}
Surface 收到 Slot 索引和返回 flags 后,只在需要重新分配或本地缓存为空时调用 requestBuffer(),然后把结果保存在自己的 mSlots[buf].buffer。mRequestBufferCalled 用于验证 Producer 在收到更新要求后确实取得了新映射,避免直接用陈旧缓存提交。
如果下一帧规格仍然匹配,dequeue 会保留原来的 GraphicBuffer,Surface 也继续使用本地 Slot 映射,不再调用 requestBuffer()。BufferSlot 复用的是固定数组位置和管理状态,GraphicBuffer 复用的才是 handle 指向的底层图形内存。
因此,requestBuffer() 高频出现是规格变化或本地缓存失效的线索,而不是每帧固定步骤。dequeueBuffer() 变慢也只说明可用 Slot、分配或受保护条件尚未满足;要定位 GPU、SurfaceFlinger 或 HWC 中的具体原因,仍需要结合队列深度、Fence 与调度 Trace。
跨 Binder 时,"复用同一个 GraphicBuffer"也不等于两端持有同一个 C++ 包装对象。GraphicBuffer::flatten() 与 unflatten() 会序列化尺寸、格式、usage 和 handle 中的文件描述符、整数数据,接收端再导入 handle。稳定的是 Slot 与底层分配的映射关系;包装对象可能因进程边界而不同。
BufferQueue 在 Producer 与 Consumer 之间传递 Slot 索引、Buffer handle、元数据和 Fence,不会因为一次 queue 或 acquire 就复制整幅像素。这个结论只描述 BufferQueue 的交接过程;渲染、格式转换、缩放和 Client Composition 仍可能产生额外读写。
BufferItem:一次提交,以及 Consumer 缓存是否需要刷新
BufferItem.h 中与这条交接链相关的字段可以压缩为:
cpp
class BufferItem : public Flattenable<BufferItem> {
public:
sp<GraphicBuffer> mGraphicBuffer;
sp<Fence> mFence;
Rect mCrop;
uint32_t mTransform;
int64_t mTimestamp;
uint64_t mFrameNumber;
int mSlot;
bool mAcquireCalled;
Region mSurfaceDamage;
};
queueBuffer() 只接受当前由 Producer 持有、并已经完成 requestBuffer() 契约的 DEQUEUED Slot;它同时校验裁剪、缩放模式和 Fence 等输入。校验通过后,才将 Slot 转为 QUEUED,并组装 Item。Item 会把 Slot 的 Consumer 缓存状态一起快照下来:
cpp
item.mAcquireCalled = mSlots[slot].mAcquireCalled;
item.mGraphicBuffer = mSlots[slot].mGraphicBuffer;
item.mFrameNumber = currentFrameNumber;
item.mSlot = slot;
item.mFence = acquireFence;
// 普通且不丢帧的路径
mCore->mQueue.push_back(item);
acquireBuffer() 取得 Item 后,先把 Core 侧 Slot 标记为已经被 Consumer 见过,再根据 Item 中保存的旧值决定是否返回 mGraphicBuffer:
cpp
mSlots[slot].mAcquireCalled = true;
mSlots[slot].mBufferState.acquire();
mSlots[slot].mFence = Fence::NO_FENCE;
if (outBuffer->mAcquireCalled) {
outBuffer->mGraphicBuffer = nullptr;
}
第一次 acquire 之前,Item 中的 mAcquireCalled 为 false,Consumer 会收到 GraphicBuffer 并建立自己的 Slot → Buffer 缓存。后续提交的 Item 保存 true,acquireBuffer() 可以清空输出指针,只传 Slot 索引和本次帧信息。这样省去的是 GraphicBuffer 的 Binder flatten/unflatten 和 handle 传递,不涉及像素复制或整幅内存重新映射。
Consumer 侧具体由哪个类保存映射取决于实际消费者,但接口契约要求它在 mGraphicBuffer == nullptr 时使用自己的 Slot 缓存。重新分配、清理 Slot 或断开连接会把 mAcquireCalled 复位为 false,强制下一次 acquire 重新发送 GraphicBuffer。因此,Item 中的空指针表示"沿用已知映射",不表示这个 Slot 没有 Buffer。
连续两帧可以复用同一个 Slot 和底层分配,但仍然需要两条独立的 BufferItem。两次提交的帧号、时间戳、Fence、裁剪、变换和损伤区域都可能不同;这些信息属于一次提交,不能固化在长寿命的 Slot 或 GraphicBuffer 上。
cancelBuffer:未提交的 Slot 也必须归还
如果 Producer 在 dequeue 后放弃提交,cancelBuffer() 要求 Slot 仍处于 DEQUEUED,并接收一条非空 Fence。它撤销 dequeue 计数,把普通 Slot 从 mActiveBuffers 放回 mFreeBuffers,并把 Fence 留在 Slot 中供下一次复用。成功 dequeue 后必须走 queue、cancel,或范围外的 detach/disconnect 之一。
同名字段保存的是长期状态与本次快照
BufferSlot 和 BufferItem 都有 mGraphicBuffer、mFrameNumber、mFence,但它们承担的时间尺度不同:
| 字段 | BufferSlot 中的含义 | BufferItem 中的含义 |
|---|---|---|
mGraphicBuffer |
Core 对这个 Slot 的当前绑定 | Consumer 需要刷新缓存时随本次提交携带的对象 |
mFrameNumber |
该 Slot 最近一次 queue 的帧号,用于计算 buffer age 并校验过期 release | 本次提交的帧号 |
mFence |
当前状态下等待下一位所有者接收的同步条件;交给对端后清为 NO_FENCE |
Producer 为本次提交附带的 acquire Fence |
releaseBuffer(slot, frameNumber, ...) 会比较传入帧号和 Slot 的最新 mFrameNumber。两者不一致时,普通路径返回 STALE_BUFFER_SLOT,避免把过期 release 的 Fence 和状态写到这个 Slot 当前记录的帧上。
BufferState:普通路径是计数状态的一组投影
BufferState 没有保存单一枚举,而是使用三个计数和一个 shared 标志:
cpp
struct BufferState {
uint32_t mDequeueCount;
uint32_t mQueueCount;
uint32_t mAcquireCount;
bool mShared;
};
排除 Shared Buffer Mode 后,三个计数在当前主路径中形成下面四组值:
| 状态 | mDequeueCount |
mQueueCount |
mAcquireCount |
当前所有者 |
|---|---|---|---|---|
| FREE | 0 | 0 | 0 | BufferQueue,可供 Producer 取得 |
| DEQUEUED | 1 | 0 | 0 | Producer |
| QUEUED | 0 | 1 | 0 | BufferQueue,等待 Consumer |
| ACQUIRED | 0 | 0 | 1 | Consumer |
dequeue() 增加 dequeue 计数;queue() 减少 dequeue 计数并增加 queue 计数;acquire() 再把 queue 计数转为 acquire 计数;release() 减少 acquire 计数。图中的四个状态是这组计数在普通路径中的投影,不是源码中的独立枚举。
计数结构是为 Shared Buffer Mode 保留的表达能力:shared Slot 可以同时处在多种占用关系中,同一计数也可能大于 1。当前只讨论普通路径,因此使用上表中的四组值。
Fence:所有权可以先转移,安全访问必须等待信号
BufferState 只能说明当前所有权,不能说明前一个所有者发起的 GPU 或硬件工作是否结束。Fence 把这个时间条件随所有权一起传给下一方。
BufferSlot::mFence 的含义随 Slot 状态变化;它始终指向前一个所有者启动的工作何时结束,而不是固定的"GPU 写 Fence"。
| Slot 状态 | Fence 表达的完成条件 | 下一个需要等待的一方 |
|---|---|---|
| FREE | Consumer 读取已完成,或 cancel 前 Producer 写入已完成 | 下一个 Producer |
| DEQUEUED | Fence 已经随 dequeue 交给 Producer;Slot 内部为 NO_FENCE |
Producer 持有输出 Fence |
| QUEUED | Producer 写入已完成 | Consumer |
| ACQUIRED | Fence 已经随 acquire 输出交给 Consumer;Slot 内部为 NO_FENCE |
Consumer 持有输出 Fence |
四个接口中的关键赋值可以压缩为:
cpp
// BufferQueueProducer::queueBuffer()
mSlots[slot].mFence = acquireFence;
item.mFence = acquireFence;
// BufferQueueConsumer::acquireBuffer()
mSlots[slot].mBufferState.acquire();
mSlots[slot].mFence = Fence::NO_FENCE;
// BufferQueueConsumer::releaseBuffer()
mSlots[slot].mFence = releaseFence;
mSlots[slot].mBufferState.release();
// BufferQueueProducer::dequeueBuffer()
*outFence = mSlots[found].mFence;
mSlots[found].mFence = Fence::NO_FENCE;
Producer 在 queueBuffer() 中提交 acquire Fence,表示写入工作何时完成。acquireBuffer() 把带有这条 Fence 的 Item 交给 Consumer,同时清空 Slot 中的引用;Consumer 必须在读取 Buffer 前等待 Fence 满足。
Consumer 在 releaseBuffer() 中提交 release Fence,表示读取或使用工作何时完成。下一次 dequeueBuffer() 把它返回给 Producer,再清空 Slot 中的引用;Producer 必须在重新写入前等待 Fence 满足。
在这条 native Fence 主路径中,BufferQueue 只保存并转交 Fence。状态变化表示当前所有权,Fence signal 表示新所有者最早可以访问 Buffer 的时间,两者可以错开。
NO_FENCE 与 HWC 命名边界
固定版本的 Fence.cpp 中,Fence::NO_FENCE 使用 fd -1;wait() 遇到它会直接成功,表示没有待等待的异步依赖,并非错误。有效 Fence 则由 sync_wait() 等待;这是 Android synchronization framework 所说的显式同步。
HWC 语境也有 acquire、release、present Fence,但命名取决于具体交接的接收者和资源。当前只讨论 BufferQueue Producer/Consumer 的边界。判断某条 Fence 时,应先写清它由谁生成、交给谁、保护哪一次读或写。
一次"queue 已接受但下一次仍不能覆盖"的推演
假设 Producer 把帧 A 放入 Slot i,Consumer 随后 acquire 并开始读取;这时 queueBuffer() 已经成功,但 Producer 不能立刻把 A 覆盖成帧 B。沿 Slot 和 Fence 两条线看,状态如下:
| 时刻 | Slot/Item 状态 | Fence 所在位置 | Producer 能做的事 |
|---|---|---|---|
t0:queueBuffer(A) 返回 |
Slot i 为 QUEUED,BufferItem(A) 在 mQueue |
acquire Fence 随 Item 等待 Producer 写入完成 | 不能再对 i 写入 |
t1:acquireBuffer() |
Item 离开 mQueue,Slot i 为 ACQUIRED |
Consumer 持有 acquire Fence;Slot 内清为空 | 只能等待其他 FREE Slot,或处理别的在途帧 |
t2:Consumer releaseBuffer() |
Slot i 回到 FREE |
release Fence 写回 Slot,表示 Consumer 使用何时结束 | 下一次 dequeueBuffer() 可能选中 i,但必须等待该 Fence |
t3:Producer 再次 dequeue |
新一轮 DEQUEUED,旧 Item 已结束 |
release Fence 返回 Producer 后由 Producer 等待/传递 | Fence 满足后才能安全写入帧 B |
queueBuffer() 成功表示提交记录进入队列,不能据此重写底层内存;releaseBuffer() 完成表示所有权归还,Fence 仍可能未 signal。能否覆盖由 Slot 的所有权状态和 Fence 完成点共同决定。Shared Buffer、批量接口和驱动内部等待不在这条普通路径内。
一次交接完成后留下什么
完成一次 dequeue/queue/acquire/release 交接后,本次 BufferItem 已经离开 Core 的 mQueue;Consumer 可以暂时持有 acquire 输出,但下一次提交会产生新的 Item。mSlots[i] 中的 BufferSlot 始终存在。规格没有变化且 Buffer 未被清理时,mGraphicBuffer 仍指向同一个底层分配,Slot 索引对应的两端缓存也可以继续使用。
复用涉及三个层次:
mFreeBuffers让 dequeue 优先选择仍然绑定 Buffer 的 FREE Slot。- Slot 索引让 Core、Producer 缓存和 Consumer 缓存用稳定编号定位同一份底层分配。
- BufferItem 把帧号、时间戳、裁剪、变换、damage 和 acquire Fence 固定在一次提交上,不污染长期 Slot 状态。
排查时按"对象身份 → 所有权 → 提交快照 → 时间依赖"检查:先确认索引是否仍指向同一资源,再看 Slot 是否 FREE/DEQUEUED/QUEUED/ACQUIRED,随后核对本次 BufferItem 的帧号与缓存状态,最后确认 acquire/release Fence 的生产者和等待者。这样可以区分"没有空 Slot""Item 尚未被消费""缓存需要刷新"和"资源仍被硬件占用"四类问题。
固定源码索引
以下链接均固定到同一份 frameworks/native 源码提交 f7274fca5e36082674740bc6c976f73c4578d009:
libs/gui/BufferQueue.cpplibs/gui/BufferQueueCore.cpplibs/gui/include/gui/BufferQueueCore.hlibs/gui/include/gui/BufferQueueDefs.hlibs/ui/include/ui/BufferQueueDefs.hlibs/gui/include/gui/BufferSlot.hlibs/gui/include/gui/BufferItem.hlibs/gui/BufferQueueProducer.cpplibs/gui/BufferQueueConsumer.cpplibs/gui/Surface.cpplibs/ui/GraphicBuffer.cpplibs/ui/GraphicBufferAllocator.cpplibs/ui/Fence.cpp