Android UI 绘制流程
Android View的整个绘制流程包含了3个阶段,分别是measure、layout、draw。measure用于计算View的宽高,结果放在mMeasuredWidth和mMeasuredHeight中;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。
Surface是一个接口,供生产方与消耗方交换缓冲区。用于显示 Surface 的 BufferQueue 通常配置为三重缓冲。缓冲区是按需分配的,因此,如果生产方足够缓慢地生成缓冲区(例如在 60 fps 的显示屏上以 30 fps 的速度缓冲),队列中可能只有两个分配的缓冲区。
软件渲染
执行ViewRootImpl#drawSoftware方法。
- 获取Canvas:canvas = mSurface.lockCanvas(dirty);
- 执行View#draw:mView.draw(canvas);
- 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如何启动运行
1 | RenderThread& RenderThread::getInstance() { |
会生成一个RecordingCanvas来记录所有的绘制步骤:
在使用硬件加速时,onDraw的canvas实际上是RecordingCanvas。RecordingCanvas最终的产物是DisplayList,然后交给RenderThread去执行真正的绘制流程。
Surface的生成过程
ViewRootImpl中通过Surface的无参构造函数创建了一个空的Surface。在ViewRootImpl.relayoutWindow中会调用
1 | private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, |
BlastBufferQueue.createSurface在native层生成一个BBQSurface对象返回给java层
1 | //c++ |
BlastBufferQueue.createSurface返回给java层的对象是在jni层调用了下面这个Surface构造函数,因此这个Surface就关联了native的Surface了:
1 | //Surface.java |
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
}
}
}
}
}
}
}
实际分配内存是在SurfaceFlinger进程中,通过hal层的Gralloc来分配内存。可以使用adb shell dumpsys SurfaceFlinger命令查看内存分配情况,如下:
1 | GraphicBufferAllocator buffers: |
参考资料:
BufferQueue生产者和消费者
BufferQueue的生产者端连着app,消费者端连接的是SurfaceFligger。
附录一:Canvas Java层与Native源码对应关系
本节从源码角度说明了Java层的Canvas与Native层函数之间的对应关系。
android.graphics.Canvas、android.graphics.BaseCanvas、android.graphics.BaseRecordingCanvas这三个类中的jni方法都在frameworks\base\libs\hwui\jni\android_graphics_Canvas.cpp中定义:1
2
3
4
5
6
7int 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;
}android.graphics.RecordingCanvas对应的jni方法在frameworks\base\libs\hwui\jni\android_graphics_DisplayListCanvas.cpp中定义;1
2
3
4
5
6
7const 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 | //android-12.1.0_r27\frameworks\base\graphics\java\android\graphics\RecordingCanvas.java |
RecordingCanvas构造函数中调用了native函数nCreateDisplayListCanvas,并最终将结果赋值给了父类Canvas#mNativeCanvasWrapper变量,这个变量存储的就是native层的Canvas对象指针,对应c++层SkiaRecordingCanvas类。nCreateDisplayListCanvas源码实现如下:
1 | //android\android-12.1.0_r27\frameworks\base\libs\hwui\jni\android_graphics_DisplayListCanvas.cpp |
示例:drawCircle
Java层drawCircle
比如我们在View的onDraw函数中画一个圆,代码如下:
1 | protected void onDraw(Canvas canvas) { |
onDraw函数的canvas参数实际上是RecordingCanvas类。因为RecordingCanvas没有重写这个drawCircle函数,所以调用的父类BaseRecordingCanvas的drawCircle函数及其对应的jni函数nDrawCircle:
1 | //frameworks\base\graphics\java\android\graphics\BaseRecordingCanvas.java |
Native层drawCircle
1 | //frameworks\base\libs\hwui\jni\android_graphics_Canvas.cpp |
canvasHandle就是mNativeCanvasWrapper,对应C++的SkiaRecordingCanvas类。SkiaRecordingCanvas中并没有复写drawCirle(其实也有一个drawCircle但是参数签名不匹配),所以调用父类SkiaCanvas的drawCircle函数。
1 | void SkiaCanvas::drawCircle(float x, float y, float radius, const Paint& paint) { |
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 | //external\skia\include\core\SkScalar.h |
1 | //frameworks\base\libs\hwui\RecordingCanvas.h |
待办
-
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() 方法多次调用的原因