该 Profile GPU 渲染 工具指示渲染管线每个阶段渲染前一帧所花费的相对时间。此信息可以帮助您识别管线中的瓶颈,以便您知道要优化哪些内容以提高应用的渲染性能。
此页面简要说明了每个管线阶段期间发生的情况,并讨论了可能导致瓶颈出现的问题。在阅读此页面之前,您应该熟悉 Profile GPU 渲染 中提供的信息。此外,要了解所有阶段如何协同工作,查看 渲染管线的工作原理。 可能会有所帮助。
视觉表示
Profile GPU 渲染工具以图形的形式显示阶段及其相对时间:一个颜色编码的直方图。图 1 显示了此类显示的示例。
Profile GPU 渲染图中显示的每个垂直条的每个段都代表管线的一个阶段,并在条形图中使用特定的颜色突出显示。图 2 显示了每个显示颜色的含义的关键。
了解每个颜色表示的含义后,您可以针对应用的特定方面尝试优化其渲染性能。
阶段及其含义
本节说明了在图 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()方法
。
此段很大时
简单来说,您可以将此指标理解为显示为每个失效视图运行所有对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);
发出命令和实际绘制显示列表之间并不总是存在 1:1 的对应关系。“发出命令”捕获将绘制命令发送到 GPU 所需的时间,而“绘制”指标表示捕获已发出的命令到显示列表中所需的时间。
这种差异源于系统尽可能缓存显示列表。因此,在某些情况下,滚动、转换或动画需要系统重新发送显示列表,但实际上不必从头开始重新构建它(重新捕获绘制命令)。因此,您可能会看到“发出命令”栏很高,但“绘制命令”栏却不高。
处理/交换缓冲区
Android 完成将所有显示列表提交到 GPU 后,系统会发出一个最终命令,告诉图形驱动程序它已完成当前帧。此时,驱动程序终于可以将更新的图像呈现到屏幕上了。
此段很大时
必须了解,GPU 与 CPU 并行执行工作。Android 系统向 GPU 发出绘制命令,然后继续执行下一个任务。GPU 从队列中读取这些绘制命令并处理它们。
在 CPU 发出命令的速度快于 GPU 使用命令的速度的情况下,处理器之间的通信队列可能会变得满。发生这种情况时,CPU 会阻塞并等待队列中有空间放置下一个命令。这种队列满的状态通常出现在“交换缓冲区”阶段,因为此时已提交了整个帧的命令。
缓解此问题的关键是减少 GPU 上正在执行的工作的复杂性,这与您为“发出命令”阶段所做的事情类似。
其他
除了渲染系统执行其工作所需的时间外,主线程上还会发生另一组工作,并且与渲染无关。此工作消耗的时间报告为“其他时间”。“其他时间”通常表示可能在渲染的两个连续帧之间发生在 UI 线程上的工作。
此段很大时
如果此值较高,则您的应用可能存在回调、意图或其他应该在其他线程上发生的工作。诸如方法跟踪或Systrace之类的工具可以提供对主线程上正在运行的任务的可视化。此信息可以帮助您确定性能改进的目标。