转载请注明来源:http://blog.csdn.net/goldenfish1919/article/details/36890475
从3.0(API level 11)开始,Android 2D渲染pipeline开始支持硬件加速,这意味着所有的绘制操作都是由使用了GPU的canvas来完成的。
但是启用硬件加速会增加app所需要的资源,app会消耗更多的RAM。
如果你的Target API level是>=14的,那么默认是启用硬件加速度的,但是,你也可以明确的手动来启用。
如果你的app只是使用了标准的view和Drawable,开启硬件加速不会带来任何不好的绘制效果。
但是,因为并不是所有的2D绘制都支持硬件加速,打开硬件加速可能会影响一些自定义的view或者是绘制调用,经常出现的问题有元素不可见、异常、错误渲染的像素。
为了修正这些问题,Android提供了一些选项用来在不同级别上启用或者是禁用硬件加速。
参考“控制硬件加速”章节
如果你的app要自定义绘制,你要在实际的硬件设备上打开硬件加速来测试下,然后找到可能存在的问题。
“不支持的绘制操作”这个章节描述了硬件加速存在的已知的问题和如何来使用它。
控制硬件加速
你可以在下列级别上控制硬件加速:
Application
Activity
Window
View
Application级别
在你的manifest文件中,设置<application>标签的如下属性给整个app启用硬件加速:
<application android:hardwareAccelerated="true" ...>
Activity级别
如果全局启用了硬件加速的情况下,你的应用无法正常工作,你也可以控制单个activity是否启用硬件加速。
在activity级别启用或者禁用硬件加速,可以给<activity>添加android:hardwareAccelerated属性。
下面的例子在整个app级别启用了硬件加速,但是在某个activity上禁用了硬件加速。
<application android:hardwareAccelerated="true">
<activity ... />
<activity android:hardwareAccelerated="false" />
</application>
Window级别
如果你需要更细粒度的控制,你可以用下面的代码给指定的window启用硬件加速:
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
注意: 当前在window级别不能禁用硬件加速。
View级别
不可以用下面的代码在运行时设置单个view禁用硬件加速:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
注意:当前不能在view级别启用硬件加速。View的layer不仅可以禁用硬件加速,有其他的用途。
更多具体的使用信息请参考“View的layer”章节。
view是否被硬件加速了
有时候,知道app是否被硬件加速了是很有用的,尤其是对一些自定义的view来说。
如果你的app做了很多自定义的绘制并且新的渲染pipeline有可能并不支持所有的绘制操作的时候会更有用。
有两种检查app是否被硬件加速的方式:
View.isHardwareAccelerated()返回true,如果view被attached到了硬件加速的window上。
Canvas.isHardwareAccelerated()返回true,如果Canvas是被硬件加速的。
如果你必须要在你的绘制代码中做检查的话,尽可能的使用Canvas.isHardwareAccelerated()而不是View.isHardwareAccelerated()。
当view被attached到一个硬件加速的window上,它仍然可以使用非硬件加速的Canvas来做绘制。
比如说,当为了缓存而把view绘制到bitmap的时候,就会发生这样的情况。
Android的绘图模型
当启用了硬件加速的时候,Android框架会使用一个新的绘图模型,它会使用显示列表(display list)来把app渲染到屏幕上。
为了彻底的理解显示列表和显示列表是如何影响你的app的,首先要明白在没有硬件加速的时候,android是如何绘制view的。
下面的章节介绍了基于软件的和基于硬件加速的绘图模型。
基于软件的绘图模型
在基于软件的绘图模型中,view 的绘制遵循下面两个步骤:
让view树失效(Invalidate the hierarchy)
绘制view树(Draw the hierarchy)
每当app需要更新UI的一部分的时候,它会在包含了更改内容的view上调用invalidate()方法(或者是它的一个变种方法)。
这个invalidation的消息会沿着view的树形结构一直往上传递,然后计算出屏幕上需要被重绘的区域(dirty区)。
然后,Android系统会绘制view树上所有和dirty区有交集的view。很不幸的是,这种绘图模型有两种弊端:
首先,在这种模型下,每一个绘制路径都需要执行大量的代码。比如,如果你的app在一个Button上调用了invalidate(),
如果这个Button是位于另一个view的上面的,android系统也会重绘这个view,尽管它并没有发生任何变化。
第二个问题是这种绘图模型可能会隐藏你app里面的bug。因为android系统会重绘跟dirty区有交集的view,
那么就有可能发生一个你改变了内容的view在你还没有调用invalidate()之前就已经被重绘的情况。
当发生这种情况的时候,你需要依赖其他正在被invalidated的view才能获取正确的行为。
每次你修改你的app的时候都可能发生这样的事。
正是因为这个原因,一旦修改了影响view绘制的数据或者状态的时候,总是需要在自定义的view上调用invalidate()。
注意:Android的view在属性发生变化的时候,会自动调用invalidate(),比如:背景颜色,TextView的文本内容。
硬件加速绘图模型
Android系统仍然使用invalidate()和draw()来请求屏幕更新和渲染view,但是,实际的绘制是不一样的。
android系统会在显示列表内部记录要执行的绘制操作而不是立即就执行这些绘制操作,显示列表包含了view树绘制代码的输出。
另一个优化是,android系统只需要记录和更新通过调用invalidate()标记为dirty的view的显示列表,
那些没有invalidated的view可以简单地通过之前记录的显示列表来进行重绘。
新的绘图模型包含以下三个步骤:
让view树失效(Invalidate the hierarchy)
记录和更新显示列表(Record and update display lists)
绘制显示列表(Draw the display lists)
在这种模型下,就不能依赖view与dirty区有交集来执行draw()方法。为了确保android系统能记录view的显示列表,必须要调用invalidate()。
如果忘记调用,会导致view看起来没有任何变化,就算是view确实被改变了。
使用显示列表对动画的性能也有好处,因为设置特定的属性(比如:alpha或者是rotation)不需要invalidating目标view(这是自动完成的)。
这个优化也应用在有显示列表的view上(启用了硬件加速的app里面的任意的view)
举个例子:假如有一个LinearLayout,包含了一个ListView,下面有一个Button。LinearLayout的显示列表大概是这个样子:
DrawDisplayList(ListView)
DrawDisplayList(Button)
假如现在你想要改变ListView的透明度。在ListView上调用setAlpha(0.5f)以后,显示列表会变成:
SaveLayerAlpha(0.5)
DrawDisplayList(ListView)
Restore
DrawDisplayList(Button)
ListView的复杂的挥绘制代码并没有执行,相反,系统只需要简单的更新LinearLayout的显示列表。
在没有启用硬件加速的app中,list和它的parent的绘制代码都需要重新执行一遍。
不支持的绘制操作
启用硬件加度的情况下,2D渲染pipeline支持:大多数常见和不常见的Canvas绘制操作,android自带的所有的用来渲染应用的绘制操作,默认的组件和布局,还有一些高级的视觉效果,比如:映像,文理。
下面的表格展示了不同API level对不同操作的支持级别:

