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() 方法多次调用的原因