
在硬件加速渲染环境中,Android应用程序窗口的UI渲染是分两步进行的。第一步是构建DisplayList,发生在应用程序进程的Main Thread中;第二步是渲染DisplayList,发生在应用程序进程的Render Thread中。DisplayList是以View为单位进行构建的,因此每一个View都对应有一个DisplayList。
这里说的DisplayList与Open GL里面的DisplayList在概念上是类似的,不过是两个不同的实现。DisplayList的本质是一个缓冲区,它里面记录了即将要执行的绘制命令序列。这些绘制命令最终会转化为Open GL命令由GPU执行。这意味着我们在调用Canvas API绘制UI时,实际上只是将Canvas API调用及其参数记录在DisplayList中,然后等到下一个Vsync信号到来时,记录在DisplayList里面的绘制命令才会转化为Open GL命令由GPU执行。与直接执行绘制命令相比,先将绘制命令记录在DisplayList中然后再执行有两个好处。第一个好处是在绘制窗口的下一帧时,若某一个VIew的UI没有发生变化,那么就不必执行与它相关的Canvas API,即不用执行它的成员函数onDraw,而是直接复用上次构建的DisplayList即可。第二个好处是在绘制窗口的下一帧时,若某一个VIew的UI发生了变化,但是只是一些简单属性发生了变化,例如位置和透明度等简单属性,那么也不必重建它的DisplayList,而是直接修改上次构建的DisplayList的相关属性即可,这样也可以省去执行它的成员函数onDraw。
窗口的View层级结构是树形结构,那么DisplayList的层级结构自然也是一个以View的树形结构建立起来的Display树形结构,如图所示:

