视图经过测量和布局,其大小和位置已经确定,接下来是绘制,将视图显示在固定的屏幕位置。本文基于硬件渲染进行分析,关于软件绘制暂且不讨论。
每个App进程均包含不同的视图UI,系统并不知道您到底需要显示什么样的界面,因此,绘制过程由App进程完成。系统框架向您提供了一块画布和一系列API,用户就可以在自己的进程绘制各类视图(View)。大家都知道,在自定义视图时重写View#onDraw方法,其入参就是一块画布,这块画布是如何产生的呢,它在硬件渲染中到底扮演什么角色,利用画布API是如何实现硬件渲染的呢,有好多疑问,下面慢慢分析。
当App收到刷新命令,在Choreographer控制下,实现一次绘制刷新。
因此,UI绘制入口是ViewRootImpl#performTraversals方法。
private void performTraversals(){
if (!cancelDraw && !newSurface) {//前面已经过测量与布局,且存在Surface。
performDraw();
} else {
}
}
//ViewRootImpl#performDraw方法。
private void performDraw() {
final boolean fullRedrawNeeded = mFullRedrawNeeded;
try {
draw(fullRedrawNeeded);
} finally {
}
}
什么情况下开启硬件加速渲染?
在draw方法中,若AttachInfo内部HardwareRenderer存在,则硬件渲染,否则软件渲染。
什么时候初始化HardwareRenderer呢?
硬件加入渲染开启
ViewRootImpl#setView代码段。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
//view是DecorView视图,它实现RootViewSurfaceTaker
if (view instanceof RootViewSurfaceTaker) {
mSurfaceHolderCallback =((RootViewSurfaceTaker)view).willYouTakeTheSurface();
if (mSurfaceHolderCallback != null) {
mSurfaceHolder = new TakenSurfaceHolder();
mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
}
}
if (mSurfaceHolder == null) {
enableHardwareAcceleration(attrs);//开启硬件加速
}
...
}
private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
//应用在兼容模式时,不开启
//永久进程包括系统进程在低端设备中不加速
...
//创建ThreadedRenderer对象
mAttachInfo.mHardwareRenderer = HardwareRenderer.create(mContext, translucent);
...
}
- mSurfaceHolderCallback不空,mSurfaceHolder不空,Activity将拥有Surface,利用SurfaceHolder实现绘制,类似SurfaceView用法。
- mSurfaceHolderCallback是空,mSurfaceHolder也是空,启动硬件绘制。
创建一个ThreadedRenderer,负责硬件渲染,保存在AttachInfo内部,它继承HardwareRenderer。
ViewRootImpl#draw方法代码段。
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);//硬件渲染入口。
}
总结
在Java层,硬件渲染由ThreadedRenderer负责,每个窗体ViewRootImpl有一个ThreadedRenderer。ThreadedRenderer#draw方法是硬件渲染绘制的入口。
ThreadedRenderer分析
ThreadedRenderer#构造方法。
ThreadedRenderer(Context context, boolean translucent) {
....
long rootNodePtr = nCreateRootRenderNode();
mRootNode = RenderNode.adopt(rootNodePtr);
mRootNode.setClipToBounds(false);
mNativeProxy = nCreateProxy(translucent, rootNodePtr);
...
}
- JNI#创建底层RootRenderNode节点,创建上层RenderNode,封装RootRenderNode指针。
- JNI#创建底层RenderProxy代理。RenderProxy构造方法创建CanvasContext和RenderThread,CanvasContext封装底层RootRenderNode节点。

