从Android 3.0开始,Android的2D渲染通道开始支持硬件加速.这代表所有的在View的Canvas上的绘制操作都将使用GPU.因为要启用硬件加速增加了资源,所以APP将会消耗更多的内存.
如果Android API的版本>=14的话,那么默认情况下就会启动硬件加速,也可以明确的启动/不启动.如果我们的APP只使用标准的View和Drawable,全部打开硬件加速不会产生任何不利的效果.但是因为硬件加速并不是支持所有的2D绘制操作,开启硬件加速可能会影响一些自定义的View或者绘制方法.问题通常表现为元素不可见,异常或者错误渲染.为了解决这个问题,Android为我们提供了多种开启/不开启硬件加速的等级.稍后介绍这个功能.如果APP需要处理自定义的绘制,那么就需要在真实的硬件条件下测试硬件加速功能.下面会有章节介绍硬件加速已知的问题,并如何避开它们.
我们可以使用下面这些级别来控制硬件加速:Application,Activity,Window,View.下面来依次介绍:
Application:在Manifest文件中添加下列属性到<application>
中就可以为整个APP提供了硬件加速:
<application>
中就可以为整个APP提供了硬件加速:<application android:hardwareAccelerated="true" ...>
<activity>
的android:hardwareAccelerated属性,这个栗子是全局启用但是单个Activity不启用:<application android:hardwareAccelerated="true">
<activity .../>
<activity android:hardwareAccelerated="false" />
</application>
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
当前不能在window级别禁用硬件加速.
myView.setLayerType(View.LAYER_TYPE_SOFTWARE,null);
当前我们不能在view级别启用硬件加速.
有些时候,APP需要知道当前是否启用了硬件加速,特别是对那些自定义的View.如果我们做了很多自定义的绘制,并且并不是所有的渲染管道都支持的时候就应该关注一下这点.有两种方法可以让我们知道APP是否启动了硬件加速:
使用View.isHardwareAccelerated()方法,如果返回true则表示View关联了一个硬件加速的window.
Canvas.isHardwareAccelerated()方法,如果返回true则表示canvas被硬件加速了.
如果我们必须查看的话,尽量使用Canvas.isHardwareAccelerated()方法代替View.isHardwareAccelerated().当view关联了硬件加速的window的时候,它依然可以使用一个没有硬件加速的Canvas.比如我们绘制view到一个高速缓存的位图中的时候.
当硬件加速可用的时候,Android framework采用了一种新的渲染方式:利用display list来渲染我们的APP到屏幕上.要充分的理解display list和它是如何影响我们的APP的,就得先理解Android是如何在没有硬件加速的情况下绘制view的.下面会介绍软件绘制和硬件加速模式.
在软件绘制模式下,绘制一个view需要以下两步:invalidate the hierarchy和draw the hierarchy.不管什么时候只要APP需要刷新它的一部分UI,它就得调用它所在view的invalidate()方法.这个invalidation消息一路沿着view层计算出哪些区域需要重绘(这些需要重绘的区域叫dirtyregion)并传递给Android,然后Android负责重绘它们,但是这种重绘机制有两个缺点:
首先,这种模型需要在每次绘制的时候执行很多代码.栗如:有个button在一个view上,这时候我们的APP调用了这个button的invalidate()方法,这时候计算这个view没有改变,Android还是会重绘它.
其次,这种机制可能会隐藏一些我们APP中的bug.因为当dirty region交叉时,Android将会重绘view,所以有时候有些被更改的View即使没有调用validate()方法也可能会被重绘.当这种事情发生的时候,我们需要为另一个可能被重绘的view保留正确行为.每次修改app的时候这一行为都可以发生改变.因为这样我们在修改影响view绘制的数据或者状态的时候总是应该调用invalidate().
Android在view的属性发生改变的时候,总是会调用invalidate().比如背景色或者TextView中的文本发生了变化.
当需要屏幕更新和渲染view的时候Android使用invalidate()和draw()方法,但是处理实际绘制(actual drawing)的时候却又不同.Android不会直接执行绘制命令,而是将它们记录到display list中,display list包含了view层绘制代码的输出.还有一个优化措施是,android只会为invalidate()方法标记的view来记录和更新display list.还没有被重绘的view可以通过简单的重发前一个display list记录来重新绘制.这种新的绘制模式包含三个阶段:
Invalidate the hierarchy.
Record and update display lists.
Draw the display lists.
通过这个模式,我们就不能指望一个与dirty region相交叉的地方去自动执行它的draw()方法了.为了保证Android记录一个view的display list,我们必须调用invalidate()方法.如果忘记这样做会导致一个view在它改变属性之后还是老样子不变.
使用display list也会是动画性能受益,因为设置指定的属性比如透明度,旋转,都不需要重绘目标view(它会自动执行).这个优化也适用于View(任何启用硬件加速的view).比如,假设有个LinearLayout,包含一个在Button上的ListView.LinearLayout的display list看起来是这样:
DrawDisplayList(ListView)
DrawDisplayList(Button)
假设现在想要修改ListView为不透明.调用setAlpha(0.5f)之后,display list现在是这样的:
SaveLayerAlpha(0.5)
DrawDisplayList(ListView)
Restore
DrawDisplayList(Button)
ListView复杂的绘制代码并没有被执行,Android只是升级了LinearLayout的displaylist.在不启用硬件加速的APP中,这些list中的绘制代码会在ListView和它的父容器中执行两次.
当启用硬件加速的时候,2D渲染管道对很多不常用的操作和常用的Canvas绘制操作支持的一样好.所有的Android附带APP或者默认控件和layout的绘制操作,还有常用高级的视觉效果比如反射和瓷砖纹理都可以被支持.下表描述了不同API等级所支持的操作:
First supported API level | ||||
Canvas | ||||
drawBitmapMesh() (colors array) | 18 | |||
drawPicture() | 23 | |||
drawPosText() | 16 | |||
drawTextOnPath() | 16 | |||
drawVertices() | ✗ | |||
setDrawFilter() | 16 | |||
clipPath() | 18 | |||
clipRegion() | 18 | |||
clipRect(Region.Op.XOR) | 18 | |||
clipRect(Region.Op.Difference) | 18 | |||
clipRect(Region.Op.ReverseDifference) | 18 | |||
clipRect() with rotation/perspective | 18 | |||
Paint | ||||
setAntiAlias() (for text) | 18 | |||
setAntiAlias() (for lines) | 16 | |||
setFilterBitmap() | 17 | |||
setLinearText() | ✗ | |||
setMaskFilter() | ✗ | |||
setPathEffect() (for lines) | ✗ | |||
setRasterizer() | ✗ | |||
setShadowLayer() (other than text) | ✗ | |||
setStrokeCap() (for lines) | 18 | |||
setStrokeCap() (for points) | 19 | |||
setSubpixelText() | ✗ | |||
Xfermode | ||||
PorterDuff.Mode.DARKEN (framebuffer) | ✗ | |||
PorterDuff.Mode.LIGHTEN (framebuffer) | ✗ | |||
PorterDuff.Mode.OVERLAY (framebuffer) | ✗ | |||
Shader | ||||
ComposeShader inside ComposeShader | ✗ | |||
Same type shaders inside ComposeShader | ✗ | |||
Local matrix on ComposeShader | 18 |
在所有版本的Android中,view都有能力渲染到屏幕外缓冲区中,不管是通过使用view的drawing缓存,或者是使用Canvas.saveLayer().屏幕外缓冲区(Off-screen buffers),或者层(layers)有多种用途.我们可以使用它们在实现view的复杂动画或者组合效果的时候获得更好的性能.栗如,我们可以通过使用Canvas.saveLayer()方法临时渲染一个view到一个层中,然后用不透明特性将其组合到屏幕上来实现一个褪色的效果.
从Android 3.0开始,我们可以通过View.setLayerType()方法来决定如何/什么时候使用层.这个API接收两个参数:想要使用的层的类型和一个可选的描述了层该被如何组合的Paint对象.我们可以使用Paint参数来应用一个颜色过滤器,指定混合模式,或者让一个层不透明.一个View可以使用下列三种层类型:
LAYER_TYPE_NONE
这个view使用普通的方法渲染,不会由一个屏幕外缓冲区备份(backed).这是默认的行为.
LAYER_TYPE_HARDWARE
如果APP是硬件加速的,那么这个view会被硬件渲染为一个硬件纹理(hardwaretexture).如果APP不是硬件加速的,那么这个层类型的行为跟下面的LAYER_TYPE_SOFTWARE一样.
LAYER_TYPE_SOFTWARE
这个view被软件渲染为一个Bitmap.
我们使用的层的类型由我们的目的决定(更重视哪个):
性能
这种情况下应该使用一个硬件层类型来渲染一个view为一个硬件纹理.一旦一个view被渲染到了一个layer里,它的绘制代码在调用invalidate()的时候才会被执行.有些动画,比如透明度动画,可以被直接应用于层上,GPU完成这些操作十分的高效.
视觉效果
这种情况应该使用一个硬件或者软件层,然后通过Paint指定一个特殊的视觉效果.栗如:我们可以使用ColorMatrixColorFilter来绘制一个黑白相间的view.
兼容性
这种情况下应该使用一个软件层类型来强制view由软件渲染.如果一个view由硬件渲染,那么可能会存在渲染问题,这是一个规避硬件渲染管道限制简单的方法.
当我们的APP使用硬件加速的时候,硬件层可以提供更快和更流畅的动画体验.当动画很复杂的时候,并不能保证总是保持60帧,这种问题可以通过硬件层将view渲染到一个硬件纹理上来解决.这样就可以通过硬件纹理来完成view的动画,而不用view不断的重绘自己.除非view的属性被改变(这时候会调用invalidate()方法)或者手动调用invalidate()方法,否则view不会重绘.如果我们想要运行的APP的动画并没有获得我们理想中的流畅效果,那么就应该考虑在view上使用硬件层.
当view处在一个硬件层中的时候,它的某些属性将会由该层与屏幕合并的方式决定.设置这些属性的效率很高,因为它们不会导致view被重绘.下面的属性将会影响层合并的方式:
Alpha
修改层的透明度.
x,y,translationX,translation
修改层的位置.
scaleX,scaleY
修改层的尺寸.
rotation,rotationX,rotationY
在3D空间中修改层的方向.
pivotX,pivotY
修改层的变换的原点.
当view使用ObjectAnimator实现动画的时候,可以通过名字指定这些属性.如果我们想要访问这些属性,只需要调用相应的setter和getter.栗如:想要修改alpha,调用setAlpha()方法.下面的代码段演示了在3D下绕Y轴旋转的动画:
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();
使用硬件加速可以增强2D图片的处理性能,但是我们仍然应该通过下面的小技巧设计APP以高效的利用GPU:
减少APP中的view:
绘制越多的view就会越慢.这一原则对于软件渲染通道同样适用.减少view数量是最简单的优化UI的方法了.
避免过度绘制:
不要在层上面叠加太多.应该移除任何被不透明的view盖住的view.如果需要将多个层叠加混合使用,应该考虑将它们放在同一个层里.
不要在绘制方法中创建渲染对象:
一个常见的的错误是在每次调用渲染的方法的时候都创建一个新的Paint或者一个新的Path.这迫使垃圾回收系统更加频繁的运行,同时也绕过了缓存和硬件管道优化.
不要太频繁的修改外形:
复杂的形状,路径和圆形都会使用texture标记渲染.每次创建或者修改一个路径,硬件管道都会创建一个新的标记,这会付出不少代价.
不要太频繁的修改Bitmap:
每次我们修改Bitmap的时候,它都会作为一个GPU texture被重新绘制.
慎用透明度:
当我们通过setAlpha(),AlphaAnimation或者ObjectAnimator修改view的透明度的时候,它都会被渲染到一个屏幕外缓冲区,它会消耗双倍的填充率(fill-rate).当修改一个大的view的透明度的时候,应该考虑设置view层的类型为LAYER_TYPE_SOFTWARE.
硬件加速就是使用GPU来处理图形操作.
硬件加速可以按级别提供给APP,包括Application,Activity,Window,View.