View的硬件加速绘制及绘图缓存机制

硬件加速绘制:

与软件绘制的drawSoftware()对应的是HardwareRenderer.draw(boolean)。在创建HardwareRenderer时,已经创建了一个HardwareCanvas,这个Canvas的绘制目标是EGLSurface。然后调用根View的getDisplayList()获得一个DisplayList对象,最后将这个DisplayList通过HardwareCanvas.drawDisplayList(DisplayList)绘制HardwareCanvas绑定的EGLSurface中,再调用sEgl.eglSwapBuffer(sEglDisplayList,mEglSurface)发布绘制的内容。

那么主要的内容就在View#getDisplayList()中了,getDisplayList()的主要内容有:

1.每个View都有一个DisplayList成员,如果没有则在该方法中创建一个。父控件的DisplayList包含子控件的DisplayList指令序列。

2.通过调用该DisplayList成员的start()方法返回一个HardwareCanvas。

3.计算滚动量mScrollX/Y,并应用到Canvas中。

4.把HardwareCanvas传给draw(Canvas)方法。

5.调用DisplayList.end()。

那么draw(Canvas)方法像软件绘制一样,也是那几个内容,且执行的代码也基本一样:

1.绘制背景

2.onDraw绘制自身内容

3.dispatchDraw(Canvas)

4.绘制scrollBar装饰

不一样的是dispatchDraw(Canvas)中遍历子控件后,把HardwareCanvas传到子控件中的draw(Canvas,ViewGroup,long)方法时,在draw(Canvas,ViewGroup,long)中硬件加速绘制时,动画产生的矩阵不是应用到Canvas,而是应用到DisplayList中,最后也是在draw(Canvas,ViewGroup,long)中调用draw(Canvas);

从代码上看,DisplayList并不会被重用,就算那个View没有invalid标志,也要重新计算和绘制。在硬件加速中,Canvas的绘制目标是DisplayList。

 

软件绘图缓存:

1.无论是否使用缓存,drawSoftware()中都是调用mView.draw(Canvas)

2.绘图缓存的代码就在子控件的draw(Canvas,ViewGroup,long)中,在该方法中调用getDrawingCache(autoScale),其中调用buildDrawingCache()

3.buildDrawingCache()中判断当前快照是否有效,有效则直接返回快照,无效或者没有则重新绘制一个。

 

每个启动了软件缓存机制的View都保存了一个自身的快照(包括子控件),是一个Bitmap,这个快照已经考虑了mScroll的,但是没有考虑控件动画矩阵,控件属性矩阵的,所以在使用该缓存前,先将canvas应用了控件动画矩阵,控件属性矩阵,再调用canvas.drawBitmap(cache);

控件(控件X)绘制快照时,使用一个Canvas(不是从该控件的父控件传过来的),把这个Canvas称作Canvas_A,把父控件传过来的称作Canvas_B,把自身的东西绘制到Canvas_A上,然后把这个Canvas_A传给子控件(控件Y),那么子控件也是先绘制快照(如果没有的话),然后再把快照绘制到控件X#Canvas_A上。最后控件X把快照绘制到Canvas_B上。

如果父控件A有三个子控件,且A为控件树的根View,子控件分别为B,C,D。如果B中调用invalidate(),那么会启动控件树重绘流程。如果该控件树采用软件绘图缓存机制,那么此时B的沿途控件都被设置了invalid,即控件A被设置了invalid标志,则A的快照无效了,B也被设置了invalid标志,B的快照也无效了,C和D的控件的快照有效,那么就要重绘控件树的根View的快照,如果A自身没有实际内容绘制,那么把BCD的快照绘制到A的快照上即可,那么     C和D的就可以直接重用了,B的快照要重新绘制。

 

硬件缓存:

分析getDisplayList(DisplayList,isLayer),isLayer为false

1.判断有无缓存及缓存类型。如果缓存类型为硬件缓存,则通过getHardwareLayer()获得最新的硬件缓存。

