阅读文章:HenCoder Android 自定义 View 1-8 硬件加速
辅助参考文章:
1、Display List构建过程分析
2、Android硬件加速原理与实现简介
3、关于硬件加速的那么点儿东西
4、《Android高性能编程》 P53 - P56
5、《Android应用性能优化最佳实践》 P63 - P66
首先需要明确的是,什么是硬件加速?
所谓硬件加速,指的是把某些计算工作交给专门的硬件来做,而不是和普通的计算工作一样交给 CPU 来处理。这样不仅减轻了 CPU 的压力,而且由于有了「专人」的处理,这份计算工作的速度也被加快了。这就是「硬件加速」。
而对于 Android 来说,硬件加速有它专属的意思:在 Android 里,硬件加速专指把 View 中绘制的计算工作交给 GPU 来处理。进一步地再明确一下,这个「绘制的计算工作」指的就是把绘制方法中的那些 Canvas.drawXXX() 变成实际的像素这件事。
引用自:HenCoder Android 自定义 View 1-8 硬件加速
**然后涉及到原理的话,**参阅前两篇文章。其中,需要对 DisplayList 有一个深刻且又准确的印象。
注意!注意!注意!
且如果应用程序中只使用了标准 View 或者 Draeable,就可以为整个系统打开硬件加速的全局设置,但并不是所有的 2D 绘制的操作都支持硬件加速,因此开启全局硬件加速可能会对使用自定义控件造成影响,此时就需要不同级别的硬件加速控制,具体操作略过。
在 HenCoder 的文章中,对硬件加速
的知识点说的很形象了,但是对于某些点可能只是点了一下,说的不是很详细,这里把其中一些点提出来,在以辅助参考文章为基础,加以说明:
(1)硬件加速不仅是由于 GPU 的引入而提高了绘制效率,还由于绘制机制的改变,而极大地提高了界面内容改变时的刷新效率。
在硬件加速关闭时,绘制内容会被 CPU 转换成实际的像素,然后直接渲染到屏幕。具体来说,这个「实际的像素」,它是由 Bitmap 来承载的(在硬件加速关闭的时候,Canvas 绘制的工作方式是:把要绘制的内容写进一个 Bitmap,然后在之后的渲染过程中,这个 Bitmap 的像素内容被直接用于渲染到屏幕。)。在界面中的某个 View 由于内容发生改变而调用 invalidate() 方法时,如果没有开启硬件加速,那么为了正确计算 Bitmap 的像素,这个 View 的父 View、父 View 的父 View 乃至一直向上直到最顶级 View,以及所有和它相交的兄弟 View,都需要被调用 invalidate()来重绘。一个 View 的改变使得大半个界面甚至整个界面都重绘一遍,这个工作量是非常大的。
而在硬件加速开启时,绘制的内容会被转换成 GPU 的操作保存下来(承载的形式称为 display list,对应的类也叫做 DisplayList,详细的请参照该文的第二小段),再转交给 GPU。由于所有的绘制内容都没有变成最终的像素,所以它们之间是相互独立的,那么在界面内容发生改变的时候,只要把发生了改变的 View 调用 invalidate() 方法以更新它所对应的 GPU 操作就好,至于它的父 View 和兄弟 View,只需要保持原样。那么这个工作量就很小了。
这里有个点需要注意,不管有没有硬件加速,在屏幕上绘制 UI 的时候,还是需要重新绘制整个屏幕的内容,而不是单单绘制发生变化的内容部分,之所以加速,是因为有了 Display List 把相关的操作缓存下来,而不用每次再去重复操作了。
(2)受到 GPU 绘制方式的限制,Canvas 的有些方法在硬件加速开启式会失效或无法正常工作。具体的方法参见原文 “限制“小节。
如果你的自定义控件中有自定义绘制的内容,最好参照原文的那份表格,确保绘制操作可以正确地在所有用户的手机里能够正常显示。
所有的原生自带控件,都没有用到 API 版本不兼容的绘制操作,可以放心使用。所以你只要检查你写的自定义绘制就好。
(3)View Layer 是 View Layer,硬件加速是硬件加速,两者是两个概念,但是两者存在着交叉的地方。
使用 setLayerType()
方法有什么好处呢,那就是在设置了 View Layer 之后,可以在原基础上(如果没有开启硬件加速,则在没有开启的基础上;如果开启了硬件加速,则在开启了的基础上),更进一步的将 View 的重绘效率提高。
注意,这是以 View 为目标的。虽然整个界面的绘制也是以 View 为粒度,但是这里还是要提出一下,清晰一下概念。
而提高效率的手段,就是使用了 Off-screen buffers
(即离屏缓冲技术,个人也觉得 HenCoder 中说的 Off-screen cache
——屏缓存更合适),当设置了 View Layer 的时候(非 NONE
),它的绘制会被缓存下来,而且缓存的是最终的绘制结果(不是像硬件加速那样只是把 GPU 的操作保存下来再交给 GPU 去计算,因为在硬件加速开启时,需要绘制的内容会被转换成 GPU 的操作保存下来,承载的形式称为 display list,对应的类也叫做 DisplayList,再转交给 GPU,但是所有的绘制内容都没有变成最终的像素,因此每次绘制的时候,还是需要将 DisplayList 中保存的内容转换为最终的像素),只要绘制的内容没有变,那么不论是 CPU 绘制(基于软件绘制模型)还是 GPU 绘制(硬件加速模型)(两种类型说明参考这里的 Android Drawing Models 小节),它们都不用重新计算,而只要使用用之前缓存的绘制结果就可以了。
根据上述描述,所以说是在原基础上更进一步的提高效率。(最开始的我没有把握好 View Layer 与硬件加速的关系,以为他们是包含的关系,所以一直卡在这里没有真正的理解 setLayerType()
方法的作用)。
然后就是 NONE
(用于关闭 View Layer)、SOFTWARE
、HARDWARE
三种类型的区别:
图片内容来自《Android高性能编程》P54
根据上图中的意思,离屏位图
应该是离屏缓冲
的具体表现形式,即对应而 HenCoder 一文中说的那个「地方」:
单独启用一块地方来绘制这个 View ,而不是使用软件绘制的 Bitmap 或者通过硬件加速的 GPU。
这块「地方」可能是一块单独的 Bitmap,也可能是一块 OpenGL 的纹理(texture,OpenGL 的纹理
可以简单理解为图像的意思),具体取决于硬件加速是否开启。
且当参数为 LAYER_TYPE_SOFTWARE
的时候,可以「顺便」把硬件加速关掉而已;并且除了这个方法之外,Android 并没有提供专门的 View 级别的硬件加速开关(注意到,这里 View 级别的),所以
view.setLayerType(LAYER_TYPE_SOFTWARE, null);
就「顺便」成了一个开关硬件加速的方法。
另外参考文章链接:Hardware Acceleration - View Layers 小节
再另外提一点, 在 View 级别上如果设置了 view.setLayerType(LAYER_TYPE_SOFTWARE, null);
会利用 Off-screen buffers
技术在一定程度上提高效率,但是因为会顺便关闭硬件加速,所以是在非硬件加速的基础上(即使用「单独的 Bitmap」)来提高 View 的绘制效率的。
(4)在进行移动、旋转等(无需调用 invalidate())的属性动画的时候开启 Hardware Layer 将会极大地提升动画的效率,因为在动画过程中 View 本身并没有发生改变,只是它的位置或角度改变了,而这种改变是可以由 GPU 通过简单计算就完成的,并不需要重绘整个 View。所以在这种动画的过程中开启 Hardware Layer,可以让本来就依靠硬件加速而变流畅了的动画变得更加流畅。实现方式大概是这样:
view.setLayerType(LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setLayerType(LAYER_TYPE_NONE, null);
}
});
animator.start();
或者如果是使用 ViewPropertyAnimator,那么更简单:
view.animate()
.rotationY(90)
.withLayer(); // withLayer() 可以自动完成上面这段代码的复杂操作
不过一定要注意,只有你在对 translationX translationY rotation alpha 等无需调用 invalidate() 的属性做动画的时候,这种方法才适用,因为这种方法本身利用的就是当界面不发生时,缓存未更新所带来的时间的节省。所以简单地说——
**这种方式不适用于基于自定义属性绘制的动画。**一定记得这句话。
另外,由于设置了 View Layer 后,View 在初次绘制时以及每次 invalidate() 后重绘时,需要进行两次的绘制工作(一次绘制到 Layer,一次从 Layer 绘制到显示屏),所以其实它的每次绘制的效率是被降低了的。所以一定要慎重使用 View Layer,在需要用到它的时候再去使用。
View Layer 可以加速无 invalidate() 时的刷新效率,
但对于需要调用 invalidate() 的刷新无法加速。
View Layer 绘制所消耗的实际时间是比不使用 View Layer 时要高的,所以要慎重使用。
摘选自 HenCoder 一文