要知道DisplayList的信息并不是直接保存在View中的,Android抽象出了一个RenderNode的概念来和View树中的每一个View一一对应,并保存DisplayList的信息,顾名思义,每一个RenderNode代表了树形结构中的一个节点。
为什么要设计这么一个类出来呢,因为View毕竟只是Framework Java层的概念,在Framework C/C++层是没有View这个概念的,但是底层仍需要一个结构体来保存从上层传来的View信息,比如几何信息和绘制信息等,而保存这些信息的就是RenderNode。
接下来我们就结合源代码来分析Android应用程序窗口视图的DisplayList的构建过程。
Android应用程序窗口UI的绘制过程是从ViewRootImpl类的成员函数draw开始的,它的实现如下所示:
// frameworks\base\core\java\android\view\ViewRootImpl.java
private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
// ......
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (isHardwareEnabled()) {
// ......
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
// ......
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
if (DEBUG_DRAW) {
Log.v(mTag, "drawSoftware return: this = " + this);
}
return false;
}
}
}
// ......
}
在几种情况下,需要进行绘制:
1)、当前需要更新的区域,即ViewRootImpl类的成员变量mDirty描述的脏区域不为空。
2)、窗口当前有动画需要执行,即ViewRootImpl类的成员变量mIsAnimating的值等于true。
3)、accessibilityFocusDirty,无障碍服务相关。
之前已经已经分析过了,当硬件加速是默认开启的,所以这里走的就是硬件绘制流程ThreadedRenderer.draw,而非软件绘制drawSoftware。
1 ThreadedRenderer.draw
// frameworks\base\core\java\android\view\ThreadedRenderer.java
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
// ......
updateRootDisplayList(view, callbacks);
// ......
int syncResult = syncAndDrawFrame(frameInfo);
// ......
}
1)、ThreadedRenderer.updateRootDisplayList函数以传参View为顶层View构建DisplayList树,对于Activity来说顶层View为DecorView。
2)、ThreadedRenderer.syncAndDrawFrame用来绘制新的一帧。
本文分析DisplayList树的构建流程。
2 ThreadedRenderer.updateRootDisplayList
// frameworks\base\core\java\android\view\ThreadedRenderer.java
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
updateViewTreeDisplayList(view);
// ......
if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
try {
// ......
canvas.enableZ();
canvas.drawRenderNode(view.updateDisplayListIfDirty());
canvas.disableZ();
// ......
} finally {
mRootNode.endRecording();
}
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
1)、ThreadedRenderer类的成员函数updateRootDisplayList通过调用另一个成员函数updateViewTreeDisplayList来构建参数view描述的视图的Display List,即图1中的Decor View的Display List。
2)、mRootNodeNeedsUpdate表示是否需要更新另外一个成员变量mRootNode描述的一个Render Node的Display List。
3)、RenderNode.hasDisplayList返回RenderNode是否有Display List,如果返回 false,RenderNode 应该使用 RenderNode.beginRecording 和RenderNode.endRecording 重新记录。没有显示列表的 RenderNode 仍然可以被绘制,但是在其显示列表被更新之前,它不会对渲染内容产生影响。当 RenderNode 不再被任何东西绘制时,系统可能会自动调用 {@link #discardDisplayList()}。 因此,在绘制之前确保 RenderNode 上的 hasDisplayList 为真非常重要。
4)、ThreadedRenderer类的成员变量mRootNode描述的Render Node即为当前窗口的Root Node,更新它的Display List实际上就是要将参数view描述的视图的Display List记录到它里面去,具体方法如下所示:
- 开始记录之前,调用RenderNode.beginRecording,该方法返回一个RecordingCanvas类型的Canvas,在这个Canvas上执行的所有操作都会被记录并且存储在DisplayList上。
- 调用上面获得的RecordingCanvas的成员函数drawRenderNode将参数view描述的视图的Display List绘制在它里面。
- 记录结束后,调用RenderNode.endRecording,取出上述已经绘制好的RecordingCanvas的数据,并且作为上述Render Node的新的Display List。
3 保存绘制命令到RenderNode
3.1 ThreadedRenderer.updateViewTreeDisplayList
// frameworks\base\core\java\android\view\ThreadedRenderer.java
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
updateViewTreeDisplayList(view);
// ......
}
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();
view.mRecreateDisplayList = false;
}
3.2 View.updateDisplayListIfDirty
/**
* Gets the RenderNode for the view, and updates its DisplayList (if needed and supported)
* @hide
*/
@NonNull
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
// ......
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) {
// ......
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();
// ......
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
try {
if (layerType == LAYER_TYPE_SOFTWARE) {
// ......
} else {
// ......
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
// ......
} else {
draw(canvas);
}
}
} finally {
renderNode.endRecording();
setDisplayListProperties(renderNode);
}
// ......
} else {
// ......
}
// ......
}
在三种情况下,View类的成员函数updateDisplayListIfDirty需要重新构建当前正在处理的View或者其子View的Display List:
1)、View类的成员变量mPrivateFlags的值的PFLAG_DRAWING_CACHE_VALID位等于0,这表明上次构建的Display List已经失效。
2)、RenderNode.hasDisplayList函数返回false,表示当前RenderNode没有一个DisplayList,需要重新记录。
3)、VIew.mRecreateDisplayList为true,表示当前View被标记为INVALIDATED,或者其DisplayList已无效,此时必须重绘其DisplayList。
其中,如果View类的成员变量mPrivateFlags的值的PFLAG_DRAWING_CACHE_VALID位不等于0,并且成员变量mRenderNode描述的Render Node内部维护的Display List Data也是有效的,那么就表明上次为当前正在处理的View的UI没有发生变化。但是如果在这种情况下,View类的成员变量mRecreateDisplayList等于false,就说明虽然当前正在处理的View的UI没有发生变化,但是它的子View的UI发生了变化。这时候就需要对这些子View的Display List进行重新构建,并且更新到当前正在处理的View的Display List去。这是通过调用View类的成员函数dispatchGetDisplayList来完成的。
除了上述这种情况,其余情况均表明需要重新构建当前正在处理的View及其子View的Display List。这些Display List的构建过程如下所示:
1)、从当前正在处理的View关联的Render Node获得一个RecordingCanvas。
2)、将当前正在处理的View及其子View的UI绘制命令记录在上面获得的RecordingCanvas中。
3)、将前面已经绘制好的RecordingCanvas的Display List Data提取出来,并且设置为当前正在处理的View关联的Render Node里面去。
接下来会依次分析这三个步骤,但是在此之前,先看下这里View的成员变量mRenderNode。
View.mRenderNode的定义为:
/**
* RenderNode holding View properties, potentially holding a DisplayList of View content.
* <p>
* When non-null and valid, this is expected to contain an up-to-date copy
* of the View content. Its DisplayList content is cleared on temporary detach and reset on
* cleanup.
*/
@UnsupportedAppUsage
final RenderNode mRenderNode;
RenderNode持有View的属性,潜在地持有View内容的DisplayList。
当RenderNode为非空且有效,它被期望包含View内容的最新副本。它的DisplayList内容会在该View暂时从Window上分离的时候被清除,并且在View被清理的时候重置。
在View的构造方法中赋值:
public View(Context context) {
// ......
mRenderNode = RenderNode.create(getClass().getName(), new ViewAnimationHostBridge(this));
// ......
}
3.2.1 RenderNode.create
private RenderNode


439

被折叠的 条评论
为什么被折叠?