Canvas缩放
硬件加速2D渲染pipeline一开始只支持不缩放的绘制,因为大的缩放值会严重的降低绘制操作的质量,
比如GPU在以scale 1.0做纹理绘制的时候。在API level小于17的时候,这些操作会导致缩放伪影。
下面的表格展示了什么时间实现的能正确处理大范围缩放:

注意:简单的图形是说用不包含PathEffect和非null join(通过调用setStrokeJoin()/setStrokeMiter())的Paint调用
drawRect(),drawCircle(),drawOval(),drawRoundRect(),和drawArc()(用useCenter=false)命令绘制出来的图形。
其他的就是复杂的图形。
如果你的应用被这样的特性或者是限制所影响,可以在受影响的部分通过调用setLayerType(View.LAYER_TYPE_SOFTWARE, null)来关闭硬件加速。
这种方式使你仍然能够在其他地方利用到硬件加速。
参考“控制硬件加速”章节获取更多如何在app的不同级别启用和禁用硬件加速的信息
View Layers
在所有版本的Android中,view都可以渲染到off-screen的缓冲区里面去,要么通过使用view的绘图缓存,要么使用Canvas.saveLayer()。
Off-screen缓冲区或者叫layers有许多用处,它在复杂view做动画或者是应用复杂的效果的时候可以带来更好的性能。
比如:可以使用Canvas.saveLayer()来实现渐变效果,把view临时渲染到layer中,然后用透明度合成以后显示到屏幕上。
从Android 3.0(API level 11)开始,可以使用View.setLayerType()方法来更好的控制怎样和什么时候使用layer。
这个方法要两个参数:一个是你想要使用的layer的类型,一个是可选的Paint对象,它描述了应该如何合成这个layer。
你可以使用Paint参数来应用颜色过滤、混合效果或者是设置layer的透明度。
view可以使用下面三种layer中的一个:
LAYER_TYPE_NONE:view是按照正常的渲染方式进行渲染,不会使用off-screen缓冲区。这是默认的行为。
LAYER_TYPE_HARDWARE: 如果app启用了硬件加速,view会以硬件方式渲染成硬件texture(The view is rendered in hardware into a hardware texture if the application is hardware accelerated.)。如果app没有启用硬件加速,LAYER_TYPE_HARDWARE的作用和LAYER_TYPE_SOFTWARE是一样的。
LAYER_TYPE_SOFTWARE: view用软件的方式渲染到bitma中。(The view is rendered in software into a bitmap.)
使用哪一种layer取决于你的目标:
性能:使用LAYER_TYPE_HARDWARE把view渲染成硬件texture。view被渲染进layer以后,只有到view调用了invalidate()的时候,它的绘制代码才会被执行。有些动画,比如alpha动画可以直接应用到layer中,这对GPU是非常高效的。
视觉效果:使用LAYER_TYPE_HARDWARE或者是LAYER_TYPE_SOFTWARE和Paint给view应用特殊的视觉效果。比如:可以使用ColorMatrixColorFilter仅以黑色和白色来绘制view。
兼容性:使用LAYER_TYPE_SOFTWARE强制view以软件方式进行渲染。如果被硬件加速的view(比如你的整个app都是硬件加速的)渲染出了问题,这是一种很方便的解决硬件渲染pipeline限制的方式。
View layers和动画
当app启用了硬件加速的时候,Hardware layers可以更快更流畅的传递动画。让一个复杂的需要很多绘制操作的view以每秒60帧做动画很多时候是不可能的,但是使用hardware layers把view渲染成硬件textture可以改善这种状况。硬件textture可用来给view做动画,减少view在动画过程中重绘的需要。除非是改变了会调用view的invalidate()的属性,或者是手动调用了invalidate(),否则view是不会被重绘的。如果在你的app中运行动画的时候,没有得到你想要的流畅的效果,考虑在做动画的view上启用hardware layers。
当view背后有hardware layer做支撑的时候,view的一些属性是以把layer合成到屏幕上的方式进行处理的。设置这样的属性是非常高效的,因为他们不需要view的失效重绘。下面的属性列表会应影响layer合成的方式,调用这些属性的set方法会导致优化的失效(optimal invalidation),因为不需要重绘目标view。
alpha: 改变layer的透明度
x, y, translationX, translationY:改变layer的位置
scaleX, scaleY: 改变layer的大小
rotation, rotationX, rotationY: 改变layer在3D空间中的旋转角度
pivotX, pivotY: 改变layer的变换起点
这些属性是当view使用ObjectAnimator做动画的时候使用的名字,如果你想要访问这些属性,调用他们的set或者是get方法就可以了。
比如,修改alpha属性,可以调用setAlpha()。下面的代码片段展示了在3D中沿着Y轴让view旋转的最有效的方式:
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator.ofFloat(view, "rotationY", 180).start();
因为hardware layers会消耗video memory(这是什么玩意??),极力推荐只有在动画执行期间才启用,动画完成以后要立马禁用。
可以用动画监听来做:
View.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
animator.start();
了解更多关于动画属性的信息,请参考“动画属性”。
提示和技巧
切换到硬件加速2D图像显示可以立马就能提升性能,但是,你仍然要遵守下面的规则来设计你的app,才能更有效地使用GPU:
减少app中view的数量
系统要绘制的view的数量越多,系统运行的就越慢。这个对软件渲染pipeline同样适用。减少view是优化UI的最简单的方式。
避免过度绘制
不要在view上面绘制过多的层。删掉那些完全被上面的不透明的view所遮挡的view。如果你需要绘制很多层的view,考虑合并他们到一个层。
现在的硬件上一个好的经验是不要在屏幕上绘制超过一帧2.5倍的像素。
不要在draw方法中创建渲染对象
一种常见的错误是每当渲染方法调用的时候都去创建一个新的Paint或者Path。
这会强制垃圾收集器更频繁的运行,并且会越过硬件pipeline里面的缓存和优化
不要频繁的修改view的形状
复杂的形状、路径、和环形的实例是使用纹理遮罩效果来渲染的,
每当你创建或者是修改path的时候,硬件pipeline都会创建一个新的遮罩,这个代价是很昂贵的
不要频繁的修改bitmap
每当你修改了bitmap的内容,下次绘制的时候会被当成是一个GPU的纹理被再次上传。
小心的使用alpha
当使用setAlpha(),AlphaAnimation,或者是ObjectAnimator来设置view的透明度的时候,view是在一个off-screen的缓冲区里面进行渲染的,这需要双倍的填充率。
当在很大的view上应用alpha的时候,要考虑设置view的layer type为:LAYER_TYPE_HARDWARE。
总结一下:
(1)硬件加速是从3.0才开始支持的,它改变了android的绘图模型,能提高绘图的性能。
(2)view layer从一开始就支持,在复杂view做动画或者是应用复杂的效果的时候可以带来更好的性能。
(3)view layer的一种类型叫做LAYER_TYPE_HARDWARE,启用了硬件加速就用硬件渲染,不启用就用软件渲染。
(4)总之,做动画或者复杂的显示效果的时候,总是设置LAYER_TYPE_HARDWARE,肯定是没有问题的。如下:
View.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
animator.start();
(5)启用硬件加速已知的一些问题,参考:http://www.verydemo.com/cm.jsp?c=26&u=android-ying-jian-jia-su-wen-ti-he-cuo-wu
项目中要调用支付宝的极简支付,出现了花屏view错乱的情况,禁用硬件加速后解决了。
项目中有个页面非常卡,fps很低,后来发现是给一个很大的view设置了alpha,然后加了一句: view.setLayerType(View.LAYER_TYPE_HARDWARE, null);性能发生了翻天覆地的改善,简直不可思议!
(6)view.isHardwareAccelerated()一定要在attached到window以后调用才能得到,在onCreate(),onResume()都是获取不到的。参考:https://groups.google.com/forum/#!topic/android-developers/vAH0HAZg0uU
附一个android版本号和API level的对应关系表:

原文:http://developer.android.com/guide/topics/graphics/hardware-accel.html
参考:http://blog.csdn.net/leeo1010/article/details/17913341