启用硬件渲染
视图经过测量和布局,其大小和位置已经确定,接下来是绘制,将视图显示在固定的屏幕位置。本文基于硬件渲染进行分析,关于软件绘制知识不涉及。
每个App进程都包含各种不同的视图UI,系统并不知道你到底需要显示什么样的界面。因此,具体的绘制过程由App进程完成,系统层向你提供一块画布和一系列的api,我们就可以在自己的进程绘制各类视图View。
我们都知道,自定义视图时,需要重写View的onDraw方法,该方法的参数是Canvas,这块画布是如何产生的呢,硬件渲染中它到底扮演什么角色,利用画布api如何实现渲染呢,很多的疑问慢慢分析。
视图的测量,布局和渲染都在ViewRootImpl类中,performTraversals方法,在编舞者的控制下,当App收到刷新命令,实现一次绘制刷新。
private void performTraversals(){
if (!cancelDraw && !newSurface) {//前面已经过测量与布局,且存在Surface。
performDraw();
} else {
}
}
//ViewRootImpl#performDraw方法。
private void performDraw() {
final boolean fullRedrawNeeded = mFullRedrawNeeded;
try {
draw(fullRedrawNeeded);
} finally {
}
}
什么情况下开启硬件加速渲染?什么时候初始化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,它继承HardwareRenderer。
回到ViewRootImpl的draw方法,AttachInfo内部HardwareRenderer存在,硬件渲染,不存在会软件渲染。
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);//硬件渲染入口。
}
ThreadedRenderer架构分析
构造方法
ThreadedRenderer(Context context, boolean translucent) {
...
long rootNodePtr = nCreateRootRenderNode();
mRootNode = RenderNode.adopt(rootNodePtr);
mRootNode.setClipToBounds(false);
mNativeProxy = nCreateProxy(translucent, rootNodePtr);
...
}
JNI#方法创建一个底层RootRenderNode节点,创建一个上层RenderNode节点,封装RootRenderNode,即rootNodePtr指针。
JNI#方法创建一个底层RenderProxy代理。RenderProxy的构造方法,将创建CanvasContext和RenderThread,CanvasContext封装底层RootRenderNode节点,
ThreadedRenderer与底层结构的关系图如下。
ThreadedRenderer的draw方法,Java层硬件渲染的入口方法。
@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);
...
}
updateRootDisplayList方法,绘制整个树形视图结构,从顶层视图开始,每一个视图节点逐一绘制,最终目的是触发每一个视图的Canvas#drawXxx方法。
nSyncAndDrawFrame方法,同步帧数据,最终目的OpenGL指令写入gpu。(另外文章分析)
视图遍历绘制
如何触发树形结构中每一个视图Canvas的相关方法呢?自定义View时,重写onDraw方法,将视图绘制成我们想要的样子,就是利用Canvas的一系列drawXxx方法。视图遍历,从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);
}
}
}
它的入参是顶层视图DecorView。主流程图。
- updateViewTreeDisplayList方法,从顶层视图DecorView开始,遍历树形视图结构的每一个节点,利用视图内的RenderNode创建Canvas,绘制。
- 利用ThreadedRenderer的根RootRenderNode创建Canvas,绘制顶层RenderNode节点。
根视图结构遍历绘制简要分析
遍历的主流程在第一步,ThreadedRenderer的updateViewTreeDisplayList方法,意思是更新树视图的绘制数据。从顶层视图DecorView,真正开始树形结构递归绘制。
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标志,有这个标志,在调用每一个View的updateDisplayListIfDirty方法时,才会创建Canvas,当一个视图需要绘制时,上层肯定会设置该标志。最后会将重建标志还原。流程图。每个视图的流程是一样的,都有三个步骤,第一次绘制时,每个视图都要建立Canvas。
- 通过视图RenderNode节点start方法,创建DisplayListCanvas画布对象。
- 通过View的draw(canvas)方法,实现具体记录绘制操作,(绘制自身与派发),draw方法包括很多步骤,包括递归到子视图的updateDisplayListIfDirty方法,后面会介绍。
- 最后,RenderNode结束记录end方法。
因此,从顶层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 {
//第三步,绘制结束,保存canvas记录内容
renderNode.end(canvas);
setDisplayListProperties(renderNode);
}
} else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}
第一步 RenderNode#start方法开始,创建DisplayListCanvas画布,它继承Canvas。每个视图都有一个画布,Canvas缓存池存储画布对象。
public DisplayListCanvas start(int width, int height) {
DisplayListCanvas canvas = DisplayListCanvas.obtain(this);
canvas.setViewport(width, height);
canvas.onPreDraw(null);
return 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。
上层DisplayListCanvas和底层的架构关系图。第二步 View#draw(canvas)方法,实现视图绘制。参数就是上面创建的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派发绘制子视图,如果有跳过标志,将不会来到draw方法,直接去dispatchDraw。
- 5:如有绘制fading edges,恢复canvas' layers。
- 6:绘制修饰,如滚动条。
下面分析第三和四步骤。
第三步,onDraw方法,传入DisplayListConvas,调用drawXxxx方法。在View中,它是一个空方法,子类会实现,不管子类是叶子节点视图还是容器视图,该方法的主要目标是绘制自己。一般情况,容器视图重写的onDraw方法仅绘制一些边框,分割线之类的东西,而叶子节点视图的onDraw方法将绘制成用户想要得到的视图。
第四步,dispatchDraw方法,派发绘制子视图,在View中是空方法,容器类视图会重写。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方法(三个参数重载)。
在三个参数的draw重载方法中,有一个判断,若是硬件渲染,触发子视图的updateDisplayListIfDirty方法。
if (drawingWithRenderNode) {
renderNode = updateDisplayListIfDirty();
if (!renderNode.isValid()) {
renderNode = null;
drawingWithRenderNode = false;
}
}
因此,每个子视图都会递归到View的updateDisplayListIfDirty方法,进入此方法后,又回到和上面流程图一样的逻辑中,直到DecorView所有子视图绘制完成,并且所有子视图Canvas结束,递归完成,最后,DecorView的Canvas结束。
进入递归的点是在上面draw(Canvas canvas)方法的第四步dispatchDraw方法。如果视图getHardwareLayer方法不空,比如TextureView视图,触发Canvas的drawHardwareLayer方法,TextureView不是容器类型,是View的子类,没有绘制派发。
第三步 RenderNode#end方法,DisplayListCanvas结束,保存数据。
public void end(DisplayListCanvas canvas) {
canvas.onPostDraw();
long renderNodeData = canvas.finishRecording();
nSetDisplayListData(mNativeRenderNode, renderNodeData);
canvas.recycle();
mValid = true;
}
JNI#方法调用底层DisplayListCanvas的finishRecording方法,返回底层DisplayListCanvas对象中保存的DisplayListData指针。
JNI#方法将DisplayListData指针保存到底层RenderNode中。
Canvas释放,回收入缓存池。
DisplayListData* DisplayListCanvas::finishRecording() {
...
DisplayListData* data = mDisplayListData;
mDisplayListData = nullptr;
mSkiaCanvasProxy.reset(nullptr);
return data;
}
记录DisplayList数据结束,将RenderNode有效标志设置为true,如果没有完成,DisplayList将无法显示,绘制结束后,视图RenderNode节点标示isValid有效标志。
ThreadedRenderer根RenderNode绘制简要分析
前面介绍过,updateRootDisplayList方法分两步,先顶层视图结构遍历绘制,更新DisplayList数据,第二步是ThreadedRenderer的根RenderNode绘制,同样,通过根RenderNode创建DisplayListCanvas,通过它的drawRenderNode方法,负责绘制顶层视图DecorView的RenderNode节点。**
这里需要注意的一点是,这里根RenderNode创建的Canvas不属于任一个View视图,因为Canvas是View的RenderNode搞出来的,顶层RenderNode不依赖任何View,Canvas负责绘制DecorView的RenderNode。
看一下DisplayListCanvas的drawRenderNode方法,其中还会调用一次根View的updateDisplayListIfDirty方法,不会再进行一次View树绘制,这时的view还是DecorView,它的DisplayListCanvas已经end结束记录,并且,View的RenderNode节点mValid已有效,且mRecreateDisplayList标志已被恢复。
总结
1,在Java层,硬件渲染由ThreadedRenderer负责,每个窗体根视图ViewRootImpl下都有一个ThreadedRenderer,保存在AttachInfo,它的draw方法是硬件渲染绘制的入口。
2,从ViewRootImpl开始,一般视图会创建ThreadedRenderer,启用硬件渲染,关键点在遍历每一个视图,根据视图RenderNode创建画布,有效绘制记录存储在RenderNode关联的底层DisplayListData。
3,绘制架构包含RenderNode节点,DisplayListCanvas画布,底层DisplayListData对象,CanvasState状态存储对象,做完这些初始化工作,就可以在Java层画布上执行绘制操作方法啦,树形视图结构每一节点都有一个DisplayListCanvas,利用Canvas#drawXxx方法分别记录一些绘制操作,drawXxx画点、圆、矩形等操作,将这些操作存储在一个DisplayList集合中,这是App的UI线程负责的任务。。
4,onDraw方法自己重写,View和ViewGroup有什么自己需要的绘制在这里完成。View的dispatchDraw是空方法,不做任何操作。ViewGroup重写dispatchDraw方法,实现绘制派发到子视图。容器视图一般没有自己要绘制的东西,可能在updateDisplayListIfDirty方法就已经选择dispatchDraw了。
5,顶层视图绘制入口是draw(一个参数)方法,在draw(一个参数)中,包含六个步骤,第四步会派发每个子视图,子视图绘制入口是draw(三个参数),在draw(三个参数)中,会根据硬件渲染,进入每个子视图updateDisplayListIfDirty方法,实现递归绘制。
6,当走到RenderNode的end方法时,表示视图本身以及子视图已经全部绘制完毕,也就是说当DecorView的RenderNode#end方准备执行时,所有draw已经完成。
7,View构造方法创建每一个视图的RenderNode。每一个RenderNode都会创建DisplayListCanvas,使用时是一一对应关系。
任重而道远