
IGraphicBufferProducer驱动UI绘制
当Surface持有IGraphicBufferProducer(简称GBP)后,XML定义的UI最终能显示在屏幕上,核心是"onDraw触发绘制指令→GBP申请显存缓冲区→绘制指令写入显存→提交缓冲区→SurfaceFlinger合成显示"的闭环。
其中IGraphicBufferProducer是连接应用进程与SurfaceFlinger的核心C++ Binder接口,也是UI数据从"应用逻辑"到"物理显存"的关键桥梁。
本文将从XML布局解析开始,逐层拆解onDraw驱动UI绘制的全流程,并深入剖析C++层的核心实现逻辑。
前置核心认知
在拆解流程前,需明确驱动UI绘制的核心组件及分层实现,这是理解GBP作用的基础:
1. 核心组件定位
| 组件 | 层级/语言 | 核心作用 |
|---|---|---|
| IGraphicBufferProducer | C++(跨进程) | SurfaceFlinger提供的Binder接口,Surface通过它申请/提交GraphicBuffer(显存) |
| Surface(Java) | 应用进程-Java | 封装Native层Surface,向上暴露lockCanvas/unlockCanvasAndPost等绘制API |
| Surface(C++) | 应用进程-C++ | 持有GBP的代理对象,实现与BufferQueue的底层交互 |
| GraphicBuffer | C++(跨进程) | 物理显存的封装(Ashmem共享),UI绘制的最终数据载体 |
| BufferQueue | C++(SurfaceFlinger) | 管理GraphicBuffer队列,实现生产者(应用)-消费者(SurfaceFlinger)模型 |
| Canvas(Java/C++) | 混合 | 绘制指令的封装,Java层Canvas映射到C++层Skia画布,最终写入GraphicBuffer |
| SurfaceFlinger | C++(独立进程) | 消费GraphicBuffer,合成多Surface并输出到屏幕 |
2. 核心流程概览
XML布局解析 → View树构建 → Measure/Layout确定尺寸 → onDraw触发 →
Surface.lockCanvas → GBP.dequeueBuffer(申请GraphicBuffer) →
Canvas绑定GraphicBuffer → UI绘制指令写入显存 →
Surface.unlockCanvasAndPost → GBP.queueBuffer(提交缓冲区) →
SurfaceFlinger合成 → 屏幕显示
3. 核心结论
onDraw仅负责"描述UI该怎么画",而IGraphicBufferProducer负责"提供能画的显存空间"并"把画好的数据交给系统",C++层是显存管理、数据传输、绘制执行的核心实现层。
第一步:XML布局解析为View树
UI绘制的起点是XML布局转化为可绘制的View树,这一步为onDraw提供"绘制对象":
1. XML解析(Java层)
开发者编写的layout/activity_main.xml通过LayoutInflater解析为View树,核心逻辑:
java
// Activity.setContentView底层逻辑
public void setContentView(int resId) {
getWindow().setContentView(resId); // 对应PhoneWindow
}
// PhoneWindow.java
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor(); // 创建DecorView(根View)
}
// 核心:LayoutInflater解析XML,添加到DecorView的内容区域
LayoutInflater.from(mContext).inflate(layoutResID, mContentParent);
}
LayoutInflater.inflate:递归解析XML标签(如TextView/LinearLayout),通过反射创建View实例,构建完整的View树;- 解析完成后,View树仅为"逻辑对象",无尺寸/坐标信息,需通过Measure/Layout补充。
2. Measure/Layout(确定绘制尺寸)
ViewRootImpl的performTraversals方法触发View树的Measure(测量)、Layout(布局):
- Measure :递归计算每个View的宽高(如
TextView的文字宽度、ImageView的图片尺寸),生成MeasureSpec; - Layout :递归确定每个View在屏幕上的坐标(
left/top/right/bottom); - 这两步为
onDraw提供"在哪里画"的基础,无尺寸的View无法完成有效绘制。
第二步:onDraw触发与Canvas绑定
onDraw是UI绘制的入口,其核心是通过Surface获取绑定了GraphicBuffer的Canvas,而GraphicBuffer的申请依赖IGraphicBufferProducer。
1. onDraw的触发时机(Java层)
ViewRootImpl的performTraversals方法在Measure/Layout完成后,触发draw流程:
java
// ViewRootImpl.java
private void performTraversals() {
// 1. 执行Measure
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 2. 执行Layout
performLayout(lp, mWidth, mHeight);
// 3. 执行Draw(触发onDraw)
performDraw();
}
private void performDraw() {
// 创建绘制上下文
final Drawable dirty = mDirty;
if (mSurface.isValid()) {
// 核心:锁定Surface,获取Canvas
final Canvas canvas = mSurface.lockCanvas(dirty);
try {
// 递归绘制DecorView及其子View(触发每个View的onDraw)
mView.draw(canvas);
} finally {
// 解锁并提交缓冲区
mSurface.unlockCanvasAndPost(canvas);
}
}
}
performDraw是onDraw的总触发入口,所有View的onDraw都在此递归调用;- 关键:
mSurface.lockCanvas(dirty)是连接Java层绘制与C++层GBP的核心入口。
2. Surface.lockCanvas:绑定GBP申请GraphicBuffer(Java→C++)
Java层Surface.lockCanvas最终调用Native层方法,通过GBP申请空闲的GraphicBuffer:
java
// Surface.java (应用进程)
public Canvas lockCanvas(Rect inOutDirty) {
// 校验Surface有效性(核心:GBP是否有效)
if (!isValid()) {
throw new IllegalStateException("Surface is invalid");
}
// 调用Native层方法,申请GraphicBuffer并创建Canvas
mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
return mCanvas;
}
mNativeObject:指向C++层android::Surface对象的指针,该对象持有GBP;nativeLockCanvas:JNI方法,对应C++层android_view_Surface_nativeLockCanvas。
第三步:IGraphicBufferProducer
nativeLockCanvas是GBP发挥作用的核心环节,其底层通过GBP的dequeueBuffer申请GraphicBuffer,完成显存分配与映射。
1. JNI映射到Native层Surface(C++)
cpp
// android_view_Surface.cpp (应用进程Native层)
jlong android_view_Surface_nativeLockCanvas(JNIEnv* env, jclass clazz,
jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
// 1. 获取C++层Surface对象(持有GBP)
sp<Surface> surface = reinterpret_cast<Surface*>(nativeObject);
// 2. 定义参数:申请的GraphicBuffer尺寸、格式等
ANativeWindowBuffer* outBuffer;
ARect dirtyRect;
// 3. 核心:通过Surface锁定缓冲区(内部调用GBP.dequeueBuffer)
status_t err = surface->lock(&outBuffer, &dirtyRect);
if (err != NO_ERROR) {
jniThrowException(env, "java/lang/IllegalStateException", "锁定缓冲区失败");
return 0;
}
// 4. 将GraphicBuffer映射到Canvas(Java层Canvas绑定C++层Skia画布)
sp<GraphicBuffer> graphicBuffer = GraphicBuffer::getSelf(outBuffer);
SkCanvas* skCanvas = bindGraphicBufferToCanvas(graphicBuffer, canvasObj);
return reinterpret_cast<jlong>(skCanvas);
}
2. Surface::lock → GBP.dequeueBuffer(C++核心)
C++层Surface::lock最终调用GBP的dequeueBuffer,向SurfaceFlinger申请空闲GraphicBuffer:
cpp
// Surface.cpp (应用进程Native层)
status_t Surface::lock(ANativeWindowBuffer** outBuffer, ARect* outDirtyRect) {
// 1. 初始化参数:请求的缓冲区尺寸、格式、用法
int bufWidth = mRequestedWidth;
int bufHeight = mRequestedHeight;
uint32_t format = mRequestedFormat;
uint32_t usage = GRALLOC_USAGE_SW_WRITE_OFTEN; // CPU可写
// 2. 核心:调用GBP.dequeueBuffer申请空闲缓冲区
int bufIndex; // 缓冲区在BufferQueue中的索引
sp<Fence> fence;
status_t err = mGraphicBufferProducer->dequeueBuffer(
&bufIndex, &fence, bufWidth, bufHeight, format, usage);
if (err != NO_ERROR) {
return err;
}
// 3. 获取申请到的GraphicBuffer
sp<GraphicBuffer> graphicBuffer;
err = mGraphicBufferProducer->requestBuffer(bufIndex, &graphicBuffer);
if (err != NO_ERROR) {
mGraphicBufferProducer->cancelBuffer(bufIndex, fence);
return err;
}
// 4. 映射GraphicBuffer到应用进程虚拟地址空间(CPU可读写)
void* vaddr;
err = graphicBuffer->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, &vaddr);
if (err != NO_ERROR) {
mGraphicBufferProducer->cancelBuffer(bufIndex, fence);
return err;
}
// 5. 保存缓冲区信息,返回给上层
mLockedBuffer = graphicBuffer;
mLockedBufIndex = bufIndex;
*outBuffer = graphicBuffer.get();
*outDirtyRect = mDirtyRect;
return NO_ERROR;
}
关键细节:GBP.dequeueBuffer的核心逻辑
dequeueBuffer是IGraphicBufferProducer的核心接口,由SurfaceFlinger的BufferQueue实现;- 参数说明:
bufIndex:返回空闲缓冲区在BufferQueue中的索引(双缓冲对应0/1,三重缓冲对应0/1/2);bufWidth/bufHeight:应用请求的缓冲区尺寸(匹配View树的最终尺寸);format:像素格式(如RGBA_8888);usage:显存用途(如GRALLOC_USAGE_SW_WRITE_OFTEN表示CPU频繁写入,GRALLOC_USAGE_HW_TEXTURE表示GPU纹理);
- SurfaceFlinger侧:BufferQueue检查空闲缓冲区,若不足则触发
GraphicBufferAllocator分配新的显存(Ashmem共享内存)。
3. GraphicBuffer内存映射(C++)
graphicBuffer->lock调用GraphicBufferMapper将物理显存映射到应用进程虚拟地址空间:
cpp
// GraphicBuffer.cpp
status_t GraphicBuffer::lock(uint32_t usage, void** vaddr) {
return GraphicBufferMapper::getInstance().lock(
mHandle, usage, mLockBounds, vaddr);
}
GraphicBufferMapper:封装底层grallocHAL模块,完成物理显存到虚拟地址的映射;- 映射后,CPU可直接通过
vaddr指针读写显存,无需拷贝(零拷贝核心)。
第四步:onDraw中UI绘制到GraphicBuffer
Canvas绑定GraphicBuffer后,onDraw中的XML UI绘制指令最终转化为显存数据,分为CPU绘制(主流)和GPU绘制两种方式。
1. XML UI的onDraw指令转化(Java层)
每个View的onDraw方法是绘制指令的入口,例如:
TextView.onDraw:绘制文字、背景、下划线;ImageView.onDraw:绘制Bitmap、缩放/裁剪图片;LinearLayout.onDraw:绘制背景、分割线;
这些绘制指令最终调用Canvas的底层方法(如drawRect/drawText/drawBitmap)。
2. CPU绘制:Canvas→Skia→GraphicBuffer(C++核心)
Java层Canvas是C++层Skia画布的封装,绘制指令最终由Skia写入GraphicBuffer:
cpp
// Skia绘制核心逻辑(简化)
void SkCanvas::drawText(const char* text, size_t len, SkScalar x, SkScalar y, const SkPaint& paint) {
// 1. 文字排版(计算字符位置、大小)
SkTextBlob* blob = SkTextBlob::MakeFromText(text, len, paint);
// 2. 将文字绘制到Skia的像素缓冲区(SkBitmap)
this->drawTextBlob(blob, x, y, paint);
// 3. SkBitmap的数据同步到GraphicBuffer的虚拟地址(vaddr)
memcpy(vaddr, skBitmap.getPixels(), skBitmap.getSize());
}
- Skia:Android默认的2D图形渲染引擎,负责将高级绘制指令(如画文字、矩形)转化为像素数据;
- 零拷贝:Skia直接操作GraphicBuffer的虚拟地址,像素数据无需中间拷贝,直接写入显存。
3. GPU绘制(EGL+OpenGL ES)
若开启硬件加速(默认开启),部分绘制指令会交由GPU处理:
-
Java层Surface通过EGL关联为
EGLSurface:cpp// EGL核心逻辑(简化) EGLSurface eglSurface = eglCreateWindowSurface( eglDisplay, eglConfig, surface->getNativeWindow(), NULL); -
OpenGL ES指令直接写入GraphicBuffer(GPU显存):
cppglTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, vaddr);
- GPU绘制效率远高于CPU,适合复杂UI(如动画、渐变);
- GBP的
usage参数需设置为GRALLOC_USAGE_HW_TEXTURE,让GraphicBuffer支持GPU读写。
第五步:缓冲区提交
onDraw完成后,Surface解锁GraphicBuffer并通过GBP提交给SurfaceFlinger,这是"绘制完成"到"合成显示"的关键步骤。
1. Java层unlockCanvasAndPost(应用进程)
java
// Surface.java
public void unlockCanvasAndPost(Canvas canvas) {
// 校验Canvas有效性
checkValidCanvas(canvas);
// 调用Native层方法,解锁并提交缓冲区
nativeUnlockCanvasAndPost(mNativeObject, canvas);
}
2. Native层Surface::unlockAndPost(C++)
cpp
// android_view_Surface.cpp
void android_view_Surface_nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz,
jlong nativeObject, jobject canvasObj) {
sp<Surface> surface = reinterpret_cast<Surface*>(nativeObject);
// 核心:解锁并提交缓冲区
status_t err = surface->unlockAndPost();
if (err != NO_ERROR) {
jniThrowException(env, "java/lang/IllegalStateException", "提交缓冲区失败");
}
}
// Surface.cpp
status_t Surface::unlockAndPost() {
// 1. 解锁GraphicBuffer(解除CPU/GPU写锁定)
status_t err = mLockedBuffer->unlock();
if (err != NO_ERROR) {
return err;
}
// 2. 核心:通过GBP提交缓冲区到SurfaceFlinger
sp<Fence> fence = Fence::NO_FENCE;
err = mGraphicBufferProducer->queueBuffer(
mLockedBufIndex, fence, ANWINDOW_BUFFER_STATE_OK);
if (err != NO_ERROR) {
return err;
}
// 3. 重置锁定状态,准备下一次绘制
mLockedBuffer = nullptr;
mLockedBufIndex = -1;
return NO_ERROR;
}
3. GBP.queueBuffer:通知SurfaceFlinger消费(C++核心)
IGraphicBufferProducer::queueBuffer是提交缓冲区的核心接口:
- 功能:将"已绘制完成"的GraphicBuffer标记为"就绪",并通知SurfaceFlinger的Consumer端;
- 参数:
mLockedBufIndex:提交的缓冲区索引;fence:同步栅栏(确保GPU绘制完成后再提交);ANWINDOW_BUFFER_STATE_OK:标记缓冲区状态正常;
- SurfaceFlinger侧:BufferQueue将该缓冲区从"生产者队列"移到"消费者队列",等待合成。
第六步:SurfaceFlinger合成与屏幕显示
GBP提交的GraphicBuffer最终由SurfaceFlinger合成后输出到屏幕:
- Consumer端接收缓冲区 :SurfaceFlinger的
BufferLayerConsumer监听BufferQueue,通过IGraphicBufferConsumer::acquireBuffer获取提交的GraphicBuffer; - VSync同步:SurfaceFlinger等待屏幕垂直同步信号(VSync),确保合成节奏与屏幕刷新率(60Hz/120Hz)一致;
- 多Surface合成:按Z轴层级(如Activity Surface在状态栏之下、壁纸之上)将所有Surface的GraphicBuffer合成为一帧;
- 输出到屏幕 :合成后的帧数据通过
Display HAL提交给显示控制器,写入屏幕帧缓冲区,最终驱动像素发光显示。
核心C++接口与实现细节
1. IGraphicBufferProducer的核心接口(C++)
cpp
// IGraphicBufferProducer.h (SurfaceFlinger)
class IGraphicBufferProducer : public IInterface {
public:
// 申请空闲缓冲区
virtual status_t dequeueBuffer(int* bufIdx, sp<Fence>* fence,
uint32_t width, uint32_t height, uint32_t format, uint32_t usage) = 0;
// 获取指定索引的GraphicBuffer
virtual status_t requestBuffer(int bufIdx, sp<GraphicBuffer>* buf) = 0;
// 提交绘制完成的缓冲区
virtual status_t queueBuffer(int bufIdx, const QueueBufferInput& input,
QueueBufferOutput* output) = 0;
// 取消已申请的缓冲区
virtual status_t cancelBuffer(int bufIdx, const sp<Fence>& fence) = 0;
// 设置缓冲区数量(双缓冲/三重缓冲)
virtual status_t setBufferCount(int bufferCount) = 0;
};
- 所有接口均为纯虚函数,由SurfaceFlinger的
BufferQueueProducer实现; - 跨进程通信:通过Binder实现,应用进程持有GBP的代理对象,SurfaceFlinger持有本地对象。
2. Native层Surface与GBP的绑定
C++层Surface通过构造函数绑定GBP,生命周期与GBP一致:
cpp
// Surface.cpp
Surface::Surface(const sp<IGraphicBufferProducer>& gbp)
: mGraphicBufferProducer(gbp),
mRequestedWidth(0),
mRequestedHeight(0),
mRequestedFormat(PIXEL_FORMAT_RGBA_8888) {
// 初始化BufferQueue参数
mGraphicBufferProducer->connect(new BufferProducerListener(this),
NATIVE_WINDOW_API_CPU);
}
常见问题
1. onDraw耗时过长导致卡顿的底层原因
onDraw耗时超过一帧时长(如60Hz屏幕的16.6ms),会导致GBP无法及时提交缓冲区;- BufferQueue的缓冲区被占满后,
dequeueBuffer会阻塞,最终导致VSync信号丢失,画面卡顿。
2. 双缓冲如何避免画面撕裂(C++层实现)
- BufferQueue维护两个GraphicBuffer:一个用于应用
onDraw绘制(前台),一个用于SurfaceFlinger合成(后台); queueBuffer触发缓冲区切换,SurfaceFlinger仅在VSync信号到来时切换后台缓冲区为前台,避免绘制中途屏幕刷新。
3. XML UI绘制到显存的零拷贝原理
- GraphicBuffer基于Ashmem匿名共享内存,应用进程和SurfaceFlinger进程共享同一块物理显存;
- CPU/GPU直接写入GraphicBuffer的虚拟地址,SurfaceFlinger合成时无需拷贝数据,直接读取显存。
总结
IGraphicBufferProducer是Surface连接显存与SurfaceFlinger的核心枢纽,onDraw驱动UI绘制的本质是:
- 逻辑层:XML解析为View树,Measure/Layout确定尺寸;
- 显存层:Surface通过GBP申请GraphicBuffer,完成显存映射;
- 绘制层 :
onDraw的UI指令通过Canvas/Skia写入GraphicBuffer; - 提交层:GBP将绘制完成的缓冲区提交给SurfaceFlinger;
- 合成层:SurfaceFlinger合成多Surface并输出到屏幕。
C++层是整个流程的核心实现层:GBP的dequeueBuffer/queueBuffer管理显存分配与提交,GraphicBuffer封装物理显存,Skia负责绘制指令到像素数据的转化。
