Android 硬件和软件绘制的一些差异分析

1.从简单的现象出发

当布局关系如下图所示时,其中Child1和Child2有重叠

Applictaion关闭硬件绘制

使用软件绘制时,调用invalidate时候可以发现兄弟 Child2也进行了绘制,ParentLayout1的兄弟节点ParentLayout2 虽然执行了dispatchDraw,但是子控件并没有重绘

Applictation打开硬件绘制时

使用硬件绘制时,调用invalidate可以发现仅仅是Child1进行绘制但是Child2没有进行绘制

从上面的现象可以发现,硬件绘制时,即使view有重叠,在刷新ui的时候也可以保持只绘制一个,而且不管是软件还是硬件,父亲的兄弟节点之后的view也不需要重绘,这表明一个问题,使用硬件绘制和软件绘制的时候,方法的调用流程似乎是不一样的。

2.软件绘制和硬件绘制的介绍

软件绘制介绍

软件绘制其实很简单,所有绘制都在主线程,不分绘制和渲染两部

线程:仅仅是主线程

绘制/渲染:canvas直接包图形buffer数据,通过View一级级派发命令,从而改变整个canvas的buffer内容,最终把buffer交给surfaceflinger合成

底层实现:skia图形库

invalidate:从invalidate的view到根ViewGroop这一条线都要重绘(如父控件有内容的话),如果控件有重叠,重绘部分会更多

硬件加速绘制介绍

硬件加速对于android来说目前主流机型都是默认开启的。当开启硬件加速之后,与软件加速有较多不一样

线程:主线程和渲染线程

绘制:在硬件加速过程中与软件绘制完全不同的是draw方法里实际上并没有真的干活,这个时候的canvas更像是一个录音机,录制下所有的绘制命令,当然,如果硬件加速的window中如果设置了某个View为软件绘制,那么也是可以支持的,在硬件加速的绘制过程中,这个操作与软件绘制基本无异。

渲染:当draw方法完成遍历之后,会进入一个渲染信息同步的过程,也就是把主线程记录的绘制信息同步到渲染线程,这个过程是阻塞的。当同步完毕,主线程会重新唤醒,然后根据记录的绘制命令,调用opengl或者是vlukan接口与gpu通信,把绘制命令塞给gpu,gpu根据绘制命令生成画好的GraphicBuffer,再把buffer交给surfaceFlinger合成,最终显示在屏幕中

底层实现:opengl 或者vulkan(opengl和vulkan都是对gpu接口的封装,是跨平台的,这使得开发者不需要关注gpu的实现)

invalidate:只有当前的view需要重绘

对第一节现象的解释:

硬件绘制的时候,每个View在gpu都有对应的渲染节点,相当于每个View都是独立的个体,解耦的,所以仅仅改变某一个View而不影响其他View的时候其他的view是不需要重新调用绘制方法的,

但是对于软件绘制,他的所有view都是无脑的涂在一个bitmap上,如果两个view有重叠,那么必然会影响到另一个,因此软件绘制需要重绘

从这个角度也可以看出硬件渲染的绘制效率比软件高

3.软件绘制和硬件绘制下的BuildLayer方法

当mLayerType为LAYER_TYPE_HARDWARE 时:

builderLayer在硬件加速时实际上是在gpu中创建了一个独立的FBO(Frame buffer object),代表这个View会单独使用一块buffer,这样如果这个View子控和内容没有变化的情况下,就可以把它当成一个整体,在执行属性动画的时候会加快效率,例如:

当mLayerType为LAYER_TYPE_SOFTWARE 时:

builderLayer在软件图层时,实际上就是创建了一个和这个View一样大小的bitmap,并且把内容在这个bitmap上绘制了一遍, 当内容没有变化的时候,即使被要求重绘也可以直接把bitmap绘制在画布上,同样的,在执行动画的时候会加快效率,因为他是一个整块图片,而不是零散的View

recycleViewParent.animate().rotationX(1).withLayer().start();// withLayer 会自动判断

4.用一张函数调用流程图来看软件绘制和硬件绘制的不同

注意下,流程图没法表明函数出栈!(右键新标签打开图就可以看清了)

可以理解成在drawChlid的时候子view在硬件绘制加了一个hook操作,如果是硬件绘制不直接走正常流程,而是先走updateDisplayListIfDirty,如果发现View是softtype那么通过BuildCache来创建bitmap,然后画在硬件的录音机canvas上,如果不是,那么通过创建录音机canvas来走到onDraw dispatchDraw等方法