Android Framework(二十五)Android底层渲染:Surface的底层实现原理

文章内容

Surface 是一块内存吗?从 Java 层的使用方式看,lockCanvas() 像是直接锁定一块画布。底层对象关系却显示,Surface 并不对应一块固定内存。它持有 IGraphicBufferProducer(下文简称 IGBP),也就是 GraphicBuffer 生产者的远程 Binder 对象。

App 进程里的 Surface、Canvas 和 SkBitmap 负责申请缓冲区并写入像素;SurfaceFlinger 进程里的 BufferQueue 接收已完成的缓冲区,随后参与合成。两个进程经由 IGBP 通信,像素数据留在 mmap 共享内存中,Binder 只传 buffer 槽位下标和同步信息。

一、Surface 到底管理着什么

1.1 从“一块内存”的直觉说起

一个 Surface 要连续产生多帧画面,而正在显示的内容不能同时被下一帧覆盖。因此,它通过 IGBP 连接 BufferQueue,由队列管理多个 GraphicBuffer。Surface 保存的是生产端接口,每次绘制再取得当前可写的 buffer。

1.2 为什么必须使用共享内存

RGBA 格式中,每个像素占 4 字节。720 × 1280 的一帧约为 3.5 MB,1080 × 1920 的一帧约为 7.9 MB,都超过 Binder 驱动约 1 MB - 8 KB 的单次传输上限。把整帧像素放进 Binder 既不可行,也会引入多余拷贝。GraphicBuffer 的内存经过 mmap 映射到相关进程,跨进程接口只传递描述信息。

这块内存并不是普通的匿名内存:它由 Gralloc HAL 分配,具体的跨进程共享机制(如 ashmem、dma_buf 等)因平台实现而异。App 进程与 SurfaceFlinger 进程各自对同一块物理内存执行 mmap,把它映射进本进程的地址空间;Binder 在这中间只传递用于定位这块内存的句柄(fd)和槽位下标,不传递像素数据本身。

1.3 申请与提交的接口约定

生产者先通过 dequeueBuffer 申请一个可写槽位,根据返回的下标取得 GraphicBuffer,再将像素写入该 buffer。绘制完成后,queueBuffer 把槽位下标提交给消费端。VSync 到来时,SurfaceFlinger 从 BufferQueue 取出可用内容参与合成。这条链路可以概括为:申请 buffer 槽位、绘制到共享内存、跨进程提交槽位。

需要说明的是,dequeueBuffer/queueBuffer 传递的不只是槽位下标:两者都会附带同步栅栏(fence),用来协调 CPU/GPU 对同一块 buffer 的读写顺序,避免生产者在消费者还未读完时覆写数据。这部分细节会在跨进程提交(第四节)中具体展开。

Surface 与 GraphicBufferQueue 的关系

二、Surface 是怎么创建出来的

2.1 起点:ViewRootImpl.relayoutWindow

ViewRootImpl 在首次布局、窗口尺寸变化或可见性变化时调用 relayout。下面的调用把窗口参数发给 WMS,mSurface 作为输出参数带回服务端创建的结果。

文件:ViewRootImpl.java(relayoutWindow,约第 5415~5422 行)

java
// 发起窗口重布局请求并接收返回码int relayoutResult = mWindowSession.relayout(        // 传入窗口、序列号和布局参数        mWindow, mSeq, params,        // 换算应用缩放后的窗口宽度        (int) (mView.getMeasuredWidth() * appScale + 0.5f),        // 换算应用缩放后的窗口高度        (int) (mView.getMeasuredHeight() * appScale + 0.5f),        // 传入可见性和 Insets 标志        viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,        // 接收窗口边界和外围 Insets        mWinFrame, mPendingOverscanInsets, mPendingContentInsets,        // 接收可见区、稳定区与外侧区域        mPendingVisibleInsets, mPendingStableInsets, mPendingOutsets, mPendingConfiguration, mSurface);

输入是窗口尺寸、可见性和 Insets 状态;返回值记录 relayout 结果,mSurface 则被服务端结果更新。

2.2 WMS.relayoutWindow 创建 SurfaceControl

WMS 一侧由 WindowStateAnimator 创建 SurfaceControl,然后把它拷入 outSurface。创建失败时,输出 Surface 会被释放。

文件:WindowManagerService.java(relayoutWindow)

