Android 硬件加速流程和源码分析(三)
3 更新显示列表DisplayList
上面介绍了硬件加速过程比较重要的几个相关类,接着看硬件加速过程中显示列表(DispalyList)的更新流程,DispalyList的更新是在主线程完成的.
3.1 更新根节点绘制列表
一个图一行显示不下,继续
硬件加速 记录并更新显示列表 从canvas.drawRenderNode(view.updateDisplayListIfDirty())
开始,view是当前窗口rootView,会从rootView向下依次遍历记录rootView下面每一个View的绘制记录到当前View对应RecordingCanvas的DisplayList中,然后更新为当前View对应的RenderNode的mStagingDisplayList
暂存显示列表,等待下一次屏幕刷新时暂存显示列表mStagingDisplayList
会赋值给RenderNode的mDisplayList
进行绘制,当前view对应的RenderNode也会作为一个RenderNodeOp添加到上一级View的RenderNode的displayList的children
集合中,对RenderNode的子RenderNode的遍历就是从children
中取出遍历
3.1.1 ThreadedRenderer#updateRootDisplayList()
ThreadedRenderer#updateRootDisplayList(View view, DrawCallbacks callbacks)
public final class ThreadedRenderer extends HardwareRenderer {
...
//记录并更新显示列表 (api 28 )
//渲染器更新根节点DisplayList
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
...
//1. 开始更新,生成canvas DisplayListCanvas 继承于 RecordingCanvas
DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
try {
...
//实际调用nInsertReorderBarrier(mNativeCanvasWrapper, true),标记为z方向乱序,需要排序
canvas.insertReorderBarrier();
//最重要的一句!!!!!!,从这里开始遍历记录整个视图
//最重要的一句!!!!!!
//最重要的一句!!!!!!
// 2. 遍历view树更新每个View的displaylist
//实际的动作是向RecordingCanvas.cpp 中的displayList 添加 RecordedOp
canvas.drawRenderNode(view.updateDisplayListIfDirty());
//z方向层级排序完成,nInsertReorderBarrier(mNativeCanvasWrapper, false);
canvas.insertInorderBarrier();
...
mRootNodeNeedsUpdate = false;
} finally {
//3. 完成更新: 把RecordingCanvas中的 displayList设置给RenderNode.cpp中的 mStagingDisplayList
mRootNode.end(canvas);
}
...
}
//记录并更新显示列表 (api29 )
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
...
//1. 开始更新: 生成canvas
RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
try {
...
//实际调用nInsertReorderBarrier(mNativeCanvasWrapper, true),标记为z方向乱序,需要排序
canvas.enableZ();
//最重要的一句!!!!!!,从这里开始遍历记录整个视图
//最重要的一句!!!!!!
//最重要的一句!!!!!!
// 2. 遍历view树更新每个View的displaylist
//实际的动作是向RecordingCanvas.cpp 中的displayList 添加 RecordedOp
canvas.drawRenderNode(view.updateDisplayListIfDirty());
//z方向层级排序完成,调用nInsertReorderBarrier(mNativeCanvasWrapper, false);
canvas.disableZ();
....
mRootNodeNeedsUpdate = false;
} finally {
//3. 完成更新: 把RecordingCanvas中的 displayList设置给RenderNode.cpp中的暂存显示列表mStagingDisplayList
mRootNode.endRecording();
}
}
}
...
-
关于 insertReorderBarrier , 作用是做一个标记,用于绘制动作在z方向排序的处理
android_view_DisplayListCanvas.cpp
static void android_view_DisplayListCanvas_insertReorderBarrier(jlong canvasPtr, 146 jboolean reorderEnable) { 147 Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr); 148 canvas->insertReorderBarrier(reorderEnable); 149}
57 void RecordingCanvas::insertReorderBarrier(bool enableReorder) { 58 if (enableReorder) { 59 mDeferredBarrierType = DeferredBarrierType::OutOfOrder; //乱序 60 mDeferredBarrierClip = getRecordedClip(); 61 } else { 62 mDeferredBarrierType = DeferredBarrierType::InOrder; //有序 63 mDeferredBarrierClip = nullptr; 64 } 65} int RecordingCanvas::addOp(RecordedOp* op) { ... 589 int insertIndex = mDisplayList->ops.size(); 590 mDisplayList->ops.push_back(op);//ops记录的是View自己的绘制 591 if (mDeferredBarrierType != DeferredBarrierType::None) { 592 // op is first in new chunk 593 mDisplayList->chunks.emplace_back(); 594 DisplayList::Chunk& newChunk = mDisplayList->chunks.back(); 595 newChunk.beginOpIndex = insertIndex; 596 newChunk.endOpIndex = insertIndex + 1; //需要从新排序 在后续调用渲染管道的draw方法时会根据这个标志位来处理z方向排序 597 newChunk.reorderChildren = (mDeferredBarrierType == DeferredBarrierType::OutOfOrder); 598 newChunk.reorderClip = mDeferredBarrierClip; 599 600 int nextChildIndex = mDisplayList->children.size(); 601 newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex; 602 mDeferredBarrierType = DeferredBarrierType::None; 603 } else { 604 // standard case - append to existing chunk 605 mDisplayList->chunks.back().endOpIndex = insertIndex + 1; 606 } 607 return insertIndex; 608}
最终渲染管道 pipeline调用draw() 方法时会调用到FrameBuilder ::buildZSortedChildList()对renderNode的绘制操作在Z方向排序. 对root RenderNode的每个子RenderNode遍历完成后, mDeferredBarrierType = DeferredBarrierType::InOrder .
3.1.2 View#updateDisplayListIfDirty( )
继续看ThreadedRenderer的绘制根布局方法updateRootDisplayList()
中 canvas.drawRenderNode(view.updateDisplayListIfDirty())
View的方法调用,一个窗口布局的绘制是从根布局DecorView的updateDisplayListIfDirty开始的.
View#updateDisplayListIfDirty( )
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
public View(Context context) {
...
//当前View的renderNode
mRenderNode = RenderNode.create(getClass().getName(), this);
...
}
//记录并更新显示列表
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
...
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.isValid()
|| (mRecreateDisplayList)) {
// Don't need to recreate the display list, just need to tell our
// children to restore/recreate theirs
//1.如果当前的display不需要更新, 只需要通知子View构建displayList,所以只需要dispatchGetDisplayList()
if (renderNode.isValid()&& !mRecreateDisplayList) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchGetDisplayList();
return renderNode; // no work needed
}
// If we got here, we're recreating it. Mark it as such to ensure that
// we copy in child display lists into ours in drawChild()
//2. 需要重建当前View的displaylist
mRecreateDisplayList = true;
...
//根据这个layerType判断是否硬件加速
int layerType = getLayerType();
//每个View对应的renderNode对应一个DisplayListCanvas/RecordingCanvas
final DisplayListCanvas canvas = renderNode.start(width, height);
try {
if (layerType == LAYER_TYPE_SOFTWARE) {
//软件绘制
//自定义view避免硬件加速导致绘制失效时设置setLayerType(View.LAYER_TYPE_SOFTWARE, null)的作用就是使用软件绘制
buildDrawingCache(true);
//通过buildDrawingCache的到bitmap然后记录到硬件加速的DisplayListCanvas中
Bitmap cache = getDrawingCache(true);
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, mLayerPaint);
}
} else {
//硬件加速绘制
//没有背景那么不需要绘制自己,直接绘制子View
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
//这一步会遍历View树,添加该view的childView的drawxxx()动作到RecordCavans的displayList中
dispatchDraw(canvas);
...
} else {
//绘制自己(背景)记录绘制动作到displayList
//如果当前view有背景,那么会调用draw()绘制自己的背景(),draw()中会调用dispatchDraw()分发绘制子View
draw(canvas);
}
}
} finally {
renderNode.end(canvas);
setDisplayListProperties(renderNode);
}
...
return renderNode;
}
上面代码可以看出RenderNoder的displayList并不是每次都需要重建的,如果上一级的RenderNode不需要重建,只需要通知下一级的RenderNoder判断是否需要重新创建或者更新displayList. 在update的过程中也会判断当前View的layerType类型,如果是软件绘制,会把当前view绘制到一个bitmap上,然后添加到上一级的RenderNode中.
-
当前View的RendNode不需重建displayList的情况:
当调用
updateDisplayListIfDirty()
时,会判断当前View的RendNode是否不需重建displayList,如果不需要,那么调用dispatchGetDisplayList()
然后遍历子View调用recreateChildDisplayList()
,recreateChildDisplayList()
又调用子viewchild.updateDisplayListIfDirty()
遍历下一级子View更新子View的RecordingCanvas中的displayList.@Override protected void dispatchGetDisplayList() { final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { final View child = children[i]; if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) { recreateChildDisplayList(child); } } } private void recreateChildDisplayList(View child) { child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0; child.mPrivateFlags &= ~PFLAG_INVALIDATED; //调用自View更新显示列表 child.updateDisplayListIfDirty(); child.mRecreateDisplayList = false; }
注意, 在recreateChildDisplayList中调用
child.updateDisplayListIfDirty()
返回的结果是一个RenderNode,这里没有看到这个renderNode被添加到parent view的displayList中,因为,因为..都说了啊...parent view不需要重建displayLsit,所以child view只需要updateDisplayListIfDirty()
而不需要重新添加到parent的displayList中,已经在parent的displayList中了,也就是parent的canvas不需要再次调用canvas.drawRenderNode(...) -
当前View的RendNode需要重建displayList的情况:
首先会根据当前View的layerType选择是否软件绘制,如果是软件绘制会把当前View的bitmap记录到硬件加速的DisplayList中, 如果当前view有背景,那么会调用draw()绘制自己的背景(),draw()中会调用dispatchDraw()分发绘制子View,如果当前View没有背景,直接
dispatchDraw()
,然后会调用drawChild().
protected void dispatchDraw(Canvas canvas) { ... //遍历children,每一个child调用 drawChild(canvas, transientChild, drawingTime); ... } protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }
接着 child.draw(canvas, this, drawingTime)
View#draw(Canvas canvas, ViewGroup parent, long drawingTime)
//ViewGroup的子View绘制自己,这个方法中会根据view的layerType和是否是否开启硬件加速指定绘制方式 19842 /** 19843 * This method is called by ViewGroup.drawChild() to have each child view draw itself. 19844 * 19845 * This is where the View specializes rendering behavior based on layer type, 19846 * and hardware acceleration. 19847 */ 19848 boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { //RecordingCanvas会复写isHardwareAccelerated()返回true, 19849 final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated(); //drawingWithRenderNode 硬件加速 19855 boolean drawingWithRenderNode = mAttachInfo != null 19856 && mAttachInfo.mHardwareAccelerated 19857 && hardwareAcceleratedCanvas; 19858 .... ... 19913 if (hardwareAcceleratedCanvas) { //注意这里,如果一个View 被标记 PFLAG_INVALIDATED 后需要重建displayList 19916 mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0; 19918 } 19920 RenderNode renderNode = null; 19921 Bitmap cache = null; 19922 int layerType = getLayerType(); 19923 if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) { 19924 if (layerType != LAYER_TYPE_NONE) { 19925 // If not drawing with RenderNode, treat HW layers as SW //如果不使用硬件加速绘制,修改layerType为LAYER_TYPE_SOFTWARE 软件绘制 19926 layerType = LAYER_TYPE_SOFTWARE; 19927 buildDrawingCache(true); 19928 } 19929 cache = getDrawingCache(true); 19930 } 19931 //如果是使用硬件加速绘制,调用updateDisplayListIfDirty()更新当前View的RenderNode 19932 if (drawingWithRenderNode) { 19933 // Delay getting the display list until animation-driven alpha values are 19934 // set up and possibly passed on to the view 19935 renderNode = updateDisplayListIfDirty(); ... 19943 } ... ... //是否使用绘制缓存(软件绘制) 19953 final boolean drawingWithDrawingCache = cache != null&&!drawingWithRenderNode; 19954 ... //不使用[绘制缓存]绘制 20067 if (!drawingWithDrawingCache) { //硬件加速绘制 20068 if (drawingWithRenderNode) { ... //drawRenderNode把当前View对应的RenderNode添加到父View的displayList的ops和children集合中去 20070 ((DisplayListCanvas) canvas).drawRenderNode(renderNode); 20071 } else { //下面是在getDrawingCache(true)为null即软件绘制的bitmap缓存无效时使用软件绘制的逻辑 20072 // 不需要绘背景 20073 if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { 20074 ... 20075 dispatchDraw(canvas); 20076 } else { 20077 draw(canvas); 20078 } 20079 } 20080 } else if (cache != null) { //cache != null表示当前View不是用硬件加速绘制,但是要把软件绘制后的bitmap添加到上一级的硬件加速绘制的RenderNode中去 canvas.drawBitmap(cache, 0.0f, 0.0f, ..); 20103 } 20104 //修改View为不需要重绘,再次invalidata()后才需要再次重绘 20123 mRecreateDisplayList = false; 20124 20125 return more; 20126 }
三参数的
draw(..)
方法是给ViewGroup用来绘制子View的,在ViewGroup需要更新DisplayList的情况下child调用draw(Canvas canvas, ViewGroup parent, long drawingTime)
绘制自己,这个方法中会根据view的layerType和是否开启硬件加速指定 view的绘制方式,如果drawingWithRenderNode开启硬件加速,会调用updateDisplayListIfDirty()
更新当前View的绘制列表然后调用drawRenderNode(..)
把当前View更新后的renderNode更新到父View的RenderNode的displayList中.调用buildDrawingCache()
时表示当前View使用软件绘制,,绘制结果返回一个bitmap. 可以看出PFLAG_INVALIDATED
标志会使View需要重建displayList. 上面代码中还可以看出View软件绘制的产物是bitmap,硬件加速绘制的产物是一个包含displayList的RenderNode.
3.2 向DisplayList添加记录
硬件加速过程中所有的view在调用 updateDisplayListIfDirty( ) 方法中都会记录显示列表, 最终都向RecordingCanvas添加绘制记录RecordedOp,接着看下如何添加进去的:
3.2.1 child View添加绘制记录到parent View 的RecordingCanvas
上面时序图中没有区分java层的RecordingCanvas和DisplayListCanvas. 一个图一行显示不下,继续
硬件加速情况下,在draw(Canvas canvas, ViewGroup parent, long drawingTime)
中可以看到drawingWithRenderNode
的情况下child View 的RenderNode作为了一个RenderNodeOp被添加到了父View的RenderNode的 RecordingCanvas/DisplayListCanvas的displayList的ops和children集合中去:
api 28中,Canvas#drawRenderNode() 会调用 DisplayListCanvas#nDrawRenderNode(long renderer, long renderNode)方法,api 29开始这里有些许差别. nDrawRenderNode通过 jni调用 native 方法android_view_DisplayListCanvas_drawRenderNode
android_view_DisplayListCanvas_drawRenderNode
156 static void android_view_DisplayListCanvas_drawRenderNode(jlong canvasPtr, jlong renderNodePtr) {
157 Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
158 RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
159 canvas->drawRenderNode(renderNode);
160}
161
RecordingCanvas ::drawRenderNode(RenderNode* renderNode)
541void RecordingCanvas::drawRenderNode(RenderNode* renderNode) {
542 auto&& stagingProps = renderNode->stagingProperties();
//把RenderNode 封装成一个RenderNodeOp
543 RenderNodeOp* op = alloc().create_trivial<RenderNodeOp>(
544 Rect(stagingProps.getWidth(), stagingProps.getHeight()),
545 *(mState.currentSnapshot()->transform), getRecordedClip(), renderNode);
//添加RenderNodeOp到DisplayList的ops集合中
546 int opIndex = addOp(op);
547 if (CC_LIKELY(opIndex >= 0)) {
//drawRenderNode会向RenderNode中的DisplayList的`LsaVector<NodeOpType*> children`中添加一个op记录,遍历获取RenderNode的child就是从`children`取出
548 int childIndex = mDisplayList->addChild(op);
549
550 // update the chunk's child indices
551 DisplayList::Chunk& chunk = mDisplayList->chunks.back();
552 chunk.endChildIndex = childIndex + 1;
553
554 if (renderNode->stagingProperties().isProjectionReceiver()) {
555 // use staging property, since recording on UI thread
556 mDisplayList->projectionReceiveIndex = opIndex;
557 }
558 }
559}
可以看出drawRenderNode(..)
时RecordingCanvas调用了 mDisplayList->addChild(op)
和 addOp(op)
,前者是把子View的renderNode转化成RenderNodeOp添加到父View RecordingCanvas的DisplayList的child集合,而后者包含view的所有自己的绘制RecordedOp和子View的RenderNodeOp.
到这里实际上还没有看到View绘制自己的绘制方法是怎么添加到自己的 RecordingCanvas中的.
3.2.2 View添加绘制记录到自己的RecordingCanvas
ViewGroup调用child View的 draw(canvas, Viparent, long drawingTime)
最终会调用到 child View 的onDraw()
,在updateDisplayListIfDirty()
这一步完成的,updateDisplayListIfDirty() 方法中会获取到当前View的RecordingCanvas,然后这个RecordingCanvas会作为onDraw(canvas)的参数,系统控件和自定义控件都在onDraw()方法中完成各种canvas.drawXXX()绘制,而每一个drawXXX()方法都会调用到native层RecordingCanvas.cpp的drawXXX方法,进而向当前View的RecordingCanvas的DisplayList中添加对应的绘制记录.
下面列举几个RecordingCanvas.cpp中其他的绘制方法,比如
....
416 void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
...
419 addOp(alloc().create_trivial<PathOp>(Rect(path.getBounds()),
420 *(mState.currentSnapshot()->transform), getRecordedClip(),
421 refPaint(&paint), refPath(&path)));
422}
423
424 void RecordingCanvas::VectorDrawable(VectorDrawableRoot* tree) {
425 ...
427 addOp(alloc().create_trivial<VectorDrawableOp>(
428 tree, Rect(tree->stagingProperties()->getBounds()),
429 *(mState.currentSnapshot()->transform), getRecordedClip()));
430}
431
...
RecordingCanvas.cpp所有的drawxxx()方法都会调用到RecordingCanvas::addOp(RecordedOp* op)
RecordingCanvas ::addOp(RecordedOp* op)
580 int RecordingCanvas::addOp(RecordedOp* op) {
...
589 int insertIndex = mDisplayList->ops.size();
//添加 RenderNodeOp 到集合
//DisplayList 记录了将要执行的绘制命令
//调用Canvas API 绘制UI时,实际上只是将Canvas API调用及其参数记录在DisplayList中,然后等到下一个Vsync信号到来时,记录在Display List里面的 绘制命令才会转化为Open GL或者其他渲染管道api的渲染命令由GPU执行
590 mDisplayList->ops.push_back(op);
591 if (mDeferredBarrierType != DeferredBarrierType::None) {
592 // op is first in new chunk
593 mDisplayList->chunks.emplace_back();
594 DisplayList::Chunk& newChunk = mDisplayList->chunks.back();
595 newChunk.beginOpIndex = insertIndex;
596 newChunk.endOpIndex = insertIndex + 1;
//需要重新排序chunk
597 newChunk.reorderChildren = (mDeferredBarrierType == DeferredBarrierType::OutOfOrder);
598 newChunk.reorderClip = mDeferredBarrierClip;
599
600 int nextChildIndex = mDisplayList->children.size();
601 newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
602 mDeferredBarrierType = DeferredBarrierType::None;
603 } else {
604 // standard case - append to existing chunk
605 mDisplayList->chunks.back().endOpIndex = insertIndex + 1;
606 }
607 return insertIndex;
608}
硬件加速是canvas.drawxxx()方法最终调用的都是 RecordingCanvas.cpp中相应的方法添加一个 BaseOpType 到 RecordingCanvas 中的 DisplayList中
drawRenderNode会向RenderNode中的DisplayList的LsaVector<NodeOpType*> children
中添加一个op记录,遍历获取RenderNode的child就是从这里取出
到以上代码为止, 显示列表DisplayList的更新动作就完成了.