从 Android 3.0(API 级别 11)开始,Android 2D 渲染管道支持硬件加速,也就是说,在 View
的画布上执行的所有绘制操作都会使用 GPU。启用硬件加速需要更多资源,因此应用会占用更多内存。
如果您的目标 API 级别为 14 及更高级别,则硬件加速默认处于启用状态,但也可以明确启用该功能。如果您的应用仅使用标准视图和 Drawable
,则全局启用硬件加速不会造成任何不良绘制效果。不过,并非所有 2D 绘制操作都支持硬件加速,因此启用硬件加速可能会影响您的部分自定义视图或绘制调用。具体问题通常以不可见的元素、异常或错误渲染的像素显现。为了解决此问题,Android 允许您在多个级别选择是启用还是停用硬件加速。请参阅控制硬件加速。
如果您的应用执行自定义绘制,请在启用硬件加速的实际硬件设备上测试应用,以检查是否存在任何问题。支持绘制操作部分介绍了已知硬件加速问题和相应的解决方案。
另请参阅通过框架 API 支持 OpenGL 和 Renderscript
控制硬件加速
您可以在以下级别控制硬件加速:
- 应用
- activity
- 窗口
- 视图
应用级别
在 Android 清单文件中,将以下属性添加到 <application>
标记中,为整个应用启用硬件加速:
<application android:hardwareAccelerated="true" ...>
activity 级别
如果全局启用硬件加速后,您的应用无法正常运行,则您也可以针对各个 activity 控制硬件加速。如需在 activity 级别启用或停用硬件加速,您可以使用 <activity>
元素的 android:hardwareAccelerated
属性。以下示例展示了如何为整个应用启用硬件加速,但为一个 activity 停用硬件加速:
<application android:hardwareAccelerated="true"> <activity ... /> <activity android:hardwareAccelerated="false" /> </application>
窗口级别
如果您需要实现更精细的控制,可以使用以下代码为给定窗口启用硬件加速:
Kotlin
window.setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED )
Java
getWindow().setFlags( WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
注意:您目前无法在窗口级别停用硬件加速。
视图级别
您可以使用以下代码在运行时为单个视图停用硬件加速:
Kotlin
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
Java
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
注意:您目前无法在视图级别启用硬件加速。除了停用硬件加速之外,视图层还具有其他功能。如需详细了解视图层的具体用途,请参阅视图层。
确定视图是否经过硬件加速
有时,应用有必要了解当前是否经过硬件加速,尤其是对于自定义视图等内容。如果您的应用执行大量自定义绘制,但并非所有操作都得到新渲染管道的正确支持,这就会特别有用。
您可以通过以下两种不同的方式检查应用是否经过硬件加速。
- 如果
View
已附加到硬件加速窗口,则View.isHardwareAccelerated()
会返回true
。 - 如果
Canvas
经过硬件加速,则Canvas.isHardwareAccelerated()
会返回true
如果您必须在绘制代码中执行这项检查,请尽可能使用 Canvas.isHardwareAccelerated()
,而不是 View.isHardwareAccelerated()
。如果某个视图已附加到硬件加速窗口,则仍可以使用未经过硬件加速的画布进行绘制。例如,将视图绘制为位图以进行缓存就会发生这种情况。
Android 绘制模型
启用硬件加速后,Android 框架会采用新的绘制模型,该模型利用显示列表将您的应用渲染到屏幕上。如需充分了解显示列表以及它们可能如何影响您的应用,最好再了解一下 Android 如何在不启用硬件加速的情况下绘制视图。下面几部分介绍了基于软件的绘制模型和硬件加速绘制模型。
基于软件的绘制模型
在软件绘制模型中,绘制视图分为以下两步:
- 对层次结构进行无效化处理
- 绘制层次结构
每当应用需要更新其界面的一部分时,就会对内容已发生更改的所有视图调用 invalidate()
(或其变体之一)。无效化消息会一直传播到视图层次结构上层,以计算需要重新绘制的屏幕区域(脏区域)。然后,Android 系统会绘制层次结构中与脏区域交互的所有视图。遗憾的是,这种绘制模型具有以下两个缺点:
- 第一,每次绘制时该模型都需要执行大量代码。例如,如果您的应用对某个按钮调用
invalidate()
且该按钮位于另一个视图上方,那么即使该视图未发生更改,Android 系统仍会重新绘制该视图。 - 第二,该绘制模型会隐藏应用中的 bug。由于 Android 系统会在视图与脏区域交互时重新绘制视图,因此系统可能会重新绘制内容发生更改的视图,即使未对其调用
invalidate()
也是如此。如果发生这种情况,您要依赖其他经过无效化处理的视图才能获得正确的行为。每次修改应用时,此行为都可能会发生更改。因此,每次修改会影响视图绘制代码的数据或状态后,您都要对自定义视图调用invalidate()
。
注意:Android 视图会在其属性(例如 TextView
中的背景颜色或文本)发生更改时自动调用 invalidate()
。
硬件加速绘制模型
Android 系统仍会使用 invalidate()
和 draw()
请求屏幕更新和渲染视图,但会采用其他方式处理实际绘制过程。Android 系统不会立即执行绘制命令,而是将这些命令记录在显示列表中,这些列表中包含视图层次结构绘制代码的输出。另一项优化是,Android 系统只需要记录和更新被 invalidate()
调用标记为脏视图的视图的显示列表。只需重新发出之前记录的显示列表,即可重新绘制未经过无效化处理的视图。新绘制模型包含以下三个阶段:
- 对层次结构进行无效化处理
- 记录并更新显示列表
- 绘制显示列表
使用此模型时,您无法依赖与脏区域交互的视图来执行其 draw()
方法。要确保 Android 系统会记录视图的显示列表,您必须调用 invalidate()
。如果忘记执行此操作,则视图在发生更改后看起来仍然没有变化。
使用显示列表还有助于改进动画性能,因为设置特定属性(例如 Alpha 或旋转)不需要对目标视图进行无效化处理(该操作是自动完成的)。这项优化还适用于具有显示列表的视图(如果应用经过硬件加速,则适用于所有视图)。例如,假设有一个 LinearLayout
,其中包含一个ListView
(位于 Button
之上)。LinearLayout
的显示列表如下所示:
- DrawDisplayList(ListView)
- DrawDisplayList(Button)
假设您现在要更改 ListView
的不透明度。在对 ListView
调用 setAlpha(0.5f)
后,显示列表现在包含以下内容:
- SaveLayerAlpha(0.5)
- DrawDisplayList(ListView)
- Restore
- DrawDisplayList(Button)
系统没有执行 ListView
的复杂绘制代码,而是仅更新了更为简单的 LinearLayout
的显示列表。在未启用硬件加速的应用中,系统会再次执行列表及其父级的绘制代码。
支持绘制操作
经过硬件加速后,2D 渲染管道支持最常用的 Canvas
绘制操作以及很多不太常用的操作。用于渲染 Android 系统内置应用、默认 widget 和布局以及常见的高级视觉效果(例如反射和平铺纹理)的所有绘制操作均受到支持。
下表介绍了各种操作在各个 API 级别的支持级别:
第一个支持的 API 级别 | ||||
画布 | ||||
drawBitmapMesh()(颜色数组) | 18 | |||
drawPicture() | 23 | |||
drawPosText() | 16 | |||
drawTextOnPath() | 16 | |||
drawVertices() | 29 | |||
setDrawFilter() | 16 | |||
clipPath() | 18 | |||
clipRegion() | 18 | |||
clipRect(Region.Op.XOR) | 18 | |||
clipRect(Region.Op.Difference) | 18 | |||
clipRect(Region.Op.ReverseDifference) | 18 | |||
clipRect()(通过旋转/透视) | 18 | |||
绘制 | ||||
setAntiAlias()(适用于文本) | 18 | |||
setAntiAlias()(适用于线条) | 16 | |||
setFilterBitmap() | 17 | |||
setLinearText() | ✗ | |||
setMaskFilter() | ✗ | |||
setPathEffect()(适用于线条) | 28 | |||
setShadowLayer()(除文本之外) | 28 | |||
setStrokeCap()(适用于线条) | 18 | |||
setStrokeCap()(适用于点) | 19 | |||
setSubpixelText() | 28 | |||
Xfermode | ||||
PorterDuff.Mode.DARKEN(帧缓冲区) | 28 | |||
PorterDuff.Mode.LIGHTEN(帧缓冲区) | 28 | |||
PorterDuff.Mode.OVERLAY(帧缓冲区) | 28 | |||
着色器 | ||||
ComposeShader 内的 ComposeShader | 28 | |||
ComposeShader 内相同类型的着色器 | 28 | |||
ComposeShader 上的本地矩阵 | 18 |
画布缩放
硬件加速 2D 渲染管道最初是为了支持不可缩放的绘制构建的,其中一些绘制操作会以较高的缩放值显著降低质量。这些操作实现为按 1.0 的缩放值绘制的纹理,由 GPU 进行转换。从 API 级别 28 开始,所有绘制操作都可以顺利缩放。
下表列出了何时更改实现以正确处理大规模缩放:要缩放的绘制操作 | 第一个支持的 API 级别 |
drawText() | 18 |
drawPosText() | 28 |
drawTextOnPath() | 28 |
简单的形状* | 17 |
复杂的形状* | 28 |
drawPath() | 28 |
阴影层 | 28 |
注意:“简单”形状指的是使用 Paint 发出的 drawRect()
、drawCircle()
、drawOval()
、drawRoundRect()
和 drawArc()
(其中 useCenter=false)命令,该 Paint 不包含 PathEffect,也不包含非默认联接(通过 setStrokeJoin()
/setStrokeMiter()
)。这些绘制命令的其他实例都属于上表中的“复杂”形状。
如果缺失以上任意功能或限制会对您的应用造成影响,您可以调用 setLayerType(View.LAYER_TYPE_SOFTWARE, null)
,仅针对应用中受影响的部分关闭硬件加速。这样一来,您仍能针对其余任何部分利用硬件加速。如需详细了解如何在应用中的不同级别启用和停用硬件加速,请参阅控制硬件加速。
视图层
在所有 Android 版本中,视图能够通过以下两种方式渲染到屏幕外缓冲区:使用视图的绘制缓存或使用 Canvas.saveLayer()
。屏幕外缓冲区或层具有多种用途。在为复杂的视图添加动画效果或应用合成效果时,您可以使用屏幕外缓冲区或层获得更好的效果。例如,您可以使用 Canvas.saveLayer()
实现淡入淡出效果,以将视图暂时渲染到层,然后使用不透明度系数将其合成回屏幕上。
从 Android 3.0(API 级别 11)开始,您可以通过 View.setLayerType()
方法更好地控制如何及何时使用层。该 API 需要 2 个参数:要使用的层类型以及描述层应如何合成的可选 Paint
对象。您可以使用 Paint
参数向层应用颜色滤镜、特殊混合模式或不透明度。视图可以使用以下三种层类型之一:
LAYER_TYPE_NONE
:视图正常渲染,不受屏幕外缓冲区支持。这是默认行为。LAYER_TYPE_HARDWARE
:如果应用经过硬件加速,视图在硬件中渲染为硬件纹理。如果应用未经过硬件加速,此层类型的行为方式与LAYER_TYPE_SOFTWARE
相同。LAYER_TYPE_SOFTWARE
:视图在软件中渲染为位图。
要使用何种层类型取决于您的目标:
- 性能:使用硬件层类型可将视图渲染为硬件纹理。将视图渲染为层后,在该视图调用
invalidate()
之前,无需执行其绘制代码。然后,可将 Alpha 动画等部分动画直接应用到层,GPU 可非常高效地完成此操作。 - 视觉效果:使用硬件层或软件层类型和
Paint
可将特殊视觉处理应用到视图。例如,您可以使用ColorMatrixColorFilter
绘制黑白视图。 - 兼容性:使用软件层类型可强制在软件中渲染视图。如果经过硬件加速的视图(例如,如果整个应用都经过硬件加速)遇到渲染问题,采用这种方法可轻松解决硬件渲染管道的局限性。
视图层和动画
如果应用经过硬件加速,硬件层能够提供更快且更顺畅的动画。在为需要发出大量绘制操作的复杂视图添加动画效果时,以 60 帧/秒的速度运行动画并非总能实现。使用硬件层将视图渲染为硬件纹理可在一定程度上解决此问题。然后,硬件纹理可用于为视图添加动画效果,这样视图在动画效果添加过程中无需不断自行重新绘制。除非您更改视图的
属性(调用 invalidate()
),或者手动调用 invalidate()
。如果您在应用中运行动画,但没有获得想要的顺畅动画,请考虑对添加动画效果之后的视图启用硬件层。
如果视图由硬件层提供支持,则其部分属性可通过在屏幕上合成层的方式处理。设置这些属性有助于提高效率,因为它们不需要先对视图进行无效化处理后再重新绘制。下面列出的属性会影响层的合成方式。针对以下任何属性调用 setter 方法会得到最佳无效化效果,且无需重新绘制目标视图:
alpha
:更改层的不透明度x
、y
、translationX
、translationY
:更改层的位置scaleX
、scaleY
:更改层的大小rotation
、rotationX
、rotationY
:更改层在 3D 空间里的方向pivotX
、pivotY
:更改层的转换原点
这些是使用 ObjectAnimator
为视图添加动画效果时所用属性的名称。如果您要访问这些属性,请调用相应 setter 或 getter 方法。例如,要修改 Alpha 属性,请调用 setAlpha()
。以下代码段展示了在 3D 空间中绕 Y 轴旋转视图的最有效方式。
Kotlin
view.setLayerType(View.LAYER_TYPE_HARDWARE, null) ObjectAnimator.ofFloat(view, "rotationY", 180f).start()
Java
view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator.ofFloat(view, "rotationY", 180).start();
由于硬件层会占用视频内存,因此强烈建议您仅在动画播放期间启用,然后在动画结束后停用。您可以使用动画监听器完成此操作:
Kotlin
view.setLayerType(View.LAYER_TYPE_HARDWARE, null) ObjectAnimator.ofFloat(view, "rotationY", 180f).apply { addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { view.setLayerType(View.LAYER_TYPE_NONE, null) } }) start() }
Java
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
。