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