Android 16 显示系统 | 从View 到屏幕系列 - 5 | App 内部绘制

写在前面

前面了解 BLASTBufferQueueSurfaceGraphicBuffer 这几个基本的组件之后,这一章主要是了解 App 是如何把 UI 绘制到图形缓冲区 GraphicBufferApp 的绘制流程本质上就是把 UI 绘制到一块 GraphicBuffer,然后把这一块 GraphicBuffer 插入到 BLASTBufferQueue 就结束了。后面的流程就交给 BLASTBufferQueue 和 SurfaceFlinger 来完成了。

App 绘制的起点在 ViewRootImpl.performTraversals,当 SurfaceFlinger 收到 Vsync 信号之后,会分发给 ChoreographicChoreographic 会执行 doFrame 最后会调用到 ViewRootImpl.doTarversal

markdown 复制代码
Choreographic.doFrame
       |
   ViewRootImpl.doTarversal 
          |
     ViewRootImpl.performTraversals

ViewRootImpl.performTraversals 中会执行 View 绘制的三大流程

scss 复制代码
private void performTraversals() {
    ...
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
    performLayout(lp, mWidth, mHeight);
    ...
    perforDraw()
    ...
}

其中 UI 的绘制发生在 perforDraw,在 perforDraw 中会调用 draw

less 复制代码
    private boolean draw(boolean fullRedrawNeeded, @Nullable SurfaceSyncGroup activeSyncGroup,
            boolean syncBuffer) {
        Surface surface = mSurface;
        ...
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
        }
    }

UI的绘制可以通过软件绘制和硬件绘制,虽然软件绘制和硬件绘制有所区别,但是对于 SurfaceGraphicBuffer 来说,大致流程是一样的,我们以软件绘制为例

java 复制代码
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {

        // 定义了一个 Canva
        final Canvas canvas;

        try {
            // 初始化 Canvas,从 BLASTBufferQueue 中申请一块 GraphicBuffer
            canvas = mSurface.lockCanvas(dirty);
            canvas.setDensity(mDensity);
        } 
        ...
        try {
            ...
            // 把 canvas 提交给 View,然后在 View 的 onDraw 中通过 Canvas 绘制
            mView.draw(canvas);
        } finally {
            try {
                // 绘制完成,提交绘制结果到 BLASTBufferQueue
                surface.unlockCanvasAndPost(canvas);
            }
        }
        return true;
    }

APP UI 的软件绘制,可以分为三步:

  1. mSurface.lockCanvas(dirty) 申请一块内存--GraphicBuffer。 在前面我们了解到,此时 mSurface 已经初始化完成,内部持有 IGraphicBufferProducer 作为 BLASTBufferQueue 的生产者,所以可以通过 mSurface 来申请一块 GraphicBuffer

  2. mView.draw(canvas) 通过渲染引擎 OpenGL/Skia 进行绘制,把 View 绘制到第一步所申请的 GraphicBuffer 上。

  3. surface.unlockCanvasAndPost(canvas) 把绘制的结果 GraphicBuffer 插入到 BLASTBufferQueue 中,再由 BLASTBufferQueue 把结果提交到 SurfaceFlinger 进行合成提交到屏幕显示。

我们接下来一步一步从源码来分析:

Surface.lockCanvas--申请 Buffer

arduino 复制代码
// frameworks/base/core/java/android/view/Surface.java
    public Canvas lockCanvas(Rect inOutDirty)
            throws Surface.OutOfResourcesException, IllegalArgumentException {
        synchronized (mLock) {
            ...
            // nativeLockCanvas 是一个 native 方法
            mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
            return mCanvas;
        }
    }
    
// frameworks/base/core/jni/android_view_Surface.cpp
static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));

    if (!ACanvas_isSupportedPixelFormat(ANativeWindow_getFormat(surface.get()))) {


    Rect dirtyRect(Rect::EMPTY_RECT);
    Rect* dirtyRectPtr = NULL;

    ANativeWindow_Buffer buffer;
    status_t err = surface->lock(&buffer, dirtyRectPtr);
    if (err < 0) {
        const char* const exception = (err == NO_MEMORY) ?
                OutOfResourcesException : IllegalArgumentException;
        jniThrowException(env, exception, NULL);
        return 0;
    }

    graphics::Canvas canvas(env, canvasObj);
    canvas.setBuffer(&buffer, static_cast<int32_t>(surface->getBuffersDataSpace()));

    sp<Surface> lockedSurface(surface);
    lockedSurface->incStrong(&sRefBaseOwner);
    return (jlong) lockedSurface.get();
}

nativeLockCanvas 中主要就是通过 surface->lock 来申请一块 buffer, 然后通过 canvas.setBuffer 把这一块 buffer 交给 canvas,接下来看 surface->lock 是如何实现的

arduino 复制代码
// frameworks/native/libs/gui/Surface.cpp
status_t Surface::lock(
        ANativeWindow_Buffer* outBuffer, ARect* inOutDirtyBounds)
{
    // out 作为返回的 Buffer
    ANativeWindowBuffer* out;
    int fenceFd = -1;
    // 通过 dequeueBuffer 从 BLASTBufferQueue dequeue 一块 GraphicBuffer
    status_t err = dequeueBuffer(&out, &fenceFd);
    ...
    return err;
}

Surface::lock 中会调用 dequeueBuffer,在之前介绍 Surface 的文章中提到, dequeueBufferSurface 的核心方法