java
// 请求窗口动画器创建 SurfaceControlSurfaceControl surfaceControl = winAnimator.createSurfaceLocked();// 判断底层控制对象是否创建成功if (surfaceControl != null) {    // 把 SurfaceControl 对应的底层对象复制到输出 Surface    outSurface.copyFrom(surfaceControl);    // 开启交易日志时记录输出 Surface    if (SHOW_TRANSACTIONS) Slog.i(TAG, "OUT SURFACE " + outSurface + ": copied");// 处理未创建出 SurfaceControl 的分支} else {    // 释放无效的输出 Surface    outSurface.release();// 结束创建结果分支}

输入是当前窗口的 winAnimator;输出是被填充或释放的 outSurface。状态变化发生在 Surface 的 native 对象上。

2.3 Surface.copyFrom 替换 native 对象

copyFrom 验证 SurfaceControl 及其 native 指针,调用 nativeCreateFromSurfaceControl 创建新对象,再在锁内释放旧值并完成替换。

文件:Surface.java(copyFrom)

java
// 定义从 SurfaceControl 更新 Surface 的方法public void copyFrom(SurfaceControl other) {    // 拒绝空的 SurfaceControl 参数    if (other == null) {        // 报告调用参数错误        throw new IllegalArgumentException("other must not be null");    // 结束空参数检查    }    // 读取 SurfaceControl 的 native 对象指针    long surfaceControlPtr = other.mNativeObject;    // 拒绝已释放的 SurfaceControl    if (surfaceControlPtr == 0) {        // 报告 native 对象已失效        throw new NullPointerException("SurfaceControl native object is null. Are you using a released SurfaceControl?");    // 结束 native 指针检查    }    // 根据 SurfaceControl 创建新的 Surface native 对象    long newNativeObject = nativeCreateFromSurfaceControl(surfaceControlPtr);    // 锁定 Surface 对象状态    synchronized (mLock) {        // 判断 Surface 是否已持有 native 对象        if (mNativeObject != 0) {            // 释放原有 native 对象            nativeRelease(mNativeObject);        // 结束旧对象检查        }        // 将 Surface 指向新 native 对象        setNativeObjectLocked(newNativeObject);    // 结束同步区域    }// 结束 copyFrom 方法}

输入是有效的 SurfaceControl,输出通过 mNativeObject 的状态变化体现:旧对象被释放,Surface 转而持有新对象。

2.4 SurfaceControl 进入 native 创建流程

Java 层构造 SurfaceControl 时会进入以下 native 方法。方法返回值是 native 对象指针。

文件:SurfaceControl.java

java
// 声明创建 native SurfaceControl 的 JNI 方法private static native long nativeCreate(SurfaceSession session, String name,        // 传入尺寸、像素格式和标志        int w, int h, int format, int flags) throws OutOfResourcesException;

输入包括 SurfaceSession、名称、尺寸、格式和标志;输出是一个 long 类型的 native 指针,资源不足时抛出异常。

2.5 android_view_SurfaceControl.cpp 调用 client.createSurface

JNI 层取得 SurfaceComposerClient 后,把名称、尺寸、格式和标志交给 createSurface。

注意这里标注的是 JNI 层文件:nativeCreate 这个 JNI glue 函数位于 frameworks/base/core/jni/android_view_SurfaceControl.cpp,而 2.7 节引用的 frameworks/native/libs/gui/SurfaceControl.cpp 是 native C++ 类实现文件,getSurface() 等方法在其中。两个文件名字接近容易混淆,一个是 JNI 桥,一个是 native 类实现。

文件:android_view_SurfaceControl.cpp(nativeCreate)

cpp
// 定义 SurfaceControl 的 JNI 创建入口static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,        // 接收 Java 层传入的名称、尺寸、格式和标志        jstring name, jint w, jint h, jint format, jint flags) {    // 取得 SurfaceComposerClient,具体过程待核实    SurfaceComposerClient* client = ...;    // 通过客户端创建 SurfaceControl    sp<SurfaceControl> surface = client->createSurface(String8(name), w, h, format, flags);    // 返回有效对象指针,创建失败时返回零    return surface.get() != NULL ? surface->getObject() : 0;// 结束 JNI 创建方法}

输入沿用 Java 层的创建参数;输出是 SurfaceControl 对象指针或 0。这一步把创建工作交给 SurfaceComposerClient。