硬件渲染入口方法。
@Override
void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
...
//入参view是顶层视图DecorView。
updateRootDisplayList(view, callbacks);//更新视图树每一个节点。
final long[] frameInfo = choreographer.mFrameInfo.mFrameInfo;
//同步绘制帧。
int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
...
}
- 1:updateRootDisplayList方法,绘制整个树形视图结构,从顶层视图开始,每一个视图节点逐一绘制,最终目的是触发每一个视图的Canvas#drawXxx方法。
- 2:nSyncAndDrawFrame方法,同步帧数据,最终目的是触发opengl指令与gpu交互。(另外文章分析)
如何触发树形结构中每一个视图Canvas的相关方法呢?
在自定义View时,重写onDraw方法,将视图绘制成我们想要的样子,其本质就是利用Canvas一系列的drawXxx方法。从updateRootDisplayList开始分析。
updateRootDisplayList分析
ThreadedRenderer#updateRootDisplayList方法。
private void updateRootDisplayList(View view, HardwareDrawCallbacks callbacks) {
//第一步,从顶层视图开始,更新所有视图的DisplayList。
updateViewTreeDisplayList(view);
//第二步,根节点绘制顶层视图RenderNode。
if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
try {
final int saveCount = canvas.save();
canvas.translate(mInsetLeft, mInsetTop);
callbacks.onHardwarePreDraw(canvas);
//插入栅栏,隔离canvas操作
canvas.insertReorderBarrier();
//绘制顶层视图RenderNode。
canvas.drawRenderNode(view.updateDisplayListIfDirty());
//插入栅栏,隔离canvas操作
canvas.insertInorderBarrier();
//回调,ViewRootImpl中实现
callbacks.onHardwarePostDraw(canvas);
canvas.restoreToCount(saveCount);
mRootNodeNeedsUpdate = false;
} finally {
mRootNode.end(canvas);
}
}
}
主要流程图。

- 第一步,触发updateViewTreeDisplayList方法,从顶层视图DecorView开始,遍历树形视图结构的每一个节点,利用视图的RenderNode创建Canvas,绘制。
- 第二步,利用ThreadedRenderer的根RootRenderNode创建Canvas,绘制顶层RenderNode节点。
ThreadedRenderer#updateViewTreeDisplayList方法。
private void updateViewTreeDisplayList(View view) {
view.mPrivateFlags |= View.PFLAG_DRAWN;
view.mRecreateDisplayList = (view.mPrivateFlags &
View.PFLAG_INVALIDATED)== View.PFLAG_INVALIDATED;
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
view.updateDisplayListIfDirty();//即DecorView#updateDisplayListIfDirty方法。
view.mRecreateDisplayList = false;//还原标志。
}
视图私有标志包含PFLAG_INVALIDATED,说明需要绘制,设置mRecreateDisplayList标志,根据该标志重建Canvas。当一个视图需要绘制时,上层肯定已经设置PFLAG_INVALIDATED标志。
ThreadedRenderer根RenderNode绘制简要分析
在updateRootDisplayList方法第二步,ThreadedRenderer的根RenderNode创建DisplayListCanvas,DisplayListCanvas#drawRenderNode方法负责绘制DecorView视图的RenderNode。
这里关注的点是,Canvas不属于某一个View,也不是顶层DecorView,我们接下来就会知道,Canvas是View中RenderNode搞出来的,这里的Canvas是最顶层RenderNode生成,RenderNode不依赖某个View,而是专门绘制DecorView的RenderNode。
看一下DisplayListCanvas#drawRenderNode方法,其中的updateDisplayListIfDirty不会再进行一次View树绘制,这时的view依然是DecorView,它的DisplayListCanvas已经end结束记录,View#RenderNode节点mValid已有效,且标志mRecreateDisplayList已被还原。
从顶层视图DecorView,真正开始树形结构递归绘制。
updateDisplayListIfDirty流程图如下。

