DisplayList是一个神秘的存在,因为它在Java层没有对应的实体,但是却出现在了View的绘制流程了。在前面的文章中也提到过这个类。在View的绘制流程中,有一个很重要的方法updateDisplayListIfDirty,但是单纯只从Jave层看,却很难理解它做了什么
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
if (!canHaveDisplayList()) {
// can't populate RenderNode, don't try
return renderNode;
}
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.hasDisplayList()
|| (mRecreateDisplayList)) {
// Don't need to recreate the display list, just need to tell our
// children to restore/recreate theirs
if (renderNode.hasDisplayList()
&& !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()
mRecreateDisplayList = true;
int width = mRight - mLeft;
int height = mBottom - mTop;
int layerType = getLayerType();
// Hacky hack: Reset any stretch effects as those are applied during the draw pass
// instead of being "stateful" like other RenderNode properties
renderNode.clearStretch();
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
try {
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;
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
} else {
draw(canvas);
}
}
} finally {
renderNode.endRecording();
setDisplayListProperties(renderNode);
}
} else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}
这个方法是在Java中为数不多的涉及到DisplayList地方,但是让我们难以理解它是如何更新? 因为整个流程中看不到修改的代码,也没有DisplayList身影,我们能够明白的是,在一系列条件满足后,mRecreateDisplayList 会被设置为true, 然后开始了一次canvas绘制操作。 没错,canvas绘制就是在更新DisplayList,只不过这所有的事情都在C层完成了,所以在Java层没有体现。但是即便进入到C层,我们会发现它也是一个很神秘的存在。这篇文章将会揭示关于DisplayList那些隐晦的逻辑。
1 定义
在C层关于DisplayList又两个头文件
frameworks/base/libs/hwui/DisplayList.h
frameworks/base/libs/hwui/pipeline/skia/SkiaDisplayList.h
在分析RenderCanvas那篇文章中,我们也提到到过SkiaDisplayList.也简单分析了它的绘制逻辑。但是它更DisplayList是什么关系呢?是继承关系吗?我们从源码来来分析一下
frameworks/base/libs/hwui/DisplayList.h
using DisplayList = SkiaDisplayListWrapper;
从源码中可以看到,DisplayList原来只是SkiaDisplayListWrapper的别称,因此我们看看SkiaDisplayListWrapper
class SkiaDisplayListWrapper {
public:
// Constructs an empty (invalid) DisplayList
explicit SkiaDisplayListWrapper() {}
// Constructs a DisplayList from a SkiaDisplayList
explicit SkiaDisplayListWrapper(std::unique_ptr<skiapipeline::SkiaDisplayList> impl)
: mImpl(std::move(impl)) {}
...
}
这是一个wrapper包装类,它包装的是一个SkiaDisplayList类型的mImpl对象,也就是说业务层其实并不会直接使用SkiaDisplayList,而是会使用包装器对象,但是起作用的其实还是mImpl这个SkiaDisplayList对象。我看一个简单的包装逻辑
[[nodiscard]] bool hasContent() const {
return mImpl && !(mImpl->isEmpty());
}
可以看到,wrapper方法内部都是在调用mImpl的方法。
SkiaDisplayListWrapper 也提供值将内部的mImpl转换成SkiaDisplayList方法
[[nodiscard]] skiapipeline::SkiaDisplayList* asSkiaDl() {
return mImpl.get();
}
[[nodiscard]] const skiapipeline::SkiaDisplayList* asSkiaDl() const {
return mImpl.get();
}
因此我们在代码中可能更多的看到DisplayList这个包装者,而不是SkiaDisplayList。
2 DisplayList的角色
DisplayList在整个UI框架中起的是作用可以概括位2个,
- 是维持绘制的树形结构。在RendNode中,我们说它也是一个树形的结构,但是并没有看到相对应的定义,比如children字段,因为这个树状结构是定义在DisplayList中的。
- 保存绘制指令。在SkiaDisplayList中有一个mDisplayList的DisplayListData类型的成员,所有的绘制指令保存在这个对象的fBytes二进制数据中。这个将在介绍RecordingCanvas的文章中进行分析
本文主要分析分析一下树形结构。
frameworks/base/libs/hwui/pipeline/skia/SkiaDisplayList.h
class SkiaDisplayList {
...
std::deque<RenderNodeDrawable> mChildNodes;
...
}
在定义中定义了一个mChildNodes的deque队列,元素类型是RenderNodeDrawable, 这是一个新类,它封装了一个RenderNode,使得它可以通过skia来绘制,简单的来说它持有一个RenderNode,然后可以被绘制。
RenderNodeDrawable::RenderNodeDrawable(RenderNode* node, SkCanvas* canvas, bool composeLayer,
bool inReorderingSection)
: mRenderNode(node)
, mRecordedTransform(canvas->getTotalMatrix())
, mComposeLayer(composeLayer)
, mInReorderingSection(inReorderingSection) {}
它的绘制流程我们先不分析,仅仅需要知道mChildNodes其实就相当于一个RenderNode的队列,从而一个形成了树形的结构。
RenderNode
-DisplayList
-mChildNodes[0]: RenderNodeDrawable
- RendeNode
-mChildNodes[1]: RenderNodeDrawable
- RendeNode
....
3 树形结构的建立流程
这个一节来分析一下这棵树的建立过程
3.1 Root
在C层,有一个专门充当Root的RenderNode,那就是RootRenderNode,它继承自RenderNode, 它是在绘制初始化的时同步初始化的,并由Java层的HardwareRender持有。
frameworks/base/graphics/java/android/graphics/HardwareRenderer.java
public HardwareRenderer() {
ProcessInitializer.sInstance.initUsingContext();
mRootNode = RenderNode.adopt(nCreateRootRenderNode());
mRootNode.setClipToBounds(false);
mNativeProxy = nCreateProxy(!mOpaque, mRootNode.mNativeRenderNode);
if (mNativeProxy == 0) {
throw new OutOfMemoryError("Unable to create hardware renderer");
}
Cleaner.create(this, new DestroyContextRunnable(mNativeProxy));
ProcessInitializer.sInstance.init(mNativeProxy);
}
frameworks/base/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, jobject clazz) {
RootRenderNode* node = new RootRenderNode(std::make_unique<JvmErrorReporter>(env));
node->incStrong(0);
node->setName("RootRenderNode");
return reinterpret_cast<jlong>(node);
}
class RootRenderNode : public RenderNode {
public:
explicit RootRenderNode(std::unique_ptr<ErrorHandler> errorHandler)
: RenderNode(), mErrorHandler(std::move(errorHandler)) {}
...
}
3.1 添加节点
绘制树是在绘制的时候创建,因此只有ViewGroup才有子控件,所以添加节点是在ViewGroup绘制的时候发生的,在更新updateDisplayListIfDirty的时候,如果需要重建它displayList的时候,会通过的RenderNode重新开始记录
frameworks/base/core/java/android/view/View.java
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
...
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
....
dispatchDraw(canvas);
....
renderNode.endRecording();
setDisplayListProperties(renderNode);
...
return renderNode;
}
此处假设该ViewGroup的添加了某个子控件,于是调用了dispatchDraw方法,将child的变更记录到自己的renderNode的创建的RecrodingCanvas的dislplayList的mChildNodes之后,返回更新后的renderNode,供上一级的ViewGroup作相同的处理。
frameworks/base/core/java/android/view/ViewGroup.java
protected void dispatchDraw(Canvas canvas) {
...
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
...
}
循环调用drawChild方法,传入的canvas是RecrodingCanvas
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
这里就进入非常重要的View.draw(Canvas canvas, ViewGroup parent, long drawingTime) 方法
frameworks/base/core/java/android/view/View.java
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
....
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((RecordingCanvas) canvas).drawRenderNode(renderNode);
} else {
....
}
return more;
}
这里是通过把child的renderNode直接使用drawRenderNode方法画出来,它会进入到jni的方法
frameworks/base/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
{"nDrawRenderNode", "(JJ)V", (void*)android_view_DisplayListCanvas_drawRenderNode},
static void android_view_DisplayListCanvas_drawRenderNode(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr, jlong renderNodePtr) {
Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
canvas->drawRenderNode(renderNode);
}
这个Canvas的类型是SkiaRecordingCanvas
void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) {
// Record the child node. Drawable dtor will be invoked when mChildNodes deque is cleared.
mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier);
....
}
在这里我们看到了drawRenderNode方法会先把这个renderNode 构造成一个RenderNodeDrawable,(RenderNodeDrawable的构造方法在上面已经介绍过了)然后放入到mChildNodes,从而添加到了RenderNode树添加上。
4 总结
本文主要介绍了DisplayList这个神秘的存在以及它的作用
- 记录绘制命令Op - DisplayListData
- 保存绘制树形结构 - mChildNodes
然后着重分析了一下RenderNode树的创建过程,它是ViewGroup在绘制子控件时通过drawRenderNode时将子控件的RenderNode添加到树中的。在Java层重新绘制的,只需要去调用updateDisplayListIfDirty就可以, 因为变更都保存到底层的这棵树了。
网友评论