2.6 createSurface 同时取回 handle 和 gbp

SurfaceComposerClient::createSurface 展示了 IGBP 的来源:mClient->createSurface 通过输出参数返回 handle 和 gbp,两者一起交给 SurfaceControl 构造函数。

文件:SurfaceComposerClient.cpp(createSurface)

cpp
// 定义创建 SurfaceControl 的客户端方法sp<SurfaceControl> SurfaceComposerClient::createSurface(        // 接收 Surface 的名称、尺寸、格式和标志        const String8& name, uint32_t w, uint32_t h, PixelFormat format, uint32_t flags) {    // 声明待返回的 SurfaceControl    sp<SurfaceControl> sur;    // 只在客户端状态正常时继续创建    if (mStatus == NO_ERROR) {        // 声明 Surface 的 Binder handle        sp<IBinder> handle;        // 声明 GraphicBuffer 生产者接口        sp<IGraphicBufferProducer> gbp;        // 请求服务端创建 Surface 并填充两个输出参数        status_t err = mClient->createSurface(name, w, h, format, flags, &handle, &gbp);        // 创建出错时记录日志        ALOGE_IF(err, "SurfaceComposerClient::createSurface error %s", strerror(-err));        // 只在服务端返回成功时构造 SurfaceControl        if (err == NO_ERROR) {            // 将 handle 和 gbp 保存到 SurfaceControl            sur = new SurfaceControl(this, handle, gbp);        // 结束错误码检查        }    // 结束客户端状态检查    }    // 返回创建结果    return sur;// 结束 createSurface 方法}

输入是 Surface 的描述参数;服务端输出 handle 和 gbp,客户端状态变为持有这两个对象的 SurfaceControl。

2.7 SurfaceControl.getSurface 构造 Surface

getSurface() 中的构造参数直接回答了 Surface 底层持有什么:mGraphicBufferProducer。false 表示该 Surface 始终由 SurfaceFlinger 消费。

文件:SurfaceControl.cpp(getSurface)

cpp
// 定义获取 Surface 的方法sp<Surface> SurfaceControl::getSurface() const {    // 锁定 SurfaceControl 内部状态    Mutex::Autolock _l(mLock);    // 在尚未创建 Surface 时执行初始化    if (mSurfaceData == 0) {        // 使用 IGBP 构造由 SurfaceFlinger 消费的 Surface        mSurfaceData = new Surface(mGraphicBufferProducer, false);    // 结束惰性初始化检查    }    // 返回缓存的 Surface    return mSurfaceData;// 结束 getSurface 方法}

输入来自 SurfaceControl 保存的 IGBP;输出是惰性创建的 Surface,mSurfaceData 从空值变为该对象。

2.8 链路创建

relayoutWindow 把 WMS 创建的 SurfaceControl 带回 App 进程,copyFrom 建立 Surface 的 native 对象,而 createSurface 返回的 gbp 最终进入 new Surface(mGraphicBufferProducer, false)。这条调用链没有为 Surface 绑定一块永久不变的像素内存;它建立的是通往 GraphicBuffer 队列生产端的通道。

Surface 的创建调用链

三、Canvas 绘制原理

3.1 drawSoftware 从 lockCanvas 取得画布

软件绘制路径中,ViewRootImpl.drawSoftware 先从 Surface 锁定 Canvas,再执行 mView.draw(canvas),完成后调用 unlockCanvasAndPost。其入口是下面这一行。

文件:ViewRootImpl.java(drawSoftware)

java
// 根据脏区域从 Surface 获取可绘制的 Canvascanvas = mSurface.lockCanvas(dirty);

输入 dirty 限定待更新区域,输出 canvas 承载本次绘制上下文。此时 Surface 进入已锁定状态。

3.2 Surface.lockCanvas 进入 native 层

lockCanvas 防止同一 Surface 重复加锁,然后把 native Surface、Canvas 和脏区域交给 nativeLockCanvas。

文件:Surface.java(lockCanvas)

