动画的硬件加速
好像是3D的都能加速, 2D的部分能加速. 也不知道怎么加速.
以后要总结总结.哪些能加速, 哪些不能加速.
android图形系统详解六:View layer
View layers
在所有版本的Android中,views都有画到离屏缓冲的能力,这包括使用view的绘制cache,或使用Canvas.saveLayer().离屏缓冲,或者说层,有很多用处.你可以使用它们来为复杂的view动画或使用组合效果时提高性能.例如,你可以使用Canvas.saveLayer()实现淡出效果,这个方法会临时的把一个view画到一个layer中然后使用一个透明系数把它组合回屏幕上.
从Android3.0 (API level11)开始,你对何时以及如何通过View.setLayerType()方法来使用layer有了更多的控制能力.此方法有两个参数:你想使用的layer的类型和一个可选的Paint对象,这个对象描述了layer应被如何组合.你可以使用Paint参数来应用颜色过滤,或指定混合模式或不透明度到一个layer.view可以使用以下三个类型之一:
-
LAYER_TYPE_NONE:view按一般方式绘制,不使用离屏缓冲.这是默认的行为.
-
LAYER_TYPE_HARDWARE:如果应用被硬加速了,view会被绘制到一个硬件纹理中.如果应用没被硬加速,此类型的layer的行为同于LAYER_TYPE_SOFTWARE.
-
LAYER_TYPE_SOFTWARE:view被绘制到一个bitmap中.
要使用哪种layer类型取决于你的目标:
-
性能:使用一个硬件layer类型把view画到硬件纹理中.当一个view被画到一个layer中,它的绘制代码直到调用invalidate()时才会被执行.一些动画,比如透明变化的动画,可以直接在layer上应用,这对GPU来说轻而易举.
-
视觉效果:使用硬件或软件layer类型和一个Paint将视觉特效应用到view上.比如,你可以在后台用ColorMatrixColorFilter以黑白色画一个view.
-
兼容性:使用一个软件layer类型来强制view以软件方式绘制.如果一个view是硬加速的(比如,如果你的整个应用是硬加速的),但呈现时出现问题,改用软件方式绘制是一个避开硬件呈现管道限制的最简单的方式.
View layers 和动画
当你的应用被硬加速时,硬件layer可以提供更快更平滑的动画.当向一个有很多绘制操作的复杂的view应用动画时,要永远保证以60fps的速度运行是不可能的.但这可以通过使用硬件layers把view呈现到硬件纹理中来减轻一些负担.硬件纹理之后可以被用来产生动画,避免view为了产生动画而不断的重画自己.view在其属性发生改变而调用invalidate()或你主动调用invalidate()之前不会重画,如果你正在你的应用运行动画但是并不能获得平滑的效果,那么可以考虑在你的动画view上启用硬件layer.
当一个view被硬件layer所支持,它的一些属性的处理就跟layer混合到屏幕上的方式相关了.设置这些属性的操作将变得更高效,因为它们不再导致view变得无效再重绘了.下面列出的就是这些属性,调用这些属性的设置器时会对invalidation操作产生最佳的优化并且不再重画目标view:
-
alpha:改变layer的透明度
-
x, y,translationX, translationY:改变layer的位置
-
scaleX,scaleY:改变layer的大小
-
rotation,rotationX, rotationY: 在3D空间中改变layer的方向.
-
pivotX,pivotY: 改变layer的变换原点坐标.
这些属性是在使用一个ObjectAnimator为view产生动画时使用的.如果你想操作这些属性,调用适当的setter或getter即可.例如,要改变透明属性,调用setAlpha().下面的代码片段演示了让一个View绕Y轴进行三维变换的最佳方法:
- view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- ObjectAnimator.ofFloat(view, "rotationY", 180).start();
因为硬件layers使用显卡内存,所以强烈建议你只在动画期间使用它们并在动画结束后禁止它们.你可以使用动画侦听器来做到:
- 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图形硬加速能立即提升性能,但是你仍然需要按照以下建议来有效使用GPU:
-
减少你应用中views的数量
系统画的views越多,就越慢.这对软件呈现管线来说也是一样.减少views是优化你的UI的最简单的办法.
-
避免过度绘制
不要在彼此的上面画太多layers.移除那些完全被别的不透明view遮盖的view们.如果你需要把多个layer混合画到每个view的上面,应考虑把它们合并到一个layer中.对于当前硬件的一个好原则是画的次数不要超过每帧屏幕上像素的2.5倍(透明像素按位图计数).
-
不要在绘制方法们中创建render对象
一个常见的错误就是在每次调用绘制方法时都创建新的Paint或Path.这强制垃圾收集器运行得更频繁,并且导致高速缓存和硬件管道优化不起作用.
-
不要太频繁地修改shapes
比如混合shapes,paths,和circles时,是使用纹理遮罩呈现的.每次你创建或修改一个path,硬件管道都创建一个新的遮罩,这个代价是很昂贵的.
-
不要太频繁地修改bitmap
你每次改变一个bitmap的内容,你下次去画它时它会作为一个GPU纹理重新上载.
-
小心使用alpha(透明度)
当你用setAlpha(),AlphaAnimation,或ObjectAnimator把一个view设为透明,它将被呈现到一个离屏缓冲中,此时就需要双倍的填充速率.当在一个very大的view上应用透明度时,应考虑把view的layer类型设置为LAYER_TYPE_HARDWARE.
当硬加速被启用,Android框架利用一个新的绘制模式来把你的应用呈现到屏幕,这个模式利用了一个显示列表.要完全理解显示列表以及它如何影响你的应用,先要理解android如何在非硬加速下绘制View.下面的小节描述了软加速的和硬加速的绘制models.
软件绘制模式
在软件绘制模式,view按以下两步进行绘制:
-
1使整个View层级都变得无效
-
2绘制所有的View层级
任何时候一个应用需要更新部分UI时,它应在任何改变了内容的View上调用invalidate()(或它的任一变体),使界面无效的消息在整个View层级中传播以计算应被绘制的屏幕区域(脏区域).之后Android系统绘任View层级中所有与脏区域有交集的View. 不幸的是,这种绘制模式有两个缺点:
-
第一,这个模式需要在每个绘制路径中都执行很多代码.比如,如果你的应用在一个button上调用了invalidate()并且这个button位于另一个view之上,即使这个View没有变化,Android系统也会绘制这个View.
-
第二个问题是这个绘制模式可能隐藏你应用中的bug.因为Android系统会绘制与脏区域有交集的view们,那么一个你改变了内容的view可能在没有被调用invalidate()时也会被重绘了.当这种情况发生时,你只能依赖那个需要重绘的view来获得正确的行为.但这个行为可能在你每次修改你的应用时都会改变.因此,在任何你修改了数据或状态而影响到绘制代码的时候,你总是应该在你的自定义view上调用invalidate().
注:Androidview们会在它们的属性被改变时自动调用invalidate(),比如一个TextView的背景和文本改变时.
硬加速绘制模式
Android系统依然使用invalidate()和draw()来请求屏幕更新并画出views,但是对实际的绘制处理却不一样.现在不是在收到绘制命令立即执行了,而是Android系统把绘制命令记录到显示列表中,这个列表中包含了View层级的绘制代码的输出.另一个优化是Android系统只需为那些通过invalidate()标记为脏的View记录和更新显示列表.没有被invalidated的Views可以简单地使用先前的显示列表中的记录进行重绘.新的绘制模式包含三个阶段:
-
1使整个View层级都无效
-
2记录并更新显示列表
-
3画显示列表
使用此模式,你再不能依靠让View与脏区域交界而使它的draw()方法被调用.要保证Android系统记录下view的显示列表,你必须调用invalidate().忘记这样做会导致一个view总是一个模样,即使改变了它.但这是一个很容易被找出的bug.
使用显示列表还对提升动画性能有益,因为设置了某个属性,比如alpha或rotation等,不再需invalidating目标view(自动做了).这个优化也会应用到拥有显示列表的view上(当你的应用被硬加速时的任何view).例如,假设有一个LinearLayout包含了一个ListView和一个Button,ListView位于Button之上,LinearLayout的显示列表看起来如下:
-
DrawDisplayList(ListView)
-
DrawDisplayList(Button)
假设现在你想改变ListView的opacity,在调用了ListView的setAlpha(0.5f)后,显示列表现在包含如下项:
-
SaveLayerAlpha(0.5)
-
DrawDisplayList(ListView)
-
Restore
-
DrawDisplayList(Button)
ListView复杂的绘制过程没有被执行,而是仅更新了更简单的LinearLayout的显示列表.如果在一个未硬加速的应用中,列表和它爹的绘制代码都会被重新执行.
不支持的绘制操作
当启用了硬加速,2D呈现管道会支持通用的Canvas绘制操作,也支持不常用的操作.所有的绘制操作被用来呈现widgets,layouts以及通用高级视觉效果,比如反光和平铺纹理.下面的列表描述了已知的不能被硬加速支持的绘制操作:
-
Canvas
-
clipPath()
-
clipRegion()
-
drawPicture()
-
drawPosText()
-
drawTextOnPath()
-
drawVertices()
-
-
Paint
-
setLinearText()
-
setMaskFilter()
-
setRasterizer()
-
另外,还有一些操作的行为在启用硬加速后会变得不一样:
-
Canvas
-
clipRect():异或,差异和反差异剪切都被忽略.3D变换不应用到剪切框中
-
drawBitmapMesh():颜色数组被忽略
-
drawLines():反锯齿不被支持
-
setDrawFilter():可以设置,但被忽略
-
-
Paint
-
setDither():被忽略
-
setFilterBitmap():滤镜一直启用
-
setShadowLayer():仅对文本起作用
-
-
ComposeShader
-
ComposeShader只能包含不同类型的着色器(比如一个BitmapShader和一个LinearGradient,不能是两个BitmapShader)
-
ComposeShader不能包含ComposeShader
-
如果你的应用被这些缺少的特性或限制影响了,你可以通过调用setLayerType(View.LAYER_TYPE_SOFTWARE,null)为受影响的部分关闭硬加速.用此方法,你依然可以在其它地方享用到硬加速.
从Android3.0 (API level11)开始,Android的2D显示管道被被设计得更加支持硬加速了.硬加速使用GPU承担了所有在View的canvas上执行的绘制操作.
启用硬加速最简单的的方法是对整个应用启用硬件速.如果你的应用只使用标准的view和Drawable,全局启用硬加速将不会带来任何负面影响.然而,因为硬加速不是被所有的2D绘制所支持,所以启用它时可能对你的自定义绘制产生影响.出现的问题经常是不可见的,也可能是异常,或错误地显示了像素.为了避免这些问题,Android提供了在以下各级别上启用或禁止硬加速的能力:
-
Application
-
Activity
-
Window
-
View
如果你的应用使用了自定义绘制,需要在真实的设备上启用硬加速进行测试来找出问题."不支持的绘制操作"一节描述了已知的不能被硬加速的绘制操作以及如何避开它们.
控制硬加速
你可以在以下级别上分别控制硬加速:
-
Application
-
Activity
-
Window
-
View
Application级
在你的manifest文件中,把以下属性添加到<application>标签来对你的整个应用启用硬加速:
<applicationandroid:hardwareAccelerated="true" ...>
Activity级
如果你的应用在全局启用硬加速时行为不正确,你可以对个别activities单独启用硬加速.欲在actvity级别启用或禁止硬加速,你可以对<activity>元素使用android:hardwareAccelerated属性.下面的例子在整个应用中启用了硬加速但对一个activity禁止了硬加速:
<applicationandroid:hardwareAccelerated="true">
<activity... />
<activityandroid:hardwareAccelerated="false" />
</application>
Window级
如果你需要更高颗粒度的控制,你可以使用以下代码为一个window启用硬加速:
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
注:现在你还不能在window级别禁止硬加速.
Viewlevel
你可以在运行时使用以下代码禁止个别的View的硬加速:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE,null);
注:当前你不能在View级别启用硬加速.View层有除禁止硬加速之外的其它功能.
判定一个View是否能被硬加速
有时一个应用了解是否启用了硬件速是很有用的,对那些自定义View之类的东西尤其重要.在你的应用做了一些不被最新的管线所支持的自定义绘制时这更加重要.
有两种方法可以检查应用是否被硬加速:
-
View.isHardwareAccelerated():如果View附加到一个硬加速的window上就返回true.
-
Canvas.isHardwareAccelerated():如果Canvas被硬加速了就返回true.
如果你必须在你的绘制代码中做这个,应使用Canvas.isHardwareAccelerated()而不是View.isHardwareAccelerated().当一个view附加到一个硬加速的window上,它仍可以使用非硬件速的Canvas进行绘制操作.比如当为了高速缓存而把一个view画到一个bitmap中.