原文链接:https://developer.android.com/guide/topics/graphics/hardware-accel.html
从Android 3.0(API级别 11)开始,Android 2D渲染管道支持硬件加速,这意味着在View画布(Canvas)上执行的所有绘图操作都使用GPU。因为开启硬件加速会有额外的资源消耗,app可能会占用更多的内存。
硬件加速默认在API级别14(含)以上开启,也可以显式开启。如果你的应用仅仅使用一些标准的View和Drawable,则全局打开硬件加速也不会产生任何不利的绘图效果。但是,因为硬件加速并不支持所有的2D绘图操作,因此开启硬件加速可能会影响一些自定义View或绘图调用。问题通常表现为不可见元素、异常或者错误渲染的像素。为了解决这个问题,Android提供了在多个级别上开启或禁用硬件加速的选项。
如果你的应用做了一些自定义绘制,请在开启硬件加速的真机上测试应用来发现问题。
控制硬件加速
可以在四个级别上控制硬件加速:
- Application
- Activity
- Window
- View
Application级别
标签的hardwareAccelerated
属性为整个应用打开或者关闭硬件加速。优先级最低
Activity级别
可以对Activity进行单独控制。优先级高于Application级别。
Window级别
如果需要更细致的控制,可以对给定Window开启硬件加速。注意:不支持在Window级别关闭硬件加速。
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
View级别
可以在运行时对单个View关闭硬件加速:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
注意:当前不能在View级别开启硬件加速。设置View Layer除了禁用硬件加速外还有别的功能。
判断View是否开启了硬件加速
有两种不同方式检查是否应用开启了硬件加速:
-
View.isHardwareAccelerated()
返回true如果View附着到了开启硬件加速的Window上 -
Canvas.isHardwareAccelerated()
返回true如果Canvas是硬件加速的
如果你自定义View进行绘制的时候需要做检查的话,尽可能优先使用Canvas.isHardwareAccelerated()
。因为,当View附着到开启了硬件加速的Window时,它仍然可能使用非硬件加速的Canvas。比如,为了缓存目的,将View绘制到Bitmap时会发生这种情况。
Android绘制模型
当硬件加速开启时,Android框架将采用新的绘制模型,其利用显示列表将应用程序呈现在屏幕上。为了完全理解显示列表(display lists)以及它们如何影响应用程序,理解Android如何在没有硬件加速的模式下绘制View是非常重要的。
基于软件的绘制模型
在软件绘制模型中,View通过下面的两步绘制出来:
- 使层级失效(invalidate)
- 绘制层级
每当应用程序需要更新部分UI时,它会在任何更改了内容的View上调用invalidate()
(或等效方法)。失效消息在View层级中一路传递,来达到计算出需要重绘的屏幕区域(即脏区域)的目的。Android系统然后重绘在View层级中与脏区域相交的任何View。不幸的是,在这种绘图模型中有两个缺点:
- 第一,这个模型在每次绘制传递的时候需要执行大量代码。比如,如果你的应用程序对一个Button调用
invalidate()
,而那个Button覆盖在另一个View之上,则Android系统将会重绘这个View,即使它什么也没变。 - 第二个问题是绘制模型可能会隐藏一些你应用的bug。因为Android系统会重绘那些与脏区域相交的View,一个你已经改变了内容的View可能会被重绘,即使在其
invalidate()
方法并没有被调用。当发生这种情况时,你正在依靠别的视图失效来获得正确的行为。一旦修改了程序,这种依赖关系可能就会变化。因此,无论何时当你修改数据或者状态影响到了你自定义的View,你都应该调用其invalidate()
方法。
注意:Android View会在其属性发生变化时候自动调用
invalidate()
方法,比如TextView的文本或者背景颜色发生变化。
硬件加速绘制模型
Android系统仍然使用invalidate()
和draw()
来请求屏幕更新以及渲染View,但是处理实际的绘制有不同。Android系统不是立即执行绘制命令,而是将它们记录到包含了View层级的绘制代码输出的显示列表(display lists)中。另一个优化是,Android系统仅仅为那些通过调用invalide()而被标记成脏的View记录和更新显示列表。没有失效的View仅仅通过重新发布之前记录的显示列表来重新绘制。新的绘制模型包含三步:
- 失效View层级
- 记录和更新显示列表
- 绘制显示列表
在这种模型里,你不能依赖于View与脏区域相交来让它的draw()
方法被执行。为了确保Android系统记录View的显示列表,你必须调用其invalidate()
方法。忘记调用会导致View显示的和之前一样,即使它的内容已经更改了。
使用显示列也有利于动画性能,因为设置特定属性(比如alpha或旋转)不要求让目标View失效(自动完成的)。此优化也适用于具有显示列表的View(硬件加速开启时的任何View)。比如,假设有一个LinearLayout包含一个ListView和Button,ListView位于Button的上方。这个LinearLayout的显示列表像这样:
- DrawDisplayList(ListView)
- DrawDisplayList(Button)
假设现在你想要改变这个ListView的透明度。在对ListView调用setAlpha(0.5f)
后,显示列表现在包含:
- SaveLayerAlpha(0.5)
- DrawDisplayList(ListView)
- Restore
- DrawDisplayList(Button)
ListView的复杂的绘制代码不会被执行。相反,系统仅仅更新更简单的LinearLayout的显示列表。在没有开启硬件加速的应用中,这个ListView和它的Parent的绘制代码将会再次执行。
不支持的绘制操作
当硬件加速时,2D渲染管道支持最常使用的Canvas绘图操作以及许多较少使用的操作。支持所有用于渲染Android应用程序的绘图操作,默认窗口小部件和布局以及常见的高级视觉效果,例如反射和平铺纹理。
画布缩放(Canvas Scaling)
硬件加速的2D渲染管道是为支持未缩放的绘图而建立的,一些绘图操作在较高的缩放比下会显著降低质量。在缩放比为1.0(未缩放)的情况下,这些操作被实现为纹理绘制(texture drawn),由GPU进行转换。在API级别小于17时,使用这些操作会导致随着缩放比的增加缩放工序增加。
下表显示什么时候支持正确处理大缩放比
需要缩放的绘制操作 | 开始支持的API级别 |
---|---|
drawText() | 18 |
drawPosText() | 不支持 |
drawTextOnPath() | 不支持 |
简单图形 | 17 |
复杂图形 | 不支持 |
drawPath() | 不支持 |
Shadow layer | 不支持 |
注意:简单图形是指使用不带PathEffect的Paint,不包含默认非默认连接(setStrokeJoin() / setStrokeMiter()),利用
drawRect()
,drawCircle()
,drawOval()
,drawRoundRect()
和drawAcr()
(使用userCenter = false)命令绘制而成的图形。其他情况属于复杂图形。
如果你的应用受上面的限制而被影响,你可以对于这些影响点通过调用setLayerType(View.LAYER_TYPE_SOFTWARE, null)
来关闭硬件加速。
视图图层
在Android的所有版本,View都可以渲染到离屏缓冲区中,要么使用View的绘制缓冲区,要么使用Canvas.saveLayer()
。离屏缓冲区或者图层,有几个应用。可以在对复杂View做动画或者应用组合效果的时候利用他们来获得更好的性能。比如,你可以利用Canvas.saveLayer()
临时将View渲染到图层,然后用一个不透明度因子和其组合再渲染回屏幕,来实现渐入渐出效果。
从Android 3.0(API 11)开始,对于何时以及如何使用图层你可以通过View.setLayerType()
方法获得更多的控制能力。这个API有两个参数:图层类型,可选的Paint对象用来描述如何组合图层。你可以用Paint参数将颜色过滤器、特殊混合模式或者不透明度应用到图层。View可以使用下面三个图层类型之一:
- LAYER_TYPE_NONE:View按标准方式渲染,不会由离屏缓冲区支持。默认行为。
- LAYER_TYPE_HARDWARE:应用支持硬件加速时,将会被硬件渲染成硬件纹理。如果应用不支持硬件加速,这个图层类型等效于LAYER_TYPE_SOFTWARE。
- LAYER_TYPE_SOFTWARE:View由软件渲染成Bitmap。
怎么设置图层类型依赖于你的目标:
- 性能:使用硬件层类型将View渲染成硬件纹理。一旦View被渲染进图层,它的绘制代码只有在View调用了
invalidate()
之后才会被执行。一些动画,比如透明度动画,可以直接应用到图层,这对于GPU来说是非常高效的。 - 视觉效果:使用硬件层或者软件层类型加上一个Paint可以对View应用一些特殊的视觉效果。例如,你可以使用
ColorMatrixColorFilter
对View实现黑白效果。 - 兼容性:使用软件层来强制View使用软件的方式绘制。如果一个View是硬件加速的(比如,你的整个应用是硬件加速的),同时有渲染问题,这种方式很容易处理因为硬件渲染管道的限制导致的问题。
视图图层和动画
当应用程序是硬件加速时,硬件层可以带来更快更平滑的动画效果。当对包含很多绘制操作的复杂View做动画想要达到每秒60帧的速度几乎不可能。可以使用硬件层将View渲染成硬件纹理来进行优化。硬件纹理操作可以用作对一个view进行动画操作,当进行动画的时候可以减少对View自身频繁的重绘。除非你改变这个view的属性(调用invalidate()
方法)或者你手动的调用invalidate()
。如果在你的应用中运行一个动画,但是并没有得到你想要的平滑效果,可以考虑为你要动画的view开启硬件层。
当View由硬件层支持时,一些属性由图层在屏幕上的复合方式操控。设置这些属性的时候是很高效的,因为他们不需要View失效或重绘。下面列出的属性会影响图层的叠加。设置这些属性会使View刷新,但是不会重绘目标View。
- alpha:改变图层的不透明度
- x,y,translationX,translationY:改变图层的位置
- scaleX,scaleY:改变图层的大小
- rotation,rotationX,rotationY:在3D空间改变图层的方向
- pivotX,pivotY:改变图层的变换原点
这些属性是使用ObjectAnimator
对View进行动画操作时用到的名字。如果你想要访问这些属性,调用对应的setter和getter方法。比如,想要修改alpha属性,调用setAlpha()
。下面的代码片段展示在3D空间将View绕着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图形可以立即提高性能,但您仍然应该按照以下建议来设计应用程序以有效使用GPU:
- 减少应用中View数量
- 系统需要绘制的View越多,越慢。 对于软件渲染管道也是一样。减少view数量是优化UI的最容易的方法之一。
- 避免过度绘制
- 不要在一个点画太多层。移除那些完全不可见的View。如果你需要绘制几层叠起来,可以考虑将它们合并到一层上。就现在的硬件来看,有一个好的经验就是动画的每帧不要绘制多余屏幕像素2.5倍的像素数量(bimap中的透明像素也计算在内)。
- 不要在draw方法中创建渲染对象
- 一个常见的错误就是当绘制方法被调用的时候,每次都要创建一个新的Paint或者Path。这将迫使垃圾回收器过于频繁的运行,这将对缓冲和硬件的绘制造成影响
- 不要频繁修改形状
- 比如,复杂的图形、路径和圆使用纹理蒙层渲染。每次你创建或修改路径时,硬件管道都会创建一个新的蒙层。这个过程是很昂贵的。
- 不要频繁修改Bitmap
- 你每次改变Bitmap的内容时,下次绘制的时候都会以GPU纹理形式再上传一次。
- 要小心使用alpha通道
- 当你使用
setAlpha()
,AlphaAnimation
或者ObjectAnimator
改变View的透明度时,它都会被渲染到离屏缓冲区中,这将导致双倍的填充率。当对非常大的View应用不透明度效果时,可以考虑设置这个View的图层类型为LAYER_TYPE_HARDWARE
- 当你使用