java
// 定义软件 Canvas 锁定方法public Canvas lockCanvas(Rect inOutDirty) throws Surface.OutOfResourcesException, IllegalArgumentException {    // 锁定 Surface 内部状态    synchronized (mLock) {        // 检查 Surface 是否已释放        checkNotReleasedLocked();        // 拒绝对同一 Surface 重复加锁        if (mLockedObject != 0) {            // 报告 Surface 已处于锁定状态            throw new IllegalStateException("Surface was already locked");        // 结束重复加锁检查        }        // 调用 native 层锁定 buffer 并保存锁定对象        mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);        // 返回与 buffer 关联的 Canvas        return mCanvas;    // 结束同步区域    }// 结束 lockCanvas 方法}

输入是脏区域,输出是 mCanvas;mLockedObject 从未锁定值变为 native 层返回的锁定对象。

unlockCanvasAndPost 根据 mHwuiContext 在硬件和软件路径之间选择。软件分支的 unlockSwCanvasAndPost 最终进入 nativeUnlockCanvasAndPost。

文件:Surface.java(unlockCanvasAndPost)

java
// 定义解锁并提交 Canvas 的入口public void unlockCanvasAndPost(Canvas canvas) {    // 锁定 Surface 内部状态    synchronized (mLock) {        // 检查 Surface 是否已释放        checkNotReleasedLocked();        // 判断是否存在硬件绘制上下文        if (mHwuiContext != null) {            // 由硬件绘制上下文解锁并提交            mHwuiContext.unlockAndPost(canvas);        // 在没有硬件绘制上下文时走软件分支        } else {            // 解锁软件 Canvas 并提交 buffer            unlockSwCanvasAndPost(canvas);        // 结束绘制分支        }    // 结束同步区域    }// 结束 unlockCanvasAndPost 方法}

输入是已完成绘制的 Canvas;方法没有返回值,它使当前绘制结果进入解锁和提交阶段。

3.3 nativeLockCanvas 取得 ANativeWindow_Buffer

JNI 层把 Surface 锁定结果写入 ANativeWindow_Buffer,随后用它的宽高、格式和 stride 构造 Skia 位图信息。最后一步 bitmap.setPixels(outBuffer.bits) 把 SkBitmap 指向真正的像素地址。

文件:android_view_Surface.cpp(nativeLockCanvas)

cpp
// 声明接收可绘制 buffer 描述的结构ANativeWindow_Buffer outBuffer;// 锁定 Surface 并填充 buffer 描述status_t err = surface->lock(&outBuffer, dirtyRectPtr);// 检查 Surface 锁定是否失败if (err < 0) {    // 根据错误码选择 Java 异常类型    const char* const exception = (err == NO_MEMORY) ?        // 区分内存不足与参数错误        "java/lang/OutOfResourcesException" : "java/lang/IllegalArgumentException";    // 向 Java 层抛出选定的异常    jniThrowException(env, exception, NULL);    // 在失败时返回空指针值    return 0;// 结束锁定错误检查}// 根据 buffer 尺寸和像素格式创建 Skia 信息SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height,        // 转换像素格式并使用预乘 Alpha 类型        convertPixelFormat(outBuffer.format), kPremul_SkAlphaType);// 检查 buffer 是否使用 RGBX_8888 格式if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) {    // 把 RGBX_8888 的 Alpha 类型设为不透明    info.fAlphaType = kOpaque_SkAlphaType;// 结束像素格式检查}// 创建用于 Canvas 绘制的 SkBitmapSkBitmap bitmap;// 按 stride 和每像素字节数计算行字节数ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);// 设置位图格式和行字节数bitmap.setInfo(info, bpr);// 只为非空尺寸的 buffer 设置像素地址if (outBuffer.width > 0 && outBuffer.height > 0) {    // 将 SkBitmap 的像素指针设为 buffer 内存地址    bitmap.setPixels(outBuffer.bits);// 结束 buffer 尺寸检查}

输入是 native Surface 和脏区域;输出的 outBuffer 含有内存描述,SkBitmap 的像素指针状态随后指向 outBuffer.bits。

3.4 ANativeWindow_Buffer

ANativeWindow_Buffer 本身是一组描述字段。bits 才是可写像素内存地址,stride 表示一行像素在内存中的跨度,它可能大于画面宽度。

文件:ANativeWindow_Buffer 结构声明

c
// 定义 native 窗口 buffer 描述结构typedef struct ANativeWindow_Buffer {    // 记录水平方向的像素数    int32_t width;    // 记录垂直方向的像素数    int32_t height;    // 记录每行像素的内存跨度    int32_t stride;    // 记录 buffer 的像素格式    int32_t format;    // 保存实际像素内存地址    void* bits;// 结束结构声明并定义类型别名} ANativeWindow_Buffer;

