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 进行显示,这一块的内容我们后面单独分析。

相关推荐
xiaolizi5674898 小时前
安卓远程安卓(通过frp与adb远程)完全免费
android·远程工作
阿杰100018 小时前
ADB(Android Debug Bridge)是 Android SDK 核心调试工具,通过电脑与 Android 设备(手机、平板、嵌入式设备等)建立通信,对设备进行控制、文件传输、命令等操作。
android·adb
梨落秋霜8 小时前
Python入门篇【文件处理】
android·java·python
遥不可及zzz11 小时前
Android 接入UMP
android
Coder_Boy_13 小时前
基于SpringAI的在线考试系统设计总案-知识点管理模块详细设计
android·java·javascript
冬奇Lab13 小时前
【Kotlin系列03】控制流与函数:从if表达式到Lambda的进化之路
android·kotlin·编程语言
冬奇Lab13 小时前
稳定性性能系列之十二——Android渲染性能深度优化:SurfaceFlinger与GPU
android·性能优化·debug
冬奇Lab15 小时前
稳定性性能系列之十一——Android内存优化与OOM问题深度解决
android·性能优化
用户745890020795416 小时前
线程池
android