2.在getHardwareLayer()中如果这个缓存还有效,则直接返回这个缓存。如果不存在,则通过HardwareRenderer创建一个HardwareLayer,如果无效,则重新绘制这个缓存,通过这个Layer获取一个DisplayList,调用getDisplayList(DisplayList,isLayer),其中isLayer为true,这样getDisplayList(DisplayList,isLayer)就不会使用硬件缓存去绘制DisplayList,而会调用draw(Canva)去绘制DisplayList。

3.用上一步得到的Display去刷新绘制HardwareLayer这个硬件缓存,并返回这个Layer。

4.使用这个从getHardwareLayer()返回的Layer去绘制控件的Display成员,canvas = dislayList.start() ,canvas.drawHardwareLayer(layer,0,0,mLayerPaint)

 

使用绘图缓存:

1.启用缓存:View.setLayerType():LAYER_TYPE_NONE、LAYER_TYPE_SOFTWARE、LAYER_TYPE_HARDWARE

2.获取控件截图,在启用软件缓存的前提下,使用View.getDrawingCache(false)返回一个快照,类型为Bitmap。也自己创建一个Canvas,则要自己创建Bitmap,宽高为View的宽高,Canvas需要考虑mScrollX/Y,使用canvas.translateX/Y(),但是不用考虑动画矩阵。然后Canvas.setBitmap(bitmap),把Canvas传给View.draw(canvas)。

3.如果是启用的硬件缓存,则调用View.getHardWareLayer(),再调用自己调用Canvas.drawHardwareLayer(layer,0,0mPaint)

 

绘图缓存与硬件加速中涉及到的类

软件绘图缓存是一个Bitmap,硬件缓存是一个HardwareLayer,Canvas的绘制目标可以是Bitmap,DisplayList,HardwareLayer。

硬件加速绘制中,涉及到一套抽象类,如下:

HardwareRenderer(硬件加速图形库中间层,为创建硬件加速中所需的Hardware Canvas,DisplayList,HardwareLayer提供工厂方法)

HardwareCanvas(Canvas的子类,添加了硬件加速过程中特有的方法,如:drawDisplayList(), drawHardwarelayer())

DisplayList(一串绘制指令序列)

HardwareLayer(可以看作是硬件加速下的Bitmap)

在Android系统中,以上抽象类的实现为GL20Renderer,GLES20Canvas,GLES20DisplayList,GLES20HardwareLayer。即硬件加速绘制在底层是使用OpenGL ES的,而不是Skia软件图形库

 

绘图缓存的启用原则:

1.控件树复杂或每次绘制的计算量很大的控件,即重量级控件。因为缓存绘制的开销可能大于重绘的计算量

2.控件内容要很少发生变化,即很少被invalidate()或被子控件沿途的invalidate标记。如果绘图缓存后,一次都没用,就没效了,那么缓存绘制的开销可能大于重绘的计算量

3.在上面的原则基础上,父控件频繁改变子控件位置(mWidth/mHeight不变,mLeft/mTop/mRight/mBottom变了)或变换(父控件的mScrollX/Y、控件动画矩阵及属性动画矩阵)时,就要启用子控件的绘图缓存。(在父控件addView()或者removeView()的时候,只要子控件其尺寸未变,缓存质量不变,重新测量布局控件树后,即使mLeft/mTop/mRight/mBottom变了,原来的缓存还是能重用的)

使用ViewGroup.setChildrenDrawingWithCache()可以使子控件启用软件绘图缓存。只是使下一级启用缓存,下下级不会因此启用缓存。

 

Android中使用绘图缓存的例子:AbListView。ListView中,滑动列表过程中,很多item都是只是位置变了,尺寸和内容都没有变化,所以使用ListView中的item,即ListView中的子控件使用绘图缓存,会提高ListView滑动时的性能。

来源:淡淡的宁静

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2019年1月25日
下一篇 2019年1月25日

相关推荐