【Android API指南】动画和图像(6) - 硬件加速

来源:互联网 发布:农村淘宝加盟费多少钱 编辑:程序博客网 时间:2024/05/19 19:42
从Android3.0开始,Android 2D渲染通道被设计成能很好的支持硬件加速。使用GPU的View在Canvas上进行画的操作时都会使用硬件加速。在最新的Android版本里,图形硬件加速及绘制技巧得到了更好的提升。

开启硬件加速最简单的方法就是在整个程序中全局开启它,如果程序只使用标准的View和Drawable的话,全局开启没什么影响,但是硬件加速不支持所有的2D绘制操作,所以全局开启会对自定义组件产生影响。问题常常是出在一些不可见的元素或者错误的渲染像素。为了修复这个问题,你可以有选择的开启硬件加速,可选择的级别有:
  • Application
  • Activity
  • Window
  • View
如果使用自定义绘制,那么需要在真实的设备上测试硬件加速是否有问题。

控制硬件加速

你可以在下面这些级别控制硬件加速:
  • Application
  • Activity
  • Window
  • View
Application 级别
在manifest文件中添加下面属性开启硬件加速:
<application android:hardwareAccelerated="true" ...>
Activity 级别
可以开启全局硬件加速,然后在特定的activity上关闭硬件加速:
<application android:hardwareAccelerated="true">    <activity ... />    <activity android:hardwareAccelerated="false" /></application>
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是否开启了硬件加速

在做自定义绘制的时候,判断一个View是否已经开启硬件加速是很有用的。

下面是两个不同的判断方法:
  • View.isHardwareAccelerated()
  • Canvas.isHardwareAccelerate()
最好使用Canvas.isHardwareAccelerate(),因为一个开启了硬件加速的View也有可能使用一个非硬件加速的Canvas。这种情况发生在,绘制一个view到位图后做为缓存。

Android绘制模型

当硬件加速被开启后,Android使用一种新的绘制模型,使用一个显示列表去渲染程序到屏幕中。了解Android的软件绘制模型和硬件加速绘制模型很有必要。

软件绘制模型
软件绘制模型的步骤是:
1. 使层级无效。
2. 绘制层级。

当程序需要更新UI的一部分时,它会在任何包含被改变内容的View上调用invalidate()。这个无效的消息会发送给所有唤醒着的view层,去计算需要重绘的区域。然后Android会去绘制任何与这个脏区域相交的view层级,不幸的是,这种模式有两种缺陷:
  1. 需要更多的代码,比如,当一个button在一个view上时,如果在button上调用invalidate(),那么整个view也需要重绘,即便是这个view没有任何改变。
  2. 另外一问题就是,会在程序中隐藏一些bug。当重绘和脏区域有相交的view时,这个view可能没有调用invalidate()就重绘了。这种情况下,你正依靠其他view的无效去获得这个view的合适状态。而这种状态可能会在你修改代码后发生改变,所以,当你修改影响自定义view的代码时,你应该调用invalidate()来更新数据和状态,而不是依靠其他view。
提示:Android自带view会在属性改变时自动调用invalidate()。

硬件加速绘制模型
Android仍然使用invalidate()和draw()去请求屏幕更新和渲染view。但是实际绘图处理方式不同。不是直接执行绘制命令,而是把指令记录到一个显示列表中,这个列表包含视图层绘制代码输出。另外一个优势是,Android系统仅仅需要记录和更新显示列表,没有被失效的视图被先前记录的显示列表调用并重画,步骤是:
  1. 让层失效
  2. 记录和更新显示列表
  3. 绘制显示列表
使用这个模型,你不能依靠view和脏区域的相交执行draw()方法。为了确保系统记录了一个view的显示列表,你必须调用invalidate()。如果忘记这样做的话,就不会看到改过的效果,这就很容易发现bug。

使用显示列表也可以提高动画性能,比如透明度或者旋转,不需要让目标view失效(它会自动执行)。优化应用于任何开启硬件加速的程序中的view。比如,假设有一个LinearLayout包含一个ListView,上面有一个Button,那么显示列表是这样的:
  • DrawDisplayList(ListView)
  • DrawDisplayList(Button)
现在你想要修改ListView的透明度,调用了setAlpha(0.5f)后,显示列表现在包含内容是这样的:
  • SaveLayerAlpha(0.5)
  • DrawDisplayList(ListView)
  • Restore
  • DrawDisplayList(Button)
ListView中复杂的绘制代码不会被执行,系统仅仅更新了LinearLayout的显示列表。在没有开启硬件加速的程序,list和他的父view的绘制代码都会被执行。

不支持的绘制操作

硬件加速不支持的操作:
  • Canvas
    clipPath()
    clipRegion()
    drawPicture()
    drawTextOnPath()
    drawVertices()
  • Paint
    setLinearText()
    setMaskFilter()
    setRasterizer()
  • Xfermodes
    AvoidXfermode
    PixelXorXfermode