该结构的输入来自 Surface::lock;它将尺寸、布局和像素地址输出给 JNI 层,不另外持有一份像素副本。

3.5 Surface::lock 申请 buffer 并映射地址

Surface::lock 先检查锁定状态并连接 CPU 绘制 API,再经由 dequeueBuffer 取得后备 buffer。lockAsync 返回的 vaddr 被写入 outBuffer->bits,从而连接 GraphicBuffer 共享内存与上层 SkBitmap。

文件:Surface.cpp(lock)

cpp
// 定义 CPU 绘制路径的 Surface 锁定方法status_t Surface::lock(ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds) {    // 拒绝已锁定 Surface 的再次锁定    if (mLockedBuffer != 0) {        // 记录重复锁定错误        ALOGE("Surface::lock failed, already locked");        // 返回无效操作错误        return INVALID_OPERATION;    // 结束重复锁定检查    }    // 检查 Surface 是否已连接 CPU API    if (!mConnectedToCpu) {        // 建立 CPU 绘制连接        int err = Surface::connect(NATIVE_WINDOW_API_CPU);        // 连接失败时直接返回错误        if (err) return err;    // 结束 CPU 连接检查    }    // 声明待申请的 native buffer    ANativeWindowBuffer* out;    // 初始化同步栅栏文件描述符    int fenceFd = -1;    // 从 BufferQueue 申请一个可写 buffer    status_t err = dequeueBuffer(&out, &fenceFd);    // 申请失败时记录错误    ALOGE_IF(err, "dequeueBuffer failed (%s)", strerror(-err));    // 将 native buffer 转换为 GraphicBuffer 强引用    sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out));    // 根据后备 buffer 尺寸建立边界    const Rect bounds(backBuffer->width, backBuffer->height);    // 声明新的脏区域    Region newDirtyRegion;    // 保留关键帧中省略的脏区域处理    ...    // 声明接收共享内存地址的指针    void* vaddr;    // 按 CPU 读写用途锁定 GraphicBuffer    status_t res = backBuffer->lockAsync(            // 传入读写标志、脏区域、地址输出参数和栅栏            GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,            // 完成 lockAsync 调用并接收 vaddr            newDirtyRegion.bounds(), &vaddr, fenceFd);    // 映射失败时返回无效操作错误    if (res != 0) return INVALID_OPERATION;    // 记录当前已锁定的后备 buffer    mLockedBuffer = backBuffer;    // 输出 buffer 宽度    outBuffer->width = backBuffer->width;    // 输出 buffer 高度    outBuffer->height = backBuffer->height;    // 输出 buffer 行跨度    outBuffer->stride = backBuffer->stride;    // 输出 buffer 像素格式    outBuffer->format = backBuffer->format;    // 输出 GraphicBuffer 映射后的共享内存地址    outBuffer->bits = vaddr;    // 返回 buffer 申请阶段的状态    return err;// 结束 Surface 锁定方法}

输入是待填充的 outBuffer 和脏区域;输出包括宽高、stride、格式和 bits。mLockedBuffer 记录本次取得的后备 buffer,等待绘制和提交。

3.6 Canvas 最终画到 GraphicBuffer

Canvas 的软件绘制落在 SkBitmap 上,SkBitmap 的像素指针来自 ANativeWindow_Buffer.bits,而 bits 又来自 GraphicBuffer::lockAsync 返回的 vaddr。所以 Canvas 没有自己管理一块独立像素内存,它绘制到 Surface 本次申请的 GraphicBuffer 中。脱离 Surface 所提供的有效像素目标而直接 new Canvas,无法完成这条绘制链路,因而不会产生可提交的画面,或在相关调用中报错。

每次 lockCanvas 锁定的只是本轮 dequeueBuffer 取得的那一块 back buffer,而不是 Surface 本身;Surface 全程持有的只是访问 BufferQueue 生产端的 IGBP 接口,这与第一节「Surface 不代表一块固定内存」的结论一致。

