Android UI 绘制流程

Android View的整个绘制流程包含了3个阶段,分别是measure、layout、draw。measure用于计算View的宽高,结果放在mMeasuredWidthmMeasuredHeight中;layout的作用是计算view及其子view的位置;draw的作用就是将View真正的绘制到显示屏上。

当activity首次启动,或者更新View(如TextView重新设置text)时,都会走到ViewRootImpl.scheduleTraversals来触发View的渲染流程。


zenuml
ViewRootImpl.scheduleTraversals {
    "主线程消息循环插入同步屏障"
    Choreographer."postCallback:mTraversalRunnable" {
        scheduleFrameLocked {
            scheduleVsyncLocked {
                FrameDisplayEventReceiver.scheduleVsync()
            }
        }
    }
}
FrameDisplayEventReceiver.onVsync {
    Looper."发布一个同步消息"
}
Looper.run {
    Choreographer.doFrame {
        "doCallbacks(Choreographer.CALLBACK_INPUT, ...)"
        "doCallbacks(Choreographer.CALLBACK_ANIMATION,...)"
        "doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION,...)"
        "doCallbacks(Choreographer.CALLBACK_TRAVERSAL,...)" {
            ViewRootImpl."mTraversalBarrier.run" {
                ViewRootImpl.doTraversal {
                    "删除同步屏障"
                    performTraversals
                }
            }
        }
        "doCallbacks(Choreographer.CALLBACK_COMMIT,...)"
    }
}

zenuml
ViewRootImpl.performTraversals {
    measureHierarchy {
        performMeasure {
            DecorView.measure
        }
    }
    relayoutWindow {
        IWindowSession.relayout
        updateBlastSurfaceIfNeeded {
            new BLASTBufferQueue
            blastSurface = BLASTBufferQueue.createSurface
            "mSurface.transferFrom(blastSurface)"
        }
        ThreadedRenderer.setSurfaceControl
        ThreadedRenderer.setBlastBufferQueue
    }
    ThreadedRenderer.initialize {
        "HardwareRenderer.setSurface"
    }
    ThreadedRenderer.allocateBuffers
    ThreadedRenderer.setup
    performMeasure {
        DecorView.measure
    }
    performLayout {
        DecorView.layout
    }
    performDraw {
        draw {
            ThreadedRenderer.draw
        }
    }
}

关于relayoutWindow的调用流程可参考Android WindowManager与Window

参考:

draw() 流程

在自定义View中一般都会用到onDraw这个函数:


zenuml
ViewRootImpl.draw {
    if (isHardwareEnabled()) {
        ThreadedRenderer.draw
    } else {
        drawSoftware
    }
}

当开启硬件加速后,会使用ThreadedRenderer进行绘制,否则调用drawSoftware。

ViewRootImplSurfaceSurfaceControlSurfaceSessionCanvasBitmapIGraphicBufferProducer

Surface是一个接口,供生产方与消耗方交换缓冲区。用于显示 Surface 的 BufferQueue 通常配置为三重缓冲。缓冲区是按需分配的,因此,如果生产方足够缓慢地生成缓冲区(例如在 60 fps 的显示屏上以 30 fps 的速度缓冲),队列中可能只有两个分配的缓冲区。

软件渲染

执行ViewRootImpl#drawSoftware方法。

  1. 获取Canvas:canvas = mSurface.lockCanvas(dirty);
  2. 执行View#draw:mView.draw(canvas);
  3. surface.unlockCanvasAndPost(canvas);

执行android-12.1.0_r27\frameworks\base\core\jni\android_view_Surface.cpp的nativeUnlockCanvasAndPost方法。

Client连接的是BufferQueue的生成端,而消费端则是SurfaceFlinger。


