第5章 硬件加速
从Android 3.0 (API level 11)开始,Android 2D渲染管道能更好的支持硬件加速。硬件加速通过在View的Canvas上使用GPU执行各种绘画操作。因为硬件加速需要消耗更多的资源,所以你的App需要更多的内存。开启硬件加速最简单的方法是在整个应用全局设置。如果应用只使用标准的View和Drawable,全局设置不会产生不利的影响。然而,因为硬件加速不支持所有的2D绘制操作,开启这个功能会影响一些自定义View或者绘制调用。问题显示为不可见的元素、异常,或者像素渲染错误。为了补救这些,在以下几个层面中Android提供给你选项开启或者关闭硬件加速:
Application
Activity
Window
View
如果你的应用执行自定义绘制,当你开启硬件加速后,可以在实际的硬件设备上去测试应用并查找问题。
5.1 控制硬件加速
你可以在以下层面控制硬件加速
Application
Activity
Window
View
5.1.1 Application 级别
在你的Android manifest文件里,添加以下属性到<application>标签里,针对整个应用开启硬件加速,如代码清单5-1所示:
<application android:hardwareAccelerated="true" ...>
代码清单5-1
5.1.2 Activity级别
如果整个应用开启硬件加速表现的不稳定,你也可以针对单个Activity进行控制。在Activity级别开启或者关闭硬件加速,你可以使用android:hardwareAccelerated属性在<activity>标签内。下面是单个Activity中关闭硬件加速的例子,如代码清单5-2所示:
<application android:hardwareAccelerated="true"> <activity ... /> <activity android:hardwareAccelerated="false" /> </application>
代码清单5-2
5.1.3 Window级别
如果你需要更细致的控制,可以对于指定窗口开启硬件加速,如代码清单5-3所示:
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
代码清单5-3
注意: 在window级别不能取消硬件加速。
5.1.4 View 层
通过下面代码,你可以在运行时为个别View关闭硬件加速,如代码清单5-4所示:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
代码清单5-4
注:你不能在view层开启硬件加速。View层还有其他方法关闭硬件加速。
5.2 判断View是否已经硬件加速
有时候,知道一个应用当前是否使用了硬件加速是非常有用的,尤其像自定义View。这非常有用,当你的应用做大量的自定义绘制并且不是所有操作都支持新的渲染管道。
有两个方法查看应用是否使用了硬件加速:
View.isHardwareAccelerated():如果View附属于硬件加速的窗体,将会返回true。
Canvas.isHardwareAccelerated():如果Canvas已硬件加速,将会返回true。
如果你一定要在绘制代码中进行这个查看,请尽可能用Canvas.isHardwareAccelerated()代替View.isHardwareAccelerated()。当一个View附属于硬件加速窗体,它仍可以通过非硬件加速的Canvas来绘制。这种情况,为了缓存目的,可以在实例化时绘制一个View到bitmap中,。
5.3 Android绘制模型
当开启硬件加速,新的绘制模型利用显示列表在界面上渲染你的应用。为了完全理解显示列表和对你的应用有怎样的影响,知道Android不通过硬件加速怎么样绘制View也很重要。下面将阐述基于软件加速和基于硬件加速的绘画模型。
5.3.1 基于软件的绘制模型
在软件绘制模型中,View通过以下两个步骤被绘制:
1、使层失效(Invalidate)
2、绘制层次
每当应用需要更新部分UI时,调用invalidate()(或者它的变形)在任意需要改变内容的view里。这些失效信息被传播始终在view层,计算需要重绘的界面区域。然后,Android系统绘制任意view在这些区域。不幸的是,这种绘制模型有两个缺点:
第一,这种绘制模型在每一次绘画需要执行的大量的代码。比如,如果你的应用在按钮里调用了invalidate(),而且这个按钮在另外一个view之上,此时Android系统会重绘这个view,即时它没有发生改变。
第二,是绘画模型会隐藏你应用的错误。从Android系统开始重绘view,当他们相交的污点区域融合时,即使invalidate()没有被调用,你改变的view的内容可能会被重绘。此时,你依赖的另外一个view使之刷新来达到适当的行为。在你修改你的应用时,这个反应会在任何时候改变。因此,无论你修改数据或者修改状态,你都需要一直在你的自定义view上调用invalidate()。
注意: Android view当它的属性改变时,自动调用invalidate() ,比如背景颜色或者文本内容。
5.3.2 基于硬件的绘制模型
Android仍使用invalidate()和draw()来响应界面更新和视图渲染,不同的是实际处理绘制的时候。Android系统会将他们记录在显示列表,而不是马上执行绘画命令,它包含view层绘制代码的输出。另外一个优化是要对通过调用View.invalidate()的view会被标记,Android系统仅仅会对这些标记的view进行记录和更新显示列表。
还没被invalidate的View通过重新处理之前记录的显示列表进行简单的重绘。这个新绘制模型包括三个阶段:
1、使层失效(Invalidate)
2、记录更新显示列表
3、绘制显示列表
用此模式,你不能依赖与污点区域交叉的view来执行它的draw()。为了确保Android系统记录一个View的显示列表,你必须调用invalidate()。忘记这么做会导致视图看起来一样,甚至在改变它后。一旦发生,这个BUG很容易被发现。使用显示列表也利于动画增强,因为设置特殊的属性,像透明度和旋转,不需要使目标View失效(它自动完成)。这个优化也适用于显示列表的视图 (你应用的任一视图都会硬件加速)比如,假设有个LinearLayout中有个ListView,ListView中有个Button。LinearLayout的显示列表看上去这样:
◆DrawDisplayList(ListView)
◆DrawDisplayList(Button)
假设你现在要改变ListView的不透明,在调用setAlpha(0.5f)后,显示列表变成:
◆SaveLayerAlpha(0.5)
◆DrawDisplayList(ListView)
◆Restore
◆DrawDisplayList(Button)
ListView的设置代码没有被执行。系统只更新显示列表中更简单的LinearLayout。在未开启硬件加速的应用中,列表的绘制代码在其父亲中还会被执行一次。
5.4 不支持的绘画操作
当启动硬件加速, 2D渲染通道支持一般最常用Canvas绘画操作以及一些不常用的操作。所有的绘制操作被用于渲染程序,默认为widget和layout,还有些常见的高级可视化效果,如反光和纹理平铺也是被支持的。下面是不支持硬件加速的操作清单:
Canvas
clipPath()
clipRegion()
drawPicture()
drawTextOnPath()
drawVertices()
Paint
setLinearText()
setMaskFilter()
setRasterizer()
Xfermodes
AvoidXfermode
PixelXorXfermode
另外, 有些操作在硬件加速开启后会发生变化:
Canvas
clipRect()
: XOR
, Difference
和ReverseDifference
裁切模式是被忽略的。3D变换不适用于裁切矩形
drawBitmapMesh()
: 颜色数组被忽略
Paint
setDither()
: 忽略
setFilterBitmap()
: 过滤会一直存在
setShadowLayer()
: 仅适用于文本
PorterDuffXfermode
PorterDuff.Mode.DARKEN
将等价于SRC_OVER当针对
帧缓冲区(framebuffer)混合时。
PorterDuff.Mode.LIGHTEN
将等价于SRC_OVER当针对
帧缓冲区(framebuffer)混合时。
PorterDuff.Mode.OVERLAY
将等价于SRC_OVER当针对
帧缓冲区(framebuffer)混合时
ComposeShader
ComposeShader
只能包含不同类型的阴影(可以为一个BitmapShader
和一个LinearGradient
,但不能是两个BitmapShader实例
)
ComposeShader
不能包含一个ComposeShader
如果你的应用受缺失属性和限制的影响,你可以关闭硬件加速,在你受影响的部分调用setLayerType(View.LAYER_TYPE_SOFTWARE, null)方法, 你仍可以在其他任一地方利用硬件加速。
5.5 View 层
在Android的不同版本, view已经有能力渲染进入屏幕缓存区内,无论是view的绘制缓存,还是使用 Canvas.saveLayer()。屏幕缓存区,或层,有多种用途。当动画组合视图或者需要应用组合效果时,你可以使用他们获得更好的性能。比如,当临时渲染一个view进入一个层时,你可以使用Canvas.saveLayer()来实现淡入淡出效果,然后使用不透明系数组合回到屏幕中。
Android 3.0(API level 11)开始,通过View.setLayerType()方法可以有更多的控制。这个API有2个参数:layer的类型和可选的Paint对象。你可以使用Paint参数来应用滤镜,特殊的混合模式,或者设置为不透明性到一个层中。view可以使用三种layer类型的其中一种:
LAYER_TYPE_NONE:普通渲染并且不会进入屏幕缓存。这是默认行为。
LAYER_TYPE_SOFTWARE:由软件渲染到位图。
LAYER_TYPE_HARDWARE:如果应用程序开启硬件加速,由硬件渲染到硬件纹理。如果未开启,就同LAYER_TYPE_SOFTWARE一样。
你使用层的类型取决于你的目标:
性能:由硬件渲染到硬件纹理,一旦View被渲染到layer,直到调用invalidate()前,它的绘图代码不会被执行。有些动画,像透明度动画,直接放入layer,由GPU完成非常有效率。
视觉效果:使用硬件或者软件layer类型和Paint,并对View使用特殊的视觉处理。比如,你使用ColorMatrixColorFilter绘制一个View为去色的效果。
兼容性:使用软件layer类型促使view软件渲染。如果硬件加速的,有着渲染问题, 这是一个简单的方法来绕过限制的硬件渲染管道。
5.5.1 View层和动画
开启硬件加速,硬件层提供提供更快的和更平滑的动画效果。当有很多绘图操作时,动画是不能一直保持每秒60帧的。通过渲染为硬件纹理,硬件层可以减轻这个压力。硬件纹理可以优化View,不会让视图一直不断的重绘自己。当你调用invalidate()或者改变view属性时,view才会重绘。如果动画显示的不够平滑,考虑在你使用动画的View上开启硬件层。
当view进入后台硬件层,他的属性由屏幕上混合层处理。设置这些属性很有效果,因为他们不需要view失效或者重绘。以下属性作用于混合层。通过setter测试属性,获得最优效果:
alpha:改变层透明度
x,y,translationX,translationY:改变层位置
scaleX,scaleY:改变层大小
rotation, rotationX, rotationY:改变层的三维方向
pivotX,pivotY:改变层的转换起始点
当view被作为ObjectAnimator启用时,这些属性被使用其他名字。如果你想访问这些属性,请适当的调用setter或者getter。比如,为了改变alpha属性,调用setAlpha()。下面的代码片段展示了在3D的Y轴上旋转view最有效的方法,如代码清单5-5所示:
view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator.ofFloat(view, "rotationY", 180).start();
代码清单5-5
因为硬件层消耗影像资源,官方强烈推荐只在持续动画时开启,并且在动画结束时关闭。你可以使用动画监听完成,如代码清单5-6所示:
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-6
5.6 提示和技巧
开启硬件加速2D图像可以立即提高性能, 但你仍可以设计你的应用更有效的使用GPU性能:
1.减少应用中View的数量
View越多,速度越慢. 请求软件渲染通道也越慢。减少View是优化UI的最简单的办法。
2.避免透支
不要绘制太多的层。去除那些被完全遮挡住的图层。如果你要绘制相互叠加的多个图层,考虑将他们合并为一个图层。
3.不要在draw方法中创建render对象
一个通常的错误是每次渲染方法被调用时创一个Paint对象或者Path对象。这使得垃圾收集器运行的更频繁,忽视了缓存,并通过硬件通道优化。
4.不要太频繁的改变图形
图形实例化时,复杂的形状,如路径(Path)和圆圈是由纹理 mask渲染的。任何时候你创建或修改路径, 硬件通道创建一个新的mask,这是非常耗资源的。
5.不要太频繁的改变位图
任何时候你改变一个位图的内容,在下一次绘制时,它会作为GPU纹理被重新载入。
6。.使用透明度要小心
当你通过setAlpha()来改变透明度时,AlphaAnimation或者ObjectAnimator,在屏幕缓冲被渲染,它使用两倍的填充率。当应用alpha在非常大的View上时,考虑设置View属性LAYER_TYPE_HARDWARE。