【Android FrameWork】延伸阅读:IGraphicBufferProducer驱动UI绘制过程

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);
        }
    }
}
  • performDrawonDraw的总触发入口,所有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的核心逻辑
  • dequeueBufferIGraphicBufferProducer的核心接口,由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:封装底层gralloc HAL模块,完成物理显存到虚拟地址的映射;
  • 映射后,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处理:

  1. Java层Surface通过EGL关联为EGLSurface

    cpp 复制代码
    // EGL核心逻辑(简化)
    EGLSurface eglSurface = eglCreateWindowSurface(
            eglDisplay, eglConfig, surface->getNativeWindow(), NULL);
  2. OpenGL ES指令直接写入GraphicBuffer(GPU显存):

    cpp 复制代码
    glTexSubImage2D(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合成后输出到屏幕:

  1. Consumer端接收缓冲区 :SurfaceFlinger的BufferLayerConsumer监听BufferQueue,通过IGraphicBufferConsumer::acquireBuffer获取提交的GraphicBuffer;
  2. VSync同步:SurfaceFlinger等待屏幕垂直同步信号(VSync),确保合成节奏与屏幕刷新率(60Hz/120Hz)一致;
  3. 多Surface合成:按Z轴层级(如Activity Surface在状态栏之下、壁纸之上)将所有Surface的GraphicBuffer合成为一帧;
  4. 输出到屏幕 :合成后的帧数据通过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绘制的本质是:

  1. 逻辑层:XML解析为View树,Measure/Layout确定尺寸;
  2. 显存层:Surface通过GBP申请GraphicBuffer,完成显存映射;
  3. 绘制层onDraw的UI指令通过Canvas/Skia写入GraphicBuffer;
  4. 提交层:GBP将绘制完成的缓冲区提交给SurfaceFlinger;
  5. 合成层:SurfaceFlinger合成多Surface并输出到屏幕。

C++层是整个流程的核心实现层:GBP的dequeueBuffer/queueBuffer管理显存分配与提交,GraphicBuffer封装物理显存,Skia负责绘制指令到像素数据的转化。

相关推荐
_李小白7 小时前
【Android FrameWork】第二十八天:Activity 的 UI 绘制全过程
android·ui
_李小白8 小时前
【Android FrameWork】第三十天:Surface创建流程解析
android
元亓亓亓8 小时前
考研408--操作系统--day8--操作系统--虚拟内存&请求分页&页面置换/分配
android·java·开发语言·虚拟内存
有位神秘人9 小时前
Android的Compose系列之文本TextView
android
Engineer-Jsp9 小时前
Flutter 开发 Android 原生开发神器 flutter_api_stub
android·flutter
qq_205279059 小时前
unity 像素ui的适配问题
ui
惟恋惜9 小时前
Jetpack Compose 多页面架构实战:从 Splash 到底部导航,每个 Tab 拥有独立 ViewModel
android·ui·架构·android jetpack
ab_dg_dp10 小时前
Android bugreportz 源码分析
android