zenuml
group Java {
    <<Java>> Surface
}
group cpp {
    Jni
    <<Cpp>> sfc as Surface
    <<Cpp>> cvs as Canvas
    <<Cpp>> IGraphicBufferProducer
}
Surface.lockCanvas {
    Jni.nativeLockCanvas {
        sfc.lock {
            dequeueBuffer {
                IGraphicBufferProducer.dequeueBuffer
            }
        }
        new cvs() {

        }
        cvs.setBuffer
    }
}

非硬件加速的场景,通过lockCanvas()来获取一个画布,锁定一个缓冲区用于在CPU上进行渲染。而unlockCanvasAndPost() 可解锁缓冲区并将其发送到合成器。


zenuml
group Java {
    <<Java>> Surface
}
group cpp {
    Jni
    <<Cpp>> sfc as Surface
    <<Cpp>> cvs as Canvas
}

Surface.unlockCanvasAndPost {
    unlockSwCanvasAndPost {
        Jni.nativeUnlockCanvasAndPost {
            cvs.setBuffer(nullptr)
            sfc.unlockAndPost {
                queueBuffer {
                    IGraphicBufferProducer.queueBuffer
                }
            }
        }
    }
}

硬件渲染(硬件加速)

最终会通过RenderThread进行渲染ViewRootImpl#draw中调用mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);

RenderThread类图

RenderProxyDrawFrameTaskRenderThreadCanvasContextIRenderPipelineSkiaPipelineSkiaOpenGLPipelineSkiaVulkanPipelineDeferredLayerUpdaterThreadBaseWorkQueueLoopernew获取单例new1n ThreadedRendererThreadedRendererRenderProxyRenderProxyDrawFrameTaskDrawFrameTaskWorkQueueWorkQueueRenderThreadRenderThreadCanvasContextCanvasContextIRenderPipelineIRenderPipelinedrawjni调用syncAndDrawFramedrawFramepostAndWaitpost获取任务rundrawdraw

RenderThread如何启动运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
RenderThread& RenderThread::getInstance() {
[[clang::no_destroy]] static sp<RenderThread> sInstance = []() {
sp<RenderThread> thread = sp<RenderThread>::make();
thread->start("RenderThread"); //开始执行threadLoop
return thread;
}();
gHasRenderThreadInstance = true;
return *sInstance;
}

bool RenderThread::threadLoop() {
setpriority(PRIO_PROCESS, 0, PRIORITY_DISPLAY);
Looper::setForThread(mLooper);
if (gOnStartHook) {
gOnStartHook("RenderThread");
}
initThreadLocals();

while (true) { //loop forever
waitForWork();
processQueue(); //执行WorkQueue中的任务

if (mPendingRegistrationFrameCallbacks.size() && !mFrameCallbackTaskPending) {
mVsyncSource->drainPendingEvents();
mFrameCallbacks.insert(mPendingRegistrationFrameCallbacks.begin(),
mPendingRegistrationFrameCallbacks.end());
mPendingRegistrationFrameCallbacks.clear();
requestVsync();
}

if (!mFrameCallbackTaskPending && !mVsyncRequested && mFrameCallbacks.size()) {
// TODO: Clean this up. This is working around an issue where a combination
// of bad timing and slow drawing can result in dropping a stale vsync
// on the floor (correct!) but fails to schedule to listen for the
// next vsync (oops), so none of the callbacks are run.
requestVsync();
}
}

return false;
}

会生成一个RecordingCanvas来记录所有的绘制步骤:

Canvas类图

在使用硬件加速时,onDraw的canvas实际上是RecordingCanvas。RecordingCanvas最终的产物是DisplayList,然后交给RenderThread去执行真正的绘制流程。

Surface的生成过程

ViewRootImpl中通过Surface的无参构造函数创建了一个空的Surface。在ViewRootImpl.relayoutWindow中会调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
......
if (mSurfaceControl.isValid()) {
if (!useBLAST()) {
mSurface.copyFrom(mSurfaceControl);
} else {
updateBlastSurfaceIfNeeded();
}
......
}
void updateBlastSurfaceIfNeeded() {
......
mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);
mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback);
//BlastBufferQueue.createSurface在native层生成一个BBQSurface类返回给java层
Surface blastSurface = mBlastBufferQueue.createSurface();
//将native Surface对象迁移到mSurface对象中
mSurface.transferFrom(blastSurface);
}