因此,从DecorView#updateDisplayListIfDirty开始,就已经开始准备遍历递归每一个子视图updateDisplayListIfDirty方法了,进入每一个视图的draw方法。最终,触发每一个视图onDraw。
View#updateDisplayListIfDirty方法。
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
// ThreadedRenderer是空,直接返回节点
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.isValid()//false,还未记录绘制
|| (mRecreateDisplayList)) {//重建Canvas
if (renderNode.isValid()
&& !mRecreateDisplayList) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchGetDisplayList();
return renderNode;
}
mRecreateDisplayList = true;//重建Canvas
int width = mRight - mLeft;
int height = mBottom - mTop;
int layerType = getLayerType();
//创建DisplayListCanvas
final DisplayListCanvas canvas = renderNode.start(width, height);
//判断LayerType,以及获取HardwareLayer
try {
final HardwareLayer layer = getHardwareLayer();
if (layer != null && layer.isValid()) {
canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint);
} else if (layerType == LAYER_TYPE_SOFTWARE) {
buildDrawingCache(true);
Bitmap cache = getDrawingCache(true);
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, mLayerPaint);
}
} else {
// 一般视图走硬件渲染都执行下面程序
computeScroll();
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
//该View跳过绘制,则直接派发给子视图
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
} else {
draw(canvas);//绘制,包括绘制本身,修饰,以及派发,共六个步骤。
}
}
} finally {
renderNode.end(canvas);//绘制结束,保存canvas记录内容
setDisplayListProperties(renderNode);
}
} else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}
三个步骤
第一步 RenderNode#start开始,创建DisplayListCanvas画布。
DisplayListCanvas与底层的结构关系图。

