Profile GPU Rendering 工具指示渲染管道的每个阶段渲染上一帧所用的相对时间。这些信息可以帮助您识别管道中的瓶颈,从而了解需要优化哪些方面以提高应用的渲染性能。
本页面简要介绍了每个管道阶段发生的情况,并讨论了可能导致瓶颈的问题。在阅读本页面之前,您应该熟悉 Profile GPU Rendering 中介绍的信息。此外,为了理解所有阶段如何协同工作,回顾 渲染管道的工作原理可能会有所帮助。
可视化表示
Profile GPU Rendering 工具以图表形式显示阶段及其相对时间:一个颜色编码的直方图。图 1 显示了此类显示的一个示例。

图 1. Profile GPU Rendering 图
Profile GPU Rendering 图中显示的每个垂直条的每个分段都代表管道的一个阶段,并使用条形图中的特定颜色突出显示。图 2 显示了每个显示颜色含义的图例。

图 2. Profile GPU Rendering 图例
了解每种颜色所代表的含义后,您可以针对应用的特定方面进行优化,以提高其渲染性能。
阶段及其含义
本部分解释了图 2 中每种颜色对应阶段发生的情况,以及需要注意的瓶颈原因。
输入处理
管道的输入处理阶段衡量应用处理输入事件所花费的时间。此指标表示应用执行因输入事件回调而调用的代码所花费的时间。
当此部分较大时
此区域值较高通常是由于输入处理程序事件回调中进行了过多或过于复杂的工作。由于这些回调始终在主线程上发生,因此解决此问题的方法侧重于直接优化工作,或将工作分流到不同的线程。
另外值得注意的是,RecyclerView
滚动可能会在此阶段出现。RecyclerView
在消耗触摸事件后会立即滚动。因此,它可能会膨胀或填充新的项目视图。因此,务必使此操作尽可能快。Traceview 或 Systrace 等性能分析工具可以帮助您进一步调查。
动画
动画阶段显示了评估该帧中运行的所有动画器所需的时间。最常见的动画器是 ObjectAnimator
、ViewPropertyAnimator
和过渡。
当此部分较大时
此区域值较高通常是由于动画的某些属性更改导致的工作正在执行。例如,用于滚动 ListView
或 RecyclerView
的滑动动画会导致大量的视图膨胀和填充。
测量/布局
为了让 Android 在屏幕上绘制您的视图项,它会在视图层次结构中的布局和视图上执行两个特定操作。
首先,系统会测量视图项。每个视图和布局都具有描述屏幕上对象大小的特定数据。某些视图可以具有特定大小;其他视图的大小则会适应父布局容器的大小。
其次,系统会布局视图项。一旦系统计算出子视图的大小,系统就可以继续进行布局、调整大小和定位屏幕上的视图。
系统不仅会测量和布局要绘制的视图,还会测量和布局这些视图的父层次结构,直到根视图。
当此部分较大时
如果您的应用在此区域每帧花费大量时间,通常是因为需要布局的视图数量巨大,或者是因为层次结构中错误位置存在双重征税等问题。在这两种情况下,解决性能问题都涉及改进视图层次结构的性能。
您添加到 onLayout(boolean, int, int, int, int)
或 onMeasure(int, int)
中的代码也可能导致性能问题。Traceview 和 Systrace 可以帮助您检查调用堆栈,以识别您的代码可能存在的问题。
绘制
绘制阶段将视图的渲染操作(例如绘制背景或绘制文本)转换为一系列原生绘制命令。系统将这些命令捕获到显示列表中。
“绘制”条记录了本帧中所有需要在屏幕上更新的视图,将命令捕获到显示列表所需的时间。测量时间适用于您添加到应用中 UI 对象的任何代码。此类代码的示例可能包括 onDraw()
、dispatchDraw()
和 Drawable
类的子类中的各种 draw ()methods
。
当此部分较大时
简而言之,您可以将此指标理解为显示每个无效视图运行所有 onDraw()
调用所需的时间。此测量包括调度绘制命令到可能存在的子项和可绘制对象所花费的任何时间。因此,当您看到此条形图飙升时,原因可能是大量视图突然失效。失效使得有必要重新生成视图的显示列表。另外,长时间可能是由于一些自定义视图在其 onDraw()
方法中包含一些极其复杂的逻辑。
同步/上传
“同步与上传”指标表示在当前帧中将位图对象从 CPU 内存传输到 GPU 内存所需的时间。
作为不同的处理器,CPU 和 GPU 具有不同的专用 RAM 区域用于处理。当您在 Android 上绘制位图时,系统会在 GPU 将其渲染到屏幕之前将位图传输到 GPU 内存。然后,GPU 会缓存位图,这样除非纹理从 GPU 纹理缓存中被逐出,否则系统无需再次传输数据。
注意:在 Lollipop 设备上,此阶段显示为紫色。
当此部分较大时
帧的所有资源都需要驻留在 GPU 内存中,然后才能用于绘制帧。这意味着此指标的高值可能表示大量的小资源加载,或者少量非常大的资源。常见情况是应用显示一个接近屏幕大小的位图。另一种情况是应用显示大量缩略图。
要缩小此条形图,您可以使用以下技术:
- 确保您的位图分辨率不会远大于它们将要显示的尺寸。例如,您的应用应避免将 1024x1024 的图像显示为 48x48 的图像。
- 利用
prepareToDraw()
在下一个同步阶段之前异步预上传位图。
发出命令
“发出命令”分段表示发出将显示列表绘制到屏幕所需的所有命令所花费的时间。
为了让系统将显示列表绘制到屏幕,它会将必要的命令发送到 GPU。通常,它通过 OpenGL ES API 执行此操作。
此过程需要一些时间,因为系统在将每个命令发送到 GPU 之前会对其执行最终变换和剪裁。然后,GPU 侧会产生额外的开销,它会计算最终命令。这些命令包括最终变换和额外剪裁。
当此部分较大时
此阶段所花费的时间直接衡量系统在给定帧中渲染的显示列表的复杂性和数量。例如,拥有许多绘制操作,尤其是在每个绘制原语的固有成本很小的情况下,可能会增加此时间。例如:
Kotlin
for (i in 0 until 1000) { canvas.drawPoint() }
Java
for (int i = 0; i < 1000; i++) { canvas.drawPoint() }
比…的发出成本高得多
Kotlin
canvas.drawPoints(thousandPointArray)
Java
canvas.drawPoints(thousandPointArray);
发出命令与实际绘制显示列表之间并非总是一一对应。与捕获将绘制命令发送到 GPU 所花费时间的“发出命令”不同,“绘制”指标表示捕获已发出命令到显示列表所需的时间。
出现这种差异是因为系统会尽可能地缓存显示列表。因此,在某些情况下,滚动、变换或动画需要系统重新发送显示列表,但无需从头开始实际重建(重新捕获绘制命令)。因此,您可能会看到“发出命令”条形图很高,但“绘制命令”条形图不高。
处理/交换缓冲区
Android 完成将其所有显示列表提交到 GPU 后,系统会发出最后一个命令,通知图形驱动程序当前帧已完成。此时,驱动程序最终可以将更新的图像呈现到屏幕上。
当此部分较大时
重要的是要理解 GPU 与 CPU 并行执行工作。Android 系统向 GPU 发出绘制命令,然后继续执行下一个任务。GPU 从队列中读取这些绘制命令并处理它们。
在 CPU 发出命令的速度快于 GPU 消耗命令的速度的情况下,处理器之间的通信队列可能会变满。发生这种情况时,CPU 会阻塞并等待队列中有空间来放置下一个命令。这种满队列状态经常发生在“交换缓冲区”阶段,因为此时,已提交了整帧的命令。
解决此问题的关键是降低 GPU 上发生的工作复杂性,方式类似于您在“发出命令”阶段所做的工作。
杂项
除了渲染系统执行工作所需的时间外,还有一组额外的、与渲染无关的工作发生在主线程上。此工作所消耗的时间报告为“杂项时间”。杂项时间通常表示在两个连续渲染帧之间可能发生在 UI 线程上的工作。
当此部分较大时
如果此值很高,则很可能是您的应用具有回调、Intent 或其他应在另一个线程上进行的工作。方法跟踪或 Systrace 等工具可以提供主线程上正在运行的任务的可见性。这些信息可以帮助您确定性能改进的目标。