BlastBufferQueue.createSurface在native层生成一个BBQSurface对象返回给java层

1
2
3
4
5
6
7
8
9
//c++
sp<Surface> BLASTBufferQueue::getSurface(bool includeSurfaceControlHandle) {
std::unique_lock _lock{mMutex};
sp<IBinder> scHandle = nullptr;
if (includeSurfaceControlHandle && mSurfaceControl) {
scHandle = mSurfaceControl->getHandle();
}
return new BBQSurface(mProducer, true, scHandle, this);
}

BlastBufferQueue.createSurface返回给java层的对象是在jni层调用了下面这个Surface构造函数,因此这个Surface就关联了native的Surface了:

1
2
3
4
5
6
//Surface.java
private Surface(long nativeObject) {
synchronized (mLock) {
setNativeObjectLocked(nativeObject);
}
}

Surface构建和填充

CanvasContext是在RenderProxy的构造函数中构造的。

Surface分配内存

内存分配是在ViewRootImpl.performTraversals中执行mAttachInfo.mThreadedRenderer.allocateBuffers();类为surface申请内存。allocateBuffers在HardwareRenderer中实现,会调用native端的RenderProxy.allocateBuffers来发起内存申请。


zenuml
RenderProxy.allocateBuffers {
    CanvasContext.allocateBuffers {
        //NATIVE_WINDOW_ALLOCATE_BUFFERS
        Surface."perform" {
            Surface.allocateBuffers {
                BufferQueueProducer.allocateBuffers {
                    new GraphicBuffer {
                        initWithSize {
                            GraphicBufferAllocator.allocate
                        }
                    }
                }
            }
        }
    }
}
«Singleton»GraphicBufferAllocatorGrallocAllocator mAllocatorGraphicBufferGrallocAllocatorGralloc4AllocatorGralloc3AllocatorGralloc2Allocator

实际分配内存是在SurfaceFlinger进程中,通过hal层的Gralloc来分配内存。可以使用adb shell dumpsys SurfaceFlinger命令查看内存分配情况,如下:

1
2
3
4
5
6
GraphicBufferAllocator buffers:
Handle | Size | W (Stride) x H | Layers | Format | Usage | Requestor
0x716ff921acd0 | 10125.00 KiB | 1080 (1080) x 2400 | 1 | 1 | 0x 1b00 | FramebufferSurface
0x716ff921b300 | 10125.00 KiB | 1080 (1080) x 2400 | 1 | 1 | 0x 1b00 | FramebufferSurface
0x716ff9226430 | 82.69 KiB | 336 ( 336) x 63 | 1 | 1 | 0x 303 | RegionSamplingThread
Total allocated by GraphicBufferAllocator (estimate): 20332.69 KB

参考资料:

BufferQueue生产者和消费者

BufferQueue的生产者端连着app,消费者端连接的是SurfaceFligger。

IGraphicBufferProducerIGraphicBufferConsumerBLASTBufferQueueBufferQueueProducerBBQBufferQueueProducerBufferQueueCoreBnGraphicBufferProducerBnInterfaceIGraphicBufferProducerBBinderBnGraphicBufferConsumerBufferQueueConsumerSafeBnInterfaceIGraphicBufferConsumerBnInterfaceIGraphicBufferConsumerSurfaceControl

附录一:Canvas Java层与Native源码对应关系