每一个视图(View)都有一个画布,Canvas缓存池存储画布对象。
RenderNode#start方法。
public DisplayListCanvas start(int width, int height) {
DisplayListCanvas canvas = DisplayListCanvas.obtain(this);
canvas.setViewport(width, height);
canvas.onPreDraw(null);
return canvas;
}
DisplayListConvas继承Canvas。从缓存池获取,将start方法调用者RenderNode设置成Canvas内部RenderNode,表示此Canvas正被该视图使用,视图与RenderNode也是对应关系。
- 缓存池不足时,创建DisplayListConvas,构造方法利用JNI#构建一个底层DisplayListCanvas对象,上层Canvas#mNativeCanvasWrapper存储底层指针。
- Canvas#setViewport方法,宽高存储在Java层DisplayListConvas中,JNI#nSetViewport方法,设置底层DisplayListCanvas的宽高,存储底层CanvasState中。
- Canvas#onPreDraw方法,绘制操作前准备工作,底层DisplayListCanvas#prepare方法,默认执行底层prepareDirty(0.0f, 0.0f, width(), height()),入参区域是刚刚设置的宽高区域。
DisplayListCanvas#prepareDirty方法。
void DisplayListCanvas::prepareDirty(float left, float top,
float right, float bottom) {
mDisplayListData = new DisplayListData();
mState.initializeSaveStack(0, 0, mState.getWidth(),
mState.getHeight(), Vector3());
mDeferredBarrierType = kBarrier_InOrder;
mState.setDirtyClip(false);
mRestoreSaveCount = -1;
}
该方法准备将要绘制的脏区域,创建底层DisplayListData对象,CanvasState#initializeSaveStack初始化,创建Snapshot,初始化mRestoreSaveCount值为-1。mDeferredBarrierType值为kBarrier_InOrder,确保创建Chunk 。
总结
绘制架构包含RenderNode节点,DisplayListCanvas画布,底层DisplayListData对象,CanvasState状态存储对象,做完这些初始化工作,就可以在Java层画布上执行绘制操作方法啦,看第二步。
第二步 View#draw(canvas)方法,View绘制。
View的draw方法传入上面创建的DisplayListConvas,我们的视图有一些公有的绘制,例如背景,滚定条,修饰等。
@CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;//增加drawn标志
...
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
...
return;
}
...
}
六步
1:绘制背景。
2:必要时存储canvas' layers,绘制边缘fade。
3:onDraw方法绘制视图内容,Canvas_api方法,自己实现。
4:dispatchDraw派发绘制子视图。
5:如有绘制fading edges,恢复canvas' layers。
6:绘制修饰,如滚动条。
下面主要分析第三、四步。
第三步,onDraw方法,重写,传入DisplayListConvas,drawXxxx方法。在View中是空方法,子类实现,不管子类是叶子节点还是容器视图,其主要目标是绘制自己。
一般情况下,容器视图的onDraw重写仅绘制一些边框,分割线之类的东西,而叶子节点视图的onDraw重写绘制成用户想要得到的视图。
第四步,dispatchDraw方法,派发绘制子视图内容,容器类视图重写,在View中是空方法。
总结:
onDraw方法自己重写,利用Canvas的drawXxx实现各种类型的绘制。View和ViewGroup有什么自己需要的绘制在这里完成。
View的dispatchDraw是空方法,不做任何操作。
ViewGroup重写dispatchDraw方法,实现子视图绘制派发。
ViewGroup#dispatchDraw方法。
@Override
protected void dispatchDraw(Canvas canvas) {
for (int i = 0; i < childrenCount; i++) {
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
遍历每一个子视图,drawChild触发子视图带三个参数的draw重载方法。
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
DecorView是容器视图,在dispatchDraw方法中,派发他的子视图的draw方法(三个参数重载)。
如何进入子视图updateDisplayListIfDirty中方法进行递归呢?
在draw三个参数重载方法中有一个判断,若是硬件渲染,触发子视图updateDisplayListIfDirty方法。
因此,每一个子视图均会递归到View#updateDisplayListIfDirty方法。进入此方法又回到和上面流程图一样的逻辑中,直到DecorView所有子视图绘制完成并且所有子视图Canvas结束,递归完成。最终,DecorView的Canvas结束。
总结
顶层视图绘制入口是draw(一个参数)方法,在draw(一个参数)中,派发每个子视图,子视图绘制入口是draw(三个参数),在draw(三个参数)中,触发子视图updateDisplayListIfDirty方法,子视图递归绘制。
因此,当走到第三步end方法时,表示视图本身以及子视图已经全部绘制完毕,也就是说当DecorView的RenderNode#end方准备执行时,所有draw已经完成。
若视图getHardwareLayer不空,如TextureView,触发Canvas#drawHardwareLayer,TextureView是View子类,不涉及绘制派发,相关知识后续研究。
View构造方法创建每一个视图的RenderNode。
每一个视图的RenderNode都会创建DisplayListCanvas。
第三步 RenderNode#end方法,DisplayListCanvas结束,保存数据。
RenderNode#end方法。
public void end(DisplayListCanvas canvas) {
canvas.onPostDraw();
long renderNodeData = canvas.finishRecording();
nSetDisplayListData(mNativeRenderNode, renderNodeData);
canvas.recycle();
mValid = true;
}
三步
- 1:JNI#调用底层DisplayListCanvas的finishRecording方法,finishRecording返回底层DisplayListCanvas对象中保存的DisplayListData指针。
- 2:JNI#将DisplayListData指针保存到底层RenderNode中。
- 3:Canvas释放,回收入缓存池。
底层DisplayListCanvas#finishRecording方法。
DisplayListData* DisplayListCanvas::finishRecording() {
...
DisplayListData* data = mDisplayListData;
mDisplayListData = nullptr;
mSkiaCanvasProxy.reset(nullptr);
return data;
}
记录DisplayList数据结束,将RenderNode有效标志设置为true,如果没有完成,DisplayList无法显示。绘制结束后,视图RenderNode节点标明isValid有效标志。
总结
本文介绍硬件渲染绘制阶段上层基本流程。从ViewRootImpl开始,创建ThreadedRenderer对象,启用硬件渲染,利用ThreadedRenderer绘制,关键点在遍历每一个视图,根据视图RenderNode创建画布,有效绘制记录存储在RenderNode关联的底层DisplayListData。
所谓绘制,为树形视图结构每一节点准备一个DisplayListCanvas,利用Canvas#drawXxx方法分别记录一些绘制操作,drawXxx包括画点,画圆,画矩形等操作。将这些操作存储在一个DisplayList集合中,这是App主线程负责的任务。
任重而道远
网友评论