内部实现对应的还有一组 NDK 公开 API(android/native_window.h),ANativeWindow_lock 和 ANativeWindow_unlockAndPost 的语义分别对应内部的 Surface::lock 和 Surface::unlockAndPost。把两者对照起来看,能更清楚上面这条链路对外暴露成什么样。

c
// 头文件:android/native_window.h(NDK 公开 API,供对照参考) // 锁定 window 的下一块可写 buffer,语义对应内部 Surface::lock// window:目标 native window// outBuffer:输出参数,返回 width/height/stride/format/bits 等 buffer 描述// inOutDirtyBounds:传入/传出脏区域int32_t ANativeWindow_lock(        ANativeWindow* window,        ANativeWindow_Buffer* outBuffer,        ARect* inOutDirtyBounds); // 解锁当前 buffer 并提交给消费端,语义对应内部 Surface::unlockAndPostint32_t ANativeWindow_unlockAndPost(ANativeWindow* window);

Canvas 的像素最终画到哪

四、提交与跨进程传递

4.1 unlockCanvasAndPost 收束绘制

Java 层的 unlockCanvasAndPost 已在 3.2 展示:硬件分支调用 mHwuiContext.unlockAndPost,软件分支调用 unlockSwCanvasAndPost,后者进入 nativeUnlockCanvasAndPost。两条路径的共同目标是结束当前绘制,使已完成的 buffer 可以提交。

4.2 Surface::unlockAndPost 解锁并入队

native 层先调用 unlockAsync 结束 CPU 对 GraphicBuffer 的访问,获取同步栅栏;然后把已解锁 buffer 交给 queueBuffer。

文件:Surface.cpp(unlockAndPost)

cpp
// 定义解锁并提交当前 buffer 的方法status_t Surface::unlockAndPost() {    // 拒绝在没有锁定 buffer 时提交    if (mLockedBuffer == 0) {        // 记录缺少锁定 buffer 的错误        ALOGE("Surface::unlockAndPost failed, no locked buffer");        // 返回无效操作错误        return INVALID_OPERATION;    // 结束锁定 buffer 检查    }    // 初始化接收同步栅栏的文件描述符    int fd = -1;    // 解锁 GraphicBuffer 并取得同步栅栏    status_t err = mLockedBuffer->unlockAsync(&fd);    // 解锁失败时记录错误    ALOGE_IF(err, "failed unlocking buffer (%p)", mLockedBuffer->handle);    // 将已解锁 buffer 交给队列    err = queueBuffer(mLockedBuffer.get(), fd);    // 入队失败时记录错误    ALOGE_IF(err, "queueBuffer (handle=%p) failed (%s)", mLockedBuffer->handle, strerror(-err));    // 记录最近一次提交的 buffer    mPostedBuffer = mLockedBuffer;    // 清除当前锁定 buffer 引用    mLockedBuffer.clear();    // 返回提交结果    return err;// 结束解锁与提交方法}

输入是 mLockedBuffer 和解锁后的栅栏;输出是 queueBuffer 的状态码。对象状态从“已锁定”变为“已提交”,mLockedBuffer 随后被清空。

4.3 Surface::queueBuffer 经 IGBP 跨进程提交

queueBuffer 不传整块像素。它通过 getSlotFromBufferLocked 找到 buffer 对应的槽位下标 i,构造带时间戳、裁剪、变换和栅栏信息的 QueueBufferInput,再调用远程 IGBP 接口。关键帧中部分成员识别不清,以【待核实】的省略形式保留;其中 crop 通常来自 Surface 当前设置的裁剪区域,未显式设置时默认为整块 buffer 尺寸,具体赋值路径关键帧未展开,标【待核实】。

文件:Surface.cpp(queueBuffer)