本节从源码角度说明了Java层的Canvas与Native层函数之间的对应关系。

  1. android.graphics.Canvasandroid.graphics.BaseCanvasandroid.graphics.BaseRecordingCanvas这三个类中的jni方法都在frameworks\base\libs\hwui\jni\android_graphics_Canvas.cpp中定义:

    1
    2
    3
    4
    5
    6
    7
    int register_android_graphics_Canvas(JNIEnv* env) {
    int ret = 0;
    ret |= RegisterMethodsOrDie(env, "android/graphics/Canvas", gMethods, NELEM(gMethods));
    ret |= RegisterMethodsOrDie(env, "android/graphics/BaseCanvas", gDrawMethods, NELEM(gDrawMethods));
    ret |= RegisterMethodsOrDie(env, "android/graphics/BaseRecordingCanvas", gDrawMethods, NELEM(gDrawMethods));
    return ret;
    }
  2. android.graphics.RecordingCanvas对应的jni方法在frameworks\base\libs\hwui\jni\android_graphics_DisplayListCanvas.cpp中定义;

    1
    2
    3
    4
    5
    6
    7
    const char* const kClassPathName = "android/graphics/RecordingCanvas";
    int register_android_view_DisplayListCanvas(JNIEnv* env) {
    jclass runnableClass = FindClassOrDie(env, "java/lang/Runnable");
    gRunnableMethodId = GetMethodIDOrDie(env, runnableClass, "run", "()V");

    return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
    }

native Canvas对应的是SkiaRecordingCanvas,创建代码对应:

1
2
3
4
5
6
7
//android-12.1.0_r27\frameworks\base\graphics\java\android\graphics\RecordingCanvas.java
private RecordingCanvas(@NonNull RenderNode node, int width, int height) {
super(nCreateDisplayListCanvas(node.mNativeRenderNode, width, height));
mDensity = 0; // disable bitmap density scaling
}
@CriticalNative
private static native long nCreateDisplayListCanvas(long node, int width, int height);

RecordingCanvas构造函数中调用了native函数nCreateDisplayListCanvas,并最终将结果赋值给了父类Canvas#mNativeCanvasWrapper变量,这个变量存储的就是native层的Canvas对象指针,对应c++层SkiaRecordingCanvas类。nCreateDisplayListCanvas源码实现如下:

1
2
3
4
5
6
7
8
9
10
11
//android\android-12.1.0_r27\frameworks\base\libs\hwui\jni\android_graphics_DisplayListCanvas.cpp
static jlong android_view_DisplayListCanvas_createDisplayListCanvas(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
jint width, jint height) {
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
return reinterpret_cast<jlong>(Canvas::create_recording_canvas(width, height, renderNode));
}

//android\android-12.1.0_r27\frameworks\base\libs\hwui\hwui\Canvas.cpp
Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::RenderNode* renderNode) {
return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height);
}

示例:drawCircle

Java层drawCircle

比如我们在View的onDraw函数中画一个圆,代码如下:

1
2
3
4
protected void onDraw(Canvas canvas) {
mPaint.setColor(getResources().getColor(R.color.red));
canvas.drawCircle(50f, 50f, 10f, mPaint); //void drawCircle(float cx, float cy, float radius, @NonNull Paint paint)
}

onDraw函数的canvas参数实际上是RecordingCanvas类。因为RecordingCanvas没有重写这个drawCircle函数,所以调用的父类BaseRecordingCanvas的drawCircle函数及其对应的jni函数nDrawCircle

1
2
3
4
5
6
7
//frameworks\base\graphics\java\android\graphics\BaseRecordingCanvas.java
@FastNative
private static native void nDrawCircle(long nativeCanvas, float cx, float cy, float radius, long nativePaint);
@Override
public final void drawCircle(float cx, float cy, float radius, @NonNull Paint paint) {
nDrawCircle(mNativeCanvasWrapper, cx, cy, radius, paint.getNativeInstance());
}

Native层drawCircle

1
2
3
4
5
6
7
8
9
//frameworks\base\libs\hwui\jni\android_graphics_Canvas.cpp
static Canvas* get_canvas(jlong canvasHandle) {
return reinterpret_cast<Canvas*>(canvasHandle);
}
static void drawCircle(JNIEnv* env, jobject, jlong canvasHandle, jfloat cx, jfloat cy,
jfloat radius, jlong paintHandle) {
const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
get_canvas(canvasHandle)->drawCircle(cx, cy, radius, *paint);
}

