转载地址
Android应用程序UI硬件加速渲染的Display List构建过程分析
硬件加速绘制
相对于软件绘制,硬件加速绘制可充分利用 GPU 的性能,极大提高了绘制效率。Android Level 14 及以上默认开启硬件加速,硬件加速的控制和详细讲解参考文档:硬件加速。
软件绘制和赢家加速绘制的区别
| 渲染场景 | 纯软件绘制 | 硬件加速 | 加速效果分析 |
|---|---|---|---|
| 页面初始化 | 绘制所有 View | 创建所有 DisplayList | GPU 分担了复杂计算任务 |
在一个复杂页面调用背景透明 TextView.setText(),且调用后其尺寸位置不变 | 重绘脏区所有 View | 重建 TextView 的 DisplayList | 重叠的兄弟节点不需 CPU 重绘, GPU 会自行处理 |
TextView 逐帧播放 Alpha / Translation / Scale 动画 | 每帧都要重绘脏区所有 View | 除第一帧同场景2,之后每帧只更新 TextView 对应 RenderNode 的属性 | 刷新一帧性能极大提高,动画流畅度提高 |
修改 TextView 透明度 | 重绘脏区所有 View | 直接调用 RenderNode.setAlpha() 更新 | 加速前需全页面遍历,并重绘很多 View;加速后只触发 View.updateDisplayListIfDirty(),不再往下遍历,CPU 执行时间可忽略不计 |
Display List
Display List 本质是一个缓冲区,它里面记录了即将要执行的绘制命令序列。这些绘制命令最终会转化为 Open GL 命令由 GPU 执行。与直接执行绘制命令相比,先将绘制命令记录在 Display List 中然后再执行有两个好处。
- 第一:在绘制窗口的下一帧时,若某一个视图的
UI没有发生变化,那么就不必执行与它相关的Canvas API,即不用执行它的成员函数onDraw(),而是直接复用上次构建的Display List即可。 - 第二:在绘制窗口下一帧时,若某一个视图的
UI发生了变化,但只是一些简单的属性发生了变化,例如位置和透明度等简单属性,直接修改Display List的相关属性即可,而不用重新构建,这样也可以不用执行它的成员函数onDraw()。
RenderNode
一个 RenderNode 对应一个 View,包含自身及 子View 的所有 Display List。
硬件加速渲染流程
在硬件加速渲染环境中,Android 应用程序窗口的 UI 渲染是分两步进行的。
- 第一步:构建
Display List,发生在Main Thread中。 - 第二步:渲染
Display List发生在Render Thread中。
构建 Display List
硬件渲染的执行过程,主要在 ThreadRenderer.draw() 方法中实现。
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
// 1.
updateRootDisplayList(view, callbacks);
// 2.
if (attachInfo.mPendingAnimatingRenderNodes != null) {
final int count = attachInfo.mPendingAnimatingRenderNodes.size();
for (int i = 0; i < count; i++) {
registerAnimatingRenderNode(
attachInfo.mPendingAnimatingRenderNodes.get(i));
}
attachInfo.mPendingAnimatingRenderNodes.clear();
// We don't need this anymore as subsequent calls to
// ViewRootImpl#attachRenderNodeAnimator will go directly to us.
attachInfo.mPendingAnimatingRenderNodes = null;
}
// 3.
int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
// 4.
if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
setEnabled(false);
attachInfo.mViewRootImpl.mSurface.release();
// Invalidate since we failed to draw. This should fetch a Surface
// if it is still needed or do nothing if we are no longer drawing
attachInfo.mViewRootImpl.invalidate();
}
if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {
attachInfo.mViewRootImpl.invalidate();
}
}
ThreadRenderer.draw() 方法主要做了如下四件事:
-
- 调用
updateRootDisplayList()方法构建参数View的Display List。该View为 根视图Decor View。
- 调用
-
- 调用
registerAnimatingRenderNode()方法注册动画Render Node。
- 调用
-
- 调用
nSyncAndDrawFrame()方法通知Render Thread渲染下一帧。
- 调用
-
- 如果
nSyncAndDrawFrame()的返回值包含SYNC_LOST_SURFACE_REWARD_IF_FOUND或SYNC_REDRAW_REQUESTED标识时,表明Render Thread可能需要与Main Thread进行信息同步。
- 如果
先来看 updateRootDisplayList() 的实现。
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
updateViewTreeDisplayList(view);
if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
try {
final int saveCount = canvas.save();
canvas.translate(mInsetLeft, mInsetTop);
callbacks.onPreDraw(canvas);
canvas.enableZ();
canvas.drawRenderNode(view.updateDisplayListIfDirty());
canvas.disableZ();
callbacks.onPostDraw(canvas);
canvas.restoreToCount(saveCount);
mRootNodeNeedsUpdate = false;
} finally {
mRootNode.endRecording();
}
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
updateRootDisplayList() 方法中通过调用 updateViewTreeDisplayList() 方法构建视图树上所有视图的 Display List。
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;
}
updateViewTreeDisplayList() 方法中除了设置 mPrivateFlags、mRecreateDisplayList 标识,只调用了 View.updateDisplayListIfDirty() 方法。
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.hasDisplayList()
|| (mRecreateDisplayList)) {
// 不需要重新构建当前 View 的 Display List, 只需要更新或重建 子View 的 Display List
if (renderNode.hasDisplayList()
&& !mRecreateDisplayList) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchGetDisplayList();
return renderNode; // no work needed
}
// 代码运行到这里说明要重建当前 View 及其 子View 的 Display List
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) {
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 (debugDraw()) {
debugDrawFocus(canvas);
}
} else {
draw(canvas);
}
}
} finally {
renderNode.endRecording();
setDisplayListProperties(renderNode);
}
} else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}
若当前 View 自身不需要重建或更新 Display List 则调用 dispatchGetDisplayList() 方法,分发重建 子View Display List。 dispatchGetDisplayList() 方法实现于 ViewGroup。
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);
}
}
final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View child = mTransientViews.get(i);
if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
recreateChildDisplayList(child);
}
}
if (mOverlay != null) {
View overlayView = mOverlay.getOverlayView();
recreateChildDisplayList(overlayView);
}
if (mDisappearingChildren != null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size();
for (int i = 0; i < disappearingCount; ++i) {
final View child = disappearingChildren.get(i);
recreateChildDisplayList(child);
}
}
}
所有正在显示或正在执行动画的 子View 调用 recreateChildDisplayList() 方法。
private void recreateChildDisplayList(View child) {
child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
child.mPrivateFlags &= ~PFLAG_INVALIDATED;
child.updateDisplayListIfDirty();
child.mRecreateDisplayList = false;
}
这里 View.updateDisplayListIfDirty() 方法再次被调用,可以发现这是一个递归调用。递归过程如下:
若在递归过程中发现需要重建或更新 Display List 的 View 则调用 View.draw(Canvas) 方法,并通过 RenderNode.beginRecording()\endRecording() 方法重建或更新其 Display List。
总结 Android UI 绘制流程如下(虚线代表递归):
上图来源于:Android硬件加速原理与实现简介