另外,一些操作在硬件加速后表现不一样:
  • Canvas
    clipRect():XOR, Difference和ReverseDifference剪切模式被忽略。3D转换不会应用于剪切矩形。
    drawBitmapMesh():颜色数组被忽略
  • Paint
    setDither():忽略
    setFilterBitmap():过滤器会一直开启
    setShadowLayer():只有text能使用
  • PorterDuffXfermode
    PorterDuff.Mode.DARKEN 当针对framebuffer时和SRC_OVER是等效的。
    PorterDuff.Mode.LIGHTEN 当针对framebuffer时和SRC_OVER是等效的。 
    PorterDuff.Mode.OVERLAY 当针对framebuffer时和SRC_OVER是等效的。 
  • ComposeShader
    ComposeShader 只能包含不同类型的着色器
    ComposeShader 不能包含一个ComposeShader
如果你的程序被任何缺少特性或者限制所影响,你可以在受影响的部分关闭硬件加速。

View层

所有Android版本的view都有能力去渲染屏幕外的缓冲区,不管是使用绘制缓冲,还是使用Canvas.saveLayer()。屏幕外缓冲和层有很多用法,在复杂的动画或者组合效果时,使用它们能取得好的性能,例如,要实现一个渐变效果,你可以使用Canvas.saveLayer()在一个层上临时渲染一个view,然后使用一个不透明的因素混合这个view实现效果。

从Android3.0开始,你可以使用View.setLayerType()对层进行控制。这个API有两个参数:你想使用的layer的类型和Paint对象。你可以使用Paint去应用颜色过滤器,指定混合模型,透明度。view可以使用三个layer类型中的一个:
  • LAYER_TYPE_NONE:默认效果,不支持屏幕外缓冲。
  • LAYER_TYPE_HARDWARE:如果程序已经硬件加速,视图被硬件纹理渲染。如果没有开启硬件加速,效果和下面的LAYER_TYPE_SOFTWARE一样。
  • LAYER_TYPE_SOFTWARE:软件渲染到一个位图。
使用哪个类型取决你的目标:
  • 性能:使用硬件层类型去渲染一个view到硬件纹理。一旦view已经被渲染到layer,直到view调用invalidate()才会执行绘制代码。一些动画可以执行被应用到layer上,这样使用GPU是非常高效的。
  • 视觉效果:使用一个硬件或者软件类型和一个Paint去进行一个特别的视觉效果处理。例如,使用一个ColorMatrixColorFilter可以绘制一个黑白视图。
  • 兼容性:使用软件类型会强制软件渲染一个view。如果一个view是被硬件加速的,就会存在渲染问题,这是一个和硬件渲染限制一起工作的简单方法。
View层和动画
程序已经开启硬件加速的话,硬件层能提供更快更平滑的动画。如果动画比较复杂,要达到60帧每秒的话可能性不是很大,使用硬件层能够缓解渲染的压力。硬件纹理能够被用于动画,而且产生动画时不需要不断的重绘,除非你改变view的属性,那么会调用invalidate(),或者你自己调用invalidate()。如果你的程序动画不够平滑的话,那么考虑开启硬件层吧:

当view被硬件层所支持,那么它的一些属性会被层和屏幕混合处理,设置这些属性将会很高效,因为他们不需要失效和重绘。设置view的下面这些属性不会被重绘:
  • alpha:改变层的透明度
  • x,y,translationX,translationY:改变层的位置
  • scaleX,scaleY:改变层的尺寸
  • rotation,rotatonX,rotationY:改变层的3D方位
  • pivotX,pivotY:改变层的转换原点。
通过属性的set方法实现动画,下面的代码展示了一个沿着Y轴的3D旋转:
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);ObjectAnimator.ofFloat(view, "rotationY", 180).start();
由于硬件层会消耗视频内存,所以动画运行完成后我们要关闭它,你可以使用动画监听完成这个功能:
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()

提示和技巧

在程序中减少view的数量
view越多,绘制越慢,优化UI做简单的办法就减少view。

避免透支
不要叠加绘制太多层。移除那些被掩盖的层,如果必须要重叠的话,原则是总像素不要超过屏幕像素的2.5倍。

不要在绘制方法中创建渲染对象
一种普遍的错误就是在渲染方法中创建一个新的Paint和新的Path。这会让垃圾收集器运行更加频繁,而且也会绕过硬件通道的缓存和优化。

不要频繁修改形状
对于复杂的形状,路径,圆的实例会使用纹理遮罩来渲染,创建和修改这些形状都会创建一个新的遮罩,这样很浪费资源。

不要频繁修改位图
每次修改位图,GPU纹理就会重新加载。

小心使用透明度
当你使用setAlpha(), AlphaAnimation, 或者ObjectAnimator设置透明度时,它还会在屏幕外缓冲区被渲染,这就是两次渲染需求,所以在大视图上使用透明度的时候,最好设置view的层类型为LAYER_TYPR_HARDWARE。

原创粉丝点击