arduino 复制代码
int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {   // 从 BLASTBufferQueue 中获取一块 GraphicBuffer
    status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, dqInput.width,
                                                            dqInput.height, dqInput.format,
                                                            dqInput.usage, &mBufferAge,
                                                            dqInput.getTimestamps ?
                                                                    &frameTimestamps : nullptr);
    
    sp<GraphicBuffer>& gbuf(mSlots[buf].buffer);

    // 返回一块 GraphicBuffer
    *buffer = gbuf.get();

    return OK;
}

可以看到 Surface::dequeueBuffer 主要实现就是调用 mGraphicBufferProducer->dequeueBuffer, 前面的文章提到过,在 Surface 初始化的时候,BLASTBufferQueue 把内部的 Producer 接口赋给了 Surface 的成员变量 mGraphicBufferProducer, 所以 Surface.mGraphicBufferProducer 可以从 BLASTBufferQueuedequeue 一块 GraphicBuffer 并返回给 Canvas

View.draw(canvas)

Canvas 拿到了 GraphicBuffer 之后,就可以通过 Skia/OpenGL 来进行绘制了。View.draw 会调用 View 本身的 onDraw 方法,比如

scss 复制代码
protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mPaint);
}

canvas.drawRect 最终会调用 SkiaCanvas::drawRect,这里的渲染就跟 Skia 相关了,关于渲染的一些第三方 API,包括 Skia和 OpenGL 后续在了解,这里先放一放。我们只需要通过渲染引擎能把 UI 绘制到 GraphicBuffer 上。

surface.unlockCanvasAndPost(canvas) 提交 Buffer

scss 复制代码
// frameworks/base/core/java/android/view/Surface.java
    public void unlockCanvasAndPost(Canvas canvas) {
       ...
       unlockSwCanvasAndPost(canvas);
       ...
    }

    private void unlockSwCanvasAndPost(Canvas canvas) {
        try {
            nativeUnlockCanvasAndPost(mLockedObject, canvas);
        } finally {
            nativeRelease(mLockedObject);
            mLockedObject = 0;
        }
    }

Java 层的 unlockSwCanvasAndPost 最终会调用 native 的 nativeUnlockCanvasAndPost 方法

arduino 复制代码
// frameworks/base/core/jni/android_view_Surface.cpp
static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
    // detach the canvas from the surface
    graphics::Canvas canvas(env, canvasObj);
    canvas.setBuffer(nullptr, ADATASPACE_UNKNOWN);

    // unlock surface
    status_t err = surface->unlockAndPost();
}
  1. detach 了 CanvasSurface
  2. 执行 surface->unlockAndPost
ini 复制代码
// frameworks/native/libs/gui/Surface.cpp
status_t Surface::unlockAndPost()
{

    int fd = -1;
    status_t err = mLockedBuffer->unlockAsync(&fd);
    ALOGE_IF(err, "failed unlocking buffer (%p)", mLockedBuffer->handle);
    ...
    err = queueBuffer(mLockedBuffer.get(), fd);
    return err;
}

unlockAndPost 最终会调用 SurfacequeueBuffer 方法,这个也是 Surface 的核心方法之一,dequeueBuffer 负责从 BLASTBufferQueue 中取 Buffer,而 queueBuffer 是把 Buffer 插入 BLASTBufferQueue 中。从上述 dequeueBuffer 的实现来看,queueBuffer 必然也会调用 mGraphicBufferProducerqueueBuffer 方法。

arduino 复制代码
// frameworks/native/libs/gui/Surface.cpp
int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd,
                         SurfaceQueueBufferOutput* surfaceOutput) {
    ...
    //    Surface::queueBuffer
    // -> IConsumerListener::onFrameAvailable callback triggers automatically
    // ->   implementation calls IGraphicBufferConsumer::acquire/release immediately
    // -> SurfaceListener::onBufferRelesed callback triggers automatically
    // ->   implementation calls Surface::dequeueBuffer
    status_t err = mGraphicBufferProducer->queueBuffer(slot, input, &output);
}

到这里为止,App 的 View 就已经成功绘制到 Buffer, 并且通过 SurfacemGraphicBufferProducer->queueBuffer 成功把 Buffer 提交到了 BLASTBufferQueue 中,那么接下来的工作就是 BLASTBufferQueueComsumer 进行acquire Buffer,然后提交给 SurfaceFlinger 做进一步的合成然后提交到 Display 进行显示,这一块的内容我们后面单独分析。

相关推荐
九鼎创展科技几秒前
直播一体机技术方案解析:基于RK3588S的硬件架构特性
android·嵌入式硬件·硬件架构
峥嵘life1 小时前
Android14 锁屏密码修改为至少6位
android·安全
2501_915918419 小时前
iOS WebView 调试实战 localStorage 与 sessionStorage 同步问题全流程排查
android·ios·小程序·https·uni-app·iphone·webview
Digitally10 小时前
如何永久删除安卓设备中的照片(已验证)
android·gitee
hmywillstronger11 小时前
【Settlement】P1:整理GH中的矩形GRID角点到EXCEL中
android·excel
lvronglee11 小时前
如何编译RustDesk(Unbuntu 和Android版本)
android·rustdesk
byadom_IT12 小时前
【Android】Popup menu:弹出式菜单
android
pk_xz12345613 小时前
基于WebSockets和OpenCV的安卓眼镜视频流GPU硬解码实现
android·人工智能·opencv
安卓开发者14 小时前
Android KTX:让Kotlin开发更简洁高效的利器
android·开发语言·kotlin
whysqwhw14 小时前
OkHttp WebSocket 实现详解:数据传输、帧结构与组件关系
android