cpp
// 定义将已绘制 buffer 提交到队列的方法int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) {    // 记录本次队列操作的跟踪信息    ATRACE_CALL();    // 锁定 Surface 队列状态    Mutex::Autolock lock(mMutex);    // 声明本次提交的时间戳    int64_t timestamp;    // 默认本次时间戳由外部给定    bool isAutoTimestamp = false;    // 检查是否应自动生成时间戳    if (mTimestamp == NATIVE_WINDOW_TIMESTAMP_AUTO) {        // 取单调时钟作为提交时间        timestamp = systemTime(SYSTEM_TIME_MONOTONIC);        // 标记时间戳为自动生成        isAutoTimestamp = true;    // 处理调用方已设定时间戳的分支    } else {        // 使用 Surface 中保存的时间戳        timestamp = mTimestamp;    // 结束时间戳分支    }    // 查找 buffer 对应的队列槽位下标    int i = getSlotFromBufferLocked(buffer);    // 槽位查找失败时返回错误    if (i < 0) return i;    // 对有效栅栏文件描述符执行关闭操作    if (fenceFd >= 0) close(fenceFd);    // 根据栅栏描述符构造 Fence 对象    sp<Fence> fence(fenceFd >= 0 ? new Fence(fenceFd) : Fence::NO_FENCE);    // 声明接收生产者返回信息的对象    IGraphicBufferProducer::QueueBufferOutput output;    // 构造包含时间戳、裁剪、变换和栅栏的提交参数    IGraphicBufferProducer::QueueBufferInput input(timestamp, isAutoTimestamp,            // 传入数据空间及待核实的裁剪等成员            mDataSpace, crop, mScalingMode, mTransform, mStickyTransform,            // 传入交换模式、栅栏及粘滞变换信息            mSwapIntervalZero, fence, mStickyTransform);    // 经 IGBP 远程接口提交槽位下标和同步信息    return mGraphicBufferProducer->queueBuffer(i, input, &output);// 结束 buffer 提交方法}

输入是 buffer 指针和栅栏文件描述符;输出是 IGBP queueBuffer 的状态码。跨进程传输的主体是槽位下标 i、QueueBufferInput 和返回信息,像素仍在共享内存里。

4.4 双缓冲与三缓冲

lock 取得的是可写后备 buffer,post 后它进入待消费状态,已提交的内容则可视为前台 buffer。为什么至少需要两块 buffer?当一块 buffer 正在被 SurfaceFlinger 读取用于合成、显示时,生产者不能同时向同一块 buffer 写入,否则会出现画面撕裂或内容错乱,因此至少需要两块 buffer 轮流使用——一块用于当前绘制(back buffer),一块用于当前显示(front buffer)。三缓冲则在此基础上再增加一块可周转的 buffer:当生产者绘制完一帧、消费者还没来得及释放上一块 buffer 时,生产者仍有第三块空闲 buffer 可以继续绘制,从而减少因等待 buffer 释放造成的卡顿(jank)。反映到接口上,dequeueBuffer 在没有空闲 buffer 时会阻塞等待,这也是为什么 BufferQueue 的 buffer 数量会直接影响绘制流畅度。SurfaceFlinger 根据 VSync 节奏从 BufferQueue 取得可合成的内容。

queueBuffer 跨进程提交

五、小结

5.1 从点击按钮到屏幕

硬件输入被系统转换成事件并送达应用后,View 状态变化会触发新一轮测量、布局或绘制。ViewRootImpl 调度 View 层级绘制,软件路径通过 Surface.lockCanvas 申请 GraphicBuffer,Canvas 把像素写入其共享内存。unlockAndPost 解锁 buffer,queueBuffer 经 IGBP 把槽位下标提交给 SurfaceFlinger 一侧的 BufferQueue。VSync 到来后,SurfaceFlinger 取得已提交内容并完成合成显示。

5.2 Android 源码链路中的渲染位置

从系统启动、应用安装、四大组件到事件机制,各环节最终都汇聚到用户可见的画面上。渲染部分可按 SurfaceFlinger 启动、VSync 刷新和 Surface 底层对象三个角度理解:前两者解释消费端何时工作,Surface 这条链路解释 App 如何生产并交付一帧内容。

从点击按钮到屏幕

Surface 底层持有 IGBP 远程 Binder 对象,并由它访问 GraphicBufferQueue;Surface 本身不代表某一块固定内存。Canvas 绘制的像素经 SkBitmap -> ANativeWindow_Buffer.bits -> GraphicBuffer 写入 mmap 共享内存,然后由 queueBuffer 把 buffer 槽位下标和同步信息跨进程交给 SurfaceFlinger。申请槽位、写入共享内存、提交槽位,构成了一帧画面从 App 到屏幕的主干路径。


本文由 AgentWorkFlow 生成

觉得不错?给作者点个赞吧

分类:Android系统

评论 (0)

无需注册,留言经站长审核后公开。

加载评论中…

Android Framework(二十五)Android底层渲染:Surface的底层实现原理 · Yinchen 的博客