从Android 3.0(API级别11)开始,Android 2D渲染管道支持硬件加速,这意味着在View的画布上执行的所有绘图操作都使用GPU。 由于启用硬件加速所需的资源增加,您的应用程序将消耗更多的RAM。
如果您的目标API级别> = 14,则默认情况下会启用硬件加速,但也可以显式启用硬件加速。 如果您的应用程序仅使用标准视图和Drawables,则将其全局打开不应导致任何不利的绘图效果。 但是,由于所有2D绘图操作都不支持硬件加速,所以打开它可能会影响您的某些自定义视图或绘图调用。 问题通常表现为不可见元素,异常或错误渲染的像素。 为了解决这个问题,Android可以让您选择在多个级别启用或禁用硬件加速。 请参阅控制硬件加速。
如果您的应用程序执行自定义绘图,请在打开硬件加速的实际硬件设备上测试应用程序,以查找任何问题。 不支持的绘图操作部分描述了硬件加速的已知问题以及如何解决这些问题。
一、控制硬件加速
您可以在以下级别控制硬件加速:
1、应用
2、活动
3、窗口
4、视图
一)、应用级别
在您的Android清单文件中,将以下属性添加到
如果您的应用程序在全局开启硬件加速时表现不正确,那么您也可以对其进行单独的活动。 要在活动级别启用或禁用硬件加速,可以使用android:hardwareAccelerated属性作为
如果您需要更精细的控制,您可以使用以下代码为给定的窗口启用硬件加速:
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
四)、视图级别
您可以使用以下代码在运行时禁用单个视图的硬件加速:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
二、确定视图是否硬件加速
应用程序有时会知道它是否正在硬件加速,特别是对于自定义视图等。 如果您的应用程序执行大量自定义绘图,并且新的渲染管道不能正常支持所有操作,这一点尤其有用。
检查应用程序是否硬件加速有两种不同的方法:
1、如果View附加到硬件加速窗口,View.isHardwareAccelerated()返回true。
2、Canvas.isHardwareAccelerated()如果Canvas硬件加速则返回true。
如果您必须在绘图代码中执行此操作,请尽可能使用Canvas.isHardwareAccelerated()而不是View.isHardwareAccelerated()。 当视图附加到硬件加速窗口时,仍然可以使用非硬件加速的画布进行绘制。 例如,当将视图绘制到位图以进行缓存时,会发生这种情况。
三、Android绘图模型
当启用硬件加速时,Android框架利用一个新的绘图模型,利用显示列表将应用程序呈现在屏幕上。 要完全了解显示列表以及它们如何影响应用程序,了解Android如何在没有硬件加速的情况下绘制视图是非常有用的。 以下部分介绍了基于软件和硬件加速的绘图模型。
一)、基于软件的绘图模型
在软件绘图模型中,通过以下两个步骤绘制视图:
1、使层次结构无效
2、绘制层次结构
无论何时应用程序需要更新其UI的一部分,它会在任何已更改内容的视图上调用invalidate()(或其一个变体)。 无效消息一直传播到视图层次结构中,以计算需要重绘的屏幕区域(脏区域)。 然后,Android系统会在与脏区域相交的层次结构中绘制任何视图。 不幸的是,这个绘图模型有两个缺点:
1、首先,这个模型需要在每次绘制过程中执行大量的代码。 例如,如果您的应用程序在按钮上调用invalidate(),并且该按钮位于另一个视图的顶部,则Android系统即使没有更改也会重新绘制视图。
2、第二个问题是绘图模型可以在你的应用程序中隐藏错误。 由于Android系统在与脏区域相交时重新绘制视图,因此即使在其上未调用invalidate(),您更改的内容也可能会被重绘。 当这种情况发生时,您依靠另一个视图无效获得正确的行为。 每次修改应用程序时,都可能会发生这种情况。 因此,当您修改影响视图绘图代码的数据或状态时,您应始终在自定义视图中调用invalidate()。
注意:Android视图在其属性更改时自动调用invalidate(),例如TextView中的背景颜色或文本。
二)、硬件加速绘图模型
Android系统仍然使用invalidate()和draw()来请求屏幕更新和渲染视图,但是以不同的方式处理实际绘图。 Android系统不需要立即执行绘图命令,而是将其记录在显示列表中,其中包含视图层次结构图形代码的输出。 另一个优化是,Android系统只需要记录和更新显示列表,以便通过invalidate()调用标记为dirty的视图。 尚未废用的视图可以通过重新发布先前记录的显示列表来重新绘制。 新绘图模型包含三个阶段:
1、使层次结构无效
2、记录和更新显示列表
3、绘制显示列表
使用此模型,您不能依赖与脏区域相交的视图来执行其draw()方法。 为了确保Android系统记录视图的显示列表,您必须调用invalidate()。 忘记这样做会导致视图看起来一样,甚至在它被改变之后。
使用显示列表也有利于动画性能,因为设置特定的属性(例如Alpha或旋转)不要求使目标视图无效(它会自动完成)。 此优化也适用于带有显示列表的视图(应用程序是硬件加速时的任何视图)。例如,假设有一个LinearLayout包含Button上方的ListView。 LinearLayout的显示列表如下所示:
1、DrawDisplayList(ListView)
2、DrawDisplayList(Buttron)
现在假设您要更改ListView的不透明度。 在ListView上调用setAlpha(0.5f)后,显示列表现在包含:
1、SaveLayerAlpha(0.5)
2、DrawDisplayList(ListView)
3、Restore
4、DrawDisplayList(Button)
ListView的复杂绘图代码未被执行。 相反,系统只更新了更简单的LinearLayout的显示列表。 在没有启用硬件加速的应用程序中,列表及其父代的绘图代码将再次被执行。
四、不支持的绘图操作
当硬件加速时,2D渲染管道支持最常用的Canvas绘图操作以及许多较少使用的操作。 用于渲染随Android提供的应用程序的所有绘图操作,默认小部件和布局以及常见的高级视觉效果(如反射和平铺纹理)均受支持。
下表描述了跨API级别的各种操作的支持级别:
第一个支持的API级别 | ||||
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 |
一)、画布缩放
首先构建了硬件加速2D渲染管道,以支持非标尺绘图,一些绘图操作在较高的尺寸值下显着降低了质量。 这些操作被实现为由GPU变换的规模1.0绘制的纹理。 在API级别<17中,使用这些操作将导致缩放工件随规模而增加。
下表显示何时更改实施以正确处理大规模:
Drawing operation to be scaled | First supported API level |
drawText() | 18 |
drawPosText() | ✗ |
drawTextOnPath() | ✗ |
Simple Shapes* | 17 |
Complex Shapes* | ✗ |
drawPath() | ✗ |
Shadow layer | ✗ |
注意:“简单”形状是带有不具有PathEffect的Paint的drawRect(),drawCircle(),drawOval(),drawRoundRect()和drawArc()(with useCenter = false)),并且不 包含非默认连接(通过setStrokeJoin()/ setStrokeMiter())。 这些绘图命令的其他实例位于上图中的“复杂”下。
如果您的应用程序受到任何缺少的功能或限制的影响,您可以通过调用setLayerType(View.LAYER_TYPE_SOFTWARE,null)来关闭应用程序受影响部分的硬件加速。 这样,您仍然可以利用其他地方的硬件加速。 有关如何在应用程序中启用和禁用不同级别的硬件加速的更多信息,请参阅控制硬件加速。
五、视图层
在所有版本的Android中,视图都可以通过使用视图的绘图缓存或使用Canvas.saveLayer()来呈现到屏幕外缓冲区。 离屏缓冲区或层,有几个用途。 您可以使用它们在动画复杂视图或应用组合效果时获得更好的性能。 例如,您可以使用Canvas.saveLayer()临时将视图渲染到图层中,然后使用不透明度因子将其复制到屏幕上。
从Android 3.0(API级别11)开始,您可以更好地控制如何以及何时使用View.setLayerType()方法使用图层。 此API包含两个参数:要使用的图层类型以及描述图层如何合成的可选Paint对象。 您可以使用Paint参数将颜色过滤器,特殊混合模式或不透明度应用于图层。 视图可以使用三种类型之一:
1、LAYER_TYPE_NONE:视图呈现正常,不受屏幕外缓冲区的支持。 这是默认行为。
2、LAYER_TYPE_HARDWARE:如果应用程序硬件加速,视图将以硬件呈现为硬件纹理。 如果应用程序不是硬件加速,则此层类型的行为与LAYER_TYPE_SOFTWARE相同。
3、LAYER_TYPE_SOFTWARE:视图通过软件呈现为位图。
您使用的图层类型取决于您的目标:
1、性能:使用硬件层类型将视图呈现为硬件纹理。 一旦将视图呈现为图层,则在视图调用invalidate()之前,不必执行其绘图代码。 一些动画,如alpha动画,可以直接应用到图层上,这对GPU来说非常有效。
2、视觉效果:使用硬件或软件层类型和油漆来对视图应用特殊的视觉效果。 例如,您可以使用ColorMatrixColorFilter绘制黑白视图。
3、兼容性:使用软件层类型强制以软件呈现视图。 如果硬件加速的视图(例如,如果整个应用程序被硬件加密),则会出现渲染问题,这是解决硬件渲染管道的限制的简单方法。
一)、视图层和动画
当您的应用程序硬件加速时,硬件层可以提供更快更平滑的动画。 当动画复制视图发出大量绘图操作时,以60帧/秒的速度运行动画并不总是可行的。 这可以通过使用硬件层将视图呈现到硬件纹理来缓解。 然后可以使用硬件纹理来动画化视图,从而消除视图在动画时不断重绘自身的需要。 视图不重绘,除非您更改视图的属性,调用invalidate(),或者手动调用invalidate()。 如果您在应用程序中运行动画,并且不能获得所需的平滑结果,请考虑在动画视图上启用硬件层。
当视图由硬件层支持时,其某些属性将通过屏幕上合成图层的方式进行处理。 设置这些属性将是有效的,因为它们不要求视图无效并重新绘制。 以下属性列表会影响图层的合成方式。 为任何这些属性调用setter将导致最佳无效并且不重新绘制目标视图:
1、alpha:更改图层的不透明度
2、x,y,translationX,translationY:改变图层的位置
3、scaleX,scaleY:更改图层的大小
4、rotation,rotationX,rotationY:更改3D空间中图层的方向
5、pivotX,pivotY:更改图层的变换原点
这些属性是使用ObjectAnimator动画化视图时使用的名称。 如果要访问这些属性,请调用相应的setter或getter。 例如,要修改alpha属性,请调用setAlpha()。 以下代码片段显示了围绕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遵循以下建议:
减少应用程序中的视图数量
系统绘制的视图越多,速度就越慢。 这也适用于软件渲染管道。 缩小浏览量是优化用户界面的最简单方法之一。
避免过度使用
不要在彼此之上画太多层。 删除其上的其他不透明视图完全模糊的任何视图。 如果您需要将多个层叠在一起,请考虑将它们合并成一个单独的层。 当前硬件的一个很好的经验法则是不要画出每帧屏幕上的像素数量的2.5倍(位图计数中的透明像素!)。
不要在绘图方法中创建渲染对象
一个常见的错误是在每次调用渲染方法时创建一个新的Paint或一个新的Path。 这迫使垃圾收集器运行更频繁,并且绕过硬件管道中的缓存和优化。
不要太频繁地修改形状
例如,使用纹理蒙版渲染复杂的形状,路径和圆。 每次创建或修改路径时,硬件管道将创建一个新的掩码,这可能是昂贵的。
不要太频繁地修改位图
每次更改位图的内容时,下次再次将其作为GPU纹理重新上传。
小心使用alpha
当您使用setAlpha(),AlphaAnimation或ObjectAnimator进行半透明的视图时,它会在屏幕外缓冲区中渲染,从而将所需的填充率加倍。 在非常大的视图上应用Alpha时,请考虑将视图的图层类型设置为LAYER_TYPE_HARDWARE。