Android应用程序UI硬件加速渲染的Display List构建过程分析.docx
- 文档编号:9948159
- 上传时间:2023-02-07
- 格式:DOCX
- 页数:70
- 大小:209.12KB
Android应用程序UI硬件加速渲染的Display List构建过程分析.docx
《Android应用程序UI硬件加速渲染的Display List构建过程分析.docx》由会员分享,可在线阅读,更多相关《Android应用程序UI硬件加速渲染的Display List构建过程分析.docx(70页珍藏版)》请在冰豆网上搜索。
Android应用程序UI硬件加速渲染的DisplayList构建过程分析
Android应用程序UI硬件加速渲染的DisplayList构建过程分析
在硬件加速渲染环境中,Android应用程序窗口的UI渲染是分两步进行的。
第一步是构建DisplayList,发生在应用程序进程的MainThread中;第二步是渲染DisplayList,发生在应用程序进程的RenderThread中。
DisplayList是以视图为单位进行构建的,因此每一个视图都对应有一个DisplayList。
本文详细分析这些DisplayList的构建过程。
这里说的DisplayList与OpenGL里面的DisplayList在概念上是类似的,不过是两个不同的实现。
DisplayList的本质是一个缓冲区,它里面记录了即将要执行的绘制命令序列。
这些绘制命令最终会转化为OpenGL命令由GPU执行。
这意味着我们在调用CanvasAPI绘制UI时,实际上只是将CanvasAPI调用及其参数记录在DisplayList中,然后等到下一个Vsync信号到来时,记录在DisplayList里面的绘制命令才会转化为OpenGL命令由GPU执行。
与直接执行绘制命令相比,先将绘制命令记录在DisplayList中然后再执行有两个好处。
第一个好处是在绘制窗口的下一帧时,若某一个视图的UI没有发生变化,那么就不必执行与它相关的CanvasAPI,即不用执行它的成员函数onDraw,而是直接复用上次构建的DisplayList即可。
第二个好处是在绘制窗口的下一帧时,若某一个视图的UI发生了变化,但是只是一些简单属性发生了变化,例如位置和透明度等简单属性,那么也不必重建它的DisplayList,而是直接修改上次构建的DisplayList的相关属性即可,这样也可以省去执行它的成员函数onDraw。
Android应用程序窗口视图是树形结构的,因此它们的DisplayList是从根视图开始构建的,并且子视图的DisplayList包含在父视图的DisplayList中。
这意味着根视图的DisplayList包含了Android应用程序窗口UI所有的绘制命令,因此最后我们只需要对根视图的DisplayList进行渲染即可得到Android应用程序窗口的UI,如图1所示:
Android应用程序窗口的根视图是虚拟的,抽象为一个RootRenderNode。
此外,一个视图如果设置有Background,那么这个Background也会抽象为一个BackgroundRenderNode。
RootRenderNode、BackgroundRenderNode和其它真实的子视图,除了TextureView和软件渲染的子视图之外,都具有DisplayList,并且是通过一个称为DisplayListRenderer的对象进行构建的。
TextureView不具有DisplayList,它们是通过一个称为LayerRenderer的对象以OpenGL纹理的形式来绘制的,不过这个纹理也不是直接就进行渲染的,而是先记录在父视图的DisplayList中以后再进行渲染的。
同样,软件渲染的子视图也不具有DisplayList,它们先绘制在一个Bitmap上,然后这个Bitmap再记录在父视图的DisplayList中以后再进行渲染的。
最后,RootRenderNode的DisplayList被一个称为OpenGLRenderer的对象进行渲染,就得到Android应用程序窗口的UI了。
接下来我们就结合源代码来分析Android应用程序窗口视图的DisplayList的构建过程。
在前面一文提到,Android应用程序窗口UI的绘制过程是从ViewRootImpl类的成员函数performDraw开始的,它的实现如下所示:
[java]viewplaincopy
publicfinalclassViewRootImplimplementsViewParent,
View.AttachInfo.Callbacks,HardwareRenderer.HardwareDrawCallbacks{
......
privatevoidperformDraw(){
......
try{
draw(fullRedrawNeeded);
}finally{
......
}
......
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/ViewRootImpl.java中。
ViewRootImpl类的成员函数performDraw主要是调用了另外一个成员函数draw执行UI绘制工作,后者的实现如下所示:
[java]viewplaincopy
publicfinalclassViewRootImplimplementsViewParent,
View.AttachInfo.Callbacks,HardwareRenderer.HardwareDrawCallbacks{
......
privatevoiddraw(booleanfullRedrawNeeded){
......
finalRectdirty=mDirty;
......
if(!
dirty.isEmpty()||mIsAnimating){
......
if(mAttachInfo.mHardwareRenderer!
=null&&mAttachInfo.mHardwareRenderer.isEnabled()){
......
mAttachInfo.mHardwareRenderer.draw(mView,mAttachInfo,this);
}else{
......
if(!
drawSoftware(surface,mAttachInfo,xOffset,yOffset,scalingRequired,dirty)){
return;
}
}
}
......
}
......
}
这个函数定义在文件frameworks/base/core/java/android/view/ViewRootImpl.java中。
经过一些滚动相关的处理之后,在两种情况下,需要真正地重绘窗口的下一帧。
第一种情况是当前需要更新的区域,即ViewRootImpl类的成员变量mDirty描述的脏区域不为空。
第二种情况下窗口当前有动画需要执行,即ViewRootImpl类的成员变量mIsAnimating的值等于true。
在上述两种情况下,如果ViewRootImpl类的成员变量mAttachInfo指向的一个AttachInfo对象的成员变量mHardwareRenderer的值不为null,并且调用它指向的一个HardwareRenderer对象的成员函数isEnabled的返回值为true,那么就调用这个HardwareRenderer对象的另外一个成员函数draw执行渲染工作。
从前面一文可以知道,当使用硬件加速渲染时,ViewRootImpl类的成员变量mAttachInfo指向的一个AttachInfo对象的成员变量mHardwareRenderer的值不为null,并且它指向的是一个ThreadedRenderer对象。
如果该ThreadedRenderer对象也设置了支持硬件加速渲染,那么调用它的成员函数isEnabled的返回值就为true。
这意味着当使用硬件加速渲染时,ViewRootImpl类的成员函数draw调用的是ThreadedRenderer类的成员函数draw。
另一方面,当使用软件渲染时,ViewRootImpl类的成员函数draw调用的是另外一个成员函数drawSoftware。
软件渲染的执行过程可以参考前面一文。
这里我们只关注硬件渲染的执行过程,因此接下来我们继续分析ThreadedRenderer类的成员函数draw的实现,如下所示:
[java]viewplaincopy
publicclassThreadedRendererextendsHardwareRenderer{
......
@Override
voiddraw(Viewview,AttachInfoattachInfo,HardwareDrawCallbackscallbacks){
......
updateRootDisplayList(view,callbacks);
......
if(attachInfo.mPendingAnimatingRenderNodes!
=null){
finalintcount=attachInfo.mPendingAnimatingRenderNodes.size();
for(inti=0;i registerAnimatingRenderNode( attachInfo.mPendingAnimatingRenderNodes.get(i)); } attachInfo.mPendingAnimatingRenderNodes.clear(); //Wedon'tneedthisanymoreassubsequentcallsto //ViewRootImpl#attachRenderNodeAnimatorwillgodirectlytous. attachInfo.mPendingAnimatingRenderNodes=null; } intsyncResult=nSyncAndDrawFrame(mNativeProxy,frameTimeNanos, recordDuration,view.getResources().getDisplayMetrics().density); if((syncResult&SYNC_INVALIDATE_REQUIRED)! =0){ attachInfo.mViewRootImpl.invalidate(); } } ...... } 这个函数定义在文件frameworks/base/core/Java/android/view/ThreadedRenderer.java中。 ThreadedRenderer类的成员函数draw主要是完成以下四件事情: 1.调用成员函数updateRootDisplayList构建参数view描述的视图的DisplayList,该视图即为图1所示的DecorView。 2.调用成员函数registerAnimatingRenderNode将保存在参数attachInfo指向的一个AttachInfo对象的成员变量mPendingAnimatingRenderNodes描述的一个列表中的RenderNode注册到Native层中去。 这些RenderNode描述的是当前窗口设置的动画。 3.调用成员函数nSyncAndDrawFrame通知RenderThread绘制下一帧。 4.如果成员函数nSyncAndDrawFrame的返回值syncResult的SYNC_INVALIDATE_REQUIRED位不等于0,就表明RenderThread可能需要与MainThread进行信息同步,这时候就时候向MainThread发送一个INVALIDATE消息,以便MainThread可以进行信息同步。 这种情况一般发生在当前绘制的一帧包含有同步动画时。 例如,同步动画显示到一半,需要中止,这个中止的操作就是由MainThread发出的,然后由RenderThread检测到这个中止操作。 这里我们只关注第一件事情,其余三件事情在接下来的两篇文章中再详细分析。 ThreadedRenderer类的成员函数updateRootDisplayList的实现如下所示: [java]viewplaincopy publicclassThreadedRendererextendsHardwareRenderer{ ...... privatevoidupdateRootDisplayList(Viewview,HardwareDrawCallbackscallbacks){ ...... updateViewTreeDisplayList(view); if(mRootNodeNeedsUpdate||! mRootNode.isValid()){ HardwareCanvascanvas=mRootNode.start(mSurfaceWidth,mSurfaceHeight); try{ finalintsaveCount=canvas.save(); ....... canvas.insertReorderBarrier(); canvas.drawRenderNode(view.getDisplayList()); canvas.insertInorderBarrier(); ...... canvas.restoreToCount(saveCount); mRootNodeNeedsUpdate=false; }finally{ mRootNode.end(canvas); } } ...... } ...... } 这个函数定义在文件frameworks/base/core/java/android/view/ThreadedRenderer.java中。 ThreadedRenderer类的成员函数updateRootDisplayList通过调用另一个成员函数updateViewTreeDisplayList来构建参数view描述的视图的DisplayList,即图1中的DecorView的DisplayList。 构建好的这个DisplayList可以通过调用参数view描述的视图的成员函数getDisplayList获得的一个RenderNode来描述。 ThreadedRenderer类的成员变量mRootNodeNeedsUpdate是一个布尔变量,当它的值等于true的时候,就表示要更新另外一个成员变量mRootNode描述的一个RenderNode的DisplayList。 另外,如果ThreadedRenderer类的成员变量mRootNode描述的RenderNode还未构建过DisplayList,那么这时候调用它的成员函数isValid的返回值为true,这种情况也表示要更新它的DisplayList。 从前面一文可以知道,ThreadedRenderer类的成员变量mRootNode描述的RenderNode即为即为当前窗口的RootNode,更新它的DisplayList实际上就是要将参数view描述的视图的DisplayList记录到它里面去,具体方法如下所示: 1.调用ThreadedRenderer类的成员变量mRootNode描述的RenderNode的成员函数start获得一个HardwareCanvas。 2.调用上面获得的HardwareCanvas的成员函数drawRenderNode将参数view描述的视图的DisplayList绘制在它里面。 在绘制参数view描述的视图的DisplayList的前后,会调用HardwareCanvas的成员函数insertReorderBarrier和insertInorderBarrier分别设置一个ReorderBarrier和一个InorderBarrier。 后面我们在分析DisplayList绘制在HardwareCanvas的过程时就会看到,插入这些Barrier是为了将一个View的所有的DrawOp及其子View对应的DrawOp记录在一个Chunk中。 其中,ReorderBarrier表明在真正渲染这些Chunck记录的DrawOp时,需要考虑按照Z轴坐标值重新排列子View的渲染顺序。 3.调用ThreadedRenderer类的成员变量mRootNode描述的RenderNode的成员函数end取出上述已经绘制好的HardwareCanvas的数据,并且作为上述RenderNode的新的DisplayList。 接下来,我们首先分析ThreadedRenderer类的成员变量mRootNode描述的RenderNode的DisplayList的更新过程,即RootNode类的成员函数start、HardwareCanvas类的成员函数drawRenderNode和RootNode类的成员函数end的实现,然后再回过头来分析参数view描述的视图的DisplayList的构建过程,即ThreadedRenderer类的成员函数updateViewTreeDisplayList的实现。 RootNode类的成员函数start的实现如下所示: [java]viewplaincopy publicclassRenderNode{ ...... publicHardwareCanvasstart(intwidth,intheight){ HardwareCanvascanvas=GLES20RecordingCanvas.obtain(this); canvas.setViewport(width,height); ...... returncanvas; } ...... } 这个函数定义在文件frameworks/base/core/java/android/view/RenderNode.java中。 RootNode类的成员函数start的核心是调用GLES20RecordingCanvas类的静态成员函数obtain一个类型为GLES20RecordingCanvas的HardwareCanvas,然后在设置了该HardwareCanvas的ViewPort之后,返回给调用者。 GLES20RecordingCanvas类的静态成员函数obtain的实现如下所示: [java]viewplaincopy [java]viewplaincopy classGLES20RecordingCanvasextendsGLES20Canvas{ ...... privatestaticfinalintPOOL_LIMIT=25; privatestaticfinalSynchronizedPool newSynchronizedPool RenderNodemNode; privateGLES20RecordingCanvas(){ super(); } staticGLES20RecordingCanvasobtain(@NonNullRenderNodenode){ ...... GLES20RecordingCanvascanvas=sPool.acquire(); if(canvas==null){ canvas=newGLES20RecordingCanvas(); } canvas.mNode=node; returncanvas; } ...... } 这个函数定义在文件frameworks/base/core/java/android/view/GLES20RecordingCanvas.java中。 GLES20RecordingCanvas类的静态成员函数obtain首先是从一个GLES20RecordingCanvas对象池中请求一个GLES20RecordingCanvas对象。 如果获取失败,再直接创建一个GLES20RecordingCanvas对象。 在将获取到的GLES20RecordingCanvas对象返回给调用者之前,还会将参数node描述的RenderNode保存在其成员变量mNode中。 接下来我们继续关注GLES20RecordingCanvas对象的创建过程,即GLES20RecordingCanvas类的构造函数的实现。 GLES20RecordingCanvas类的构造函数只是简单调用了父类GLES20Canvas的构造函数,它的实现如下所示: [java]viewplaincopy在CODE上查看代码片派生到我的代码片 classGLES20CanvasextendsHardwareCanvas{ ...... protectedlongmRenderer; ...... protectedGLES20Canvas(){ ...... mRenderer=nCreateDisplayListRenderer(); ...... } ...... } 这个函数定义在文件frameworks/base/core/java/android/view/GLES20Canvas.java中。 GLES20Canvas类的构造函数最主要做的事情就是调用另外一个成员函数nCreateDisplayListRenderer在Native层创建了一个DisplayListRenderer,并且将它的地址保存在成员变量mRenderer中。 GLES20Canvas类的成员函数nCreateDisplayListRenderer是一个JNI函数,由Native层的函数android_view_GLES20Canvas_createDisplayListRenderer实现,如下所示: [cpp]viewplaincopy在CODE上查看代码片派生到我的代码片 staticjlongandroid_view_GLES20Canvas_createDisplayListRenderer(JNIEnv*env,jobjectclazz){ returnreinterpret_cast
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Android应用程序UI硬件加速渲染的Display List构建过程分析 Android 应用程序 UI 硬件加速 渲染 Display List 构建 过程 分析