canvasHandle就是mNativeCanvasWrapper,对应C++的SkiaRecordingCanvas类。SkiaRecordingCanvas中并没有复写drawCirle(其实也有一个drawCircle但是参数签名不匹配),所以调用父类SkiaCanvas的drawCircle函数。

1
2
3
4
void SkiaCanvas::drawCircle(float x, float y, float radius, const Paint& paint) {
if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return;
applyLooper(&paint, [&](const SkPaint& p) { mCanvas->drawCircle(x, y, radius, p); }); //调用mCanvas->drawCircle
}

SkCanvas* SkiaCanvas::mCanvas成员变量是在子类SkiaRecordingCanvas初始化过程中通过调用SkiaCanvas::reset(&mRecorder);来设置,mCanvas是RecordingCanvas。也就是说RecordingCanvas是SkCanvas的子类。

1
class RecordingCanvas final : public SkCanvasVirtualEnforcer<SkNoDrawCanvas> { ...... }

SkCanvasVirtualEnforcer是一个模板类,在skia库中定义,RecordingCanvas实际上继承自SkNoDrawCanvas,SkNoDrawCanvas以同样的方式继承自SkCanvas。

RecordingCanvas和SkNoDrawCanvas都没有复写SkCanvas::drawCircle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//external\skia\include\core\SkScalar.h
typedef float SkScalar;
//external\skia\src\core\SkCanvas.cpp
void SkCanvas::drawCircle(SkScalar cx, SkScalar cy, SkScalar radius, const SkPaint& paint) {
if (radius < 0) {
radius = 0;
}

SkRect r;
r.setLTRB(cx - radius, cy - radius, cx + radius, cy + radius);
this->drawOval(r, paint);
}
void SkCanvas::drawOval(const SkRect& r, const SkPaint& paint) {
TRACE_EVENT0("skia", TRACE_FUNC);
// To avoid redundant logic in our culling code and various backends, we always sort rects
// before passing them along.
this->onDrawOval(r.makeSorted(), paint); //最终调用的是子类的onDrawOval函数
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//frameworks\base\libs\hwui\RecordingCanvas.h
DisplayListData* RecordingCanvas::fDL;
//F:\github\android\android-12.1.0_r27\frameworks\base\libs\hwui\RecordingCanvas.cpp
void RecordingCanvas::onDrawOval(const SkRect& oval, const SkPaint& paint) {
fDL->drawOval(oval, paint);
}

void DisplayListData::drawOval(const SkRect& oval, const SkPaint& paint) {
this->push<DrawOval>(0, oval, paint);
}

struct DrawOval final : Op {
static const auto kType = Type::DrawOval;
DrawOval(const SkRect& oval, const SkPaint& paint) : oval(oval), paint(paint) {}
SkRect oval;
SkPaint paint;
void draw(SkCanvas* c, const SkMatrix&) const { c->drawOval(oval, paint); }
};

待办

  • View#buildLayer的作用是什么?
  • RenderThread会使用surface.lockHardwareCanvas()吗?

SurfaceView

SurfaceView是一个组件,可用于在 View 层次结构中嵌入其他合成层。使用 SurfaceView 进行渲染时,SurfaceFlinger 会直接将缓冲区合成到屏幕上。如果没有 SurfaceView,您需要将缓冲区合成到屏幕外的 Surface,然后该 Surface 会合成到屏幕上,而使用 SurfaceView 进行渲染可以省去额外的工作。

参考资料

Android-Surface原理解析
Android-Surface之双缓冲及SurfaceView解析
Android 图形架构 之三—— 创建Layer、Surface、SurfaceControl
Android图形显示系统(一)硬件合成HWC2
Android 源码 图形系统之请求布局
Android 源码 图形系统之 relayoutWindow
BlastBufferQueue 机制介绍
onMeasure() 方法多次调用的原因