去除过度渲染可以从下面渲染阶段的几方面入手:
具体如何分析和优化过度渲染,可以查看我之前的博客关于过度绘制和渲染的介绍。
GAPID是一个开发工具,用于记录和检查应用程序对图形驱动程序的调用。
[外链图片转存失败(img-4BemOgI4-1562732794197)(https://google.github.io/gapid/images/hero.gif)]
一旦捕获了目标应用程序,GAPID允许您断开与目标的连接,并检查应用程序执行的所有图形命令。
GAPID能够重放命令流,通过逐步遍历每个命令并检查流中任何点的驱动程序状态,使框架组合可视化。Replay还支持修改,允许您调整命令参数和着色器源,以便立即查看这会对帧产生什么影响。
GAPID还可以可视化应用程序所使用的纹理、着色器和绘制调用几何结构。
GAPID支持从Android设备和Windows /Linux桌面机器上捕获。在Android设备上,GAPID支持跟踪由纯Java、本地或混合应用程序生成的所有OpenGL ES和Vulkan调用。在Windows /Linux桌面机上,GAPID支持跟踪Vulkan调用。
Android:
单击欢迎屏幕中的Capture Trace文本,或者单击File → Capture Trace工具栏项打开跟踪对话框。
单击“OK”开始跟踪。
例子:
第一步:下载GAPID并安装。
第二步:打开GAPID并按照上面的捕捉步骤操作。
从上面图中可以看到应用程序每一帧的渲染,以及每一帧从CPU上传到GPU的图片纹理。如果发现有上传重复的图片或者渲染无用的渐变、阴影,可以针对这些进行优化,因为这些都会增加每一帧的渲染时间。
systrace命令允许您在系统级收集并检查在设备上运行的所有进程的实时信息。它将来自Android内核的数据(如CPU调度程序、磁盘活动和应用程序线程)组合起来生成HTML报告,类似于图1所示。
图1. 一个systrace HTML报表例子,显示了与应用程序交互的5秒。报告强调了systrace认为可能没有被正确渲染的帧。
该报告提供了Android设备在给定时间内的系统进程的总体图。它还检查捕获的跟踪信息,以突出它观察到的问题,如显示运动或动画时的UI jank,并提供关于如何修复它们的建议。但是,systrace没有收集应用程序过程中有关代码执行的信息。有关应用程序正在执行哪些方法和使用多少CPU资源的详细信息,请使用Android Studio CPU profiler。您还可以生成跟踪日志,并使用CPU profiler导入和检查它们。
要为app生成HTML报告,您需要使用以下语法从命令行运行systrace:
$ python systrace.py [options] [categories]
systrace.py 位于 android-sdk/platform-tools/systrace/ 目录下。在执行命令前,确保你已经安装了python。
要查看已连接设备支持的类别列表,请运行以下命令:
$ python systrace.py --list-categories
如果未指定任何类别或选项,systrace将生成包含所有可用类别的报告并使用默认设置。 可用的类别取决于您使用的连接设备。
全局选项 | 描述 |
---|---|
-h | --help | 显示帮助信息。 |
-l | --list-categories | 列出所连接设备可用的跟踪类别。 |
命令和选项 | 描述 |
---|---|
-o file | 将HTML跟踪报告写入指定的文件。 如果未指定此选项,systrace会将报表保存到与systrace.py相同的目录中,并将其命名为trace.html。 |
-t N | --time=N | 跟踪设备活动N秒。 如果未指定此选项,则systrace会提示您通过从命令行按Enter键来结束跟踪。 |
-b N | --buf-size=N | 使用N千字节的跟踪缓冲区大小。 此选项允许您限制跟踪期间收集的数据的总大小。 |
-k functions | --ktrace=functions | 跟踪在逗号分隔列表中指定的特定内核函数的活动。 |
-a app-name | --app=app-name | 启用应用程序的跟踪,指定为以逗号分隔的进程名称列表。 应用程序必须包含Trace类的跟踪检测调用。 您应该在配置应用程序时指定此选项,许多库(例如RecyclerView)包括跟踪检测调用,这些调用在您启用应用程序级别跟踪时提供有用信息。 有关更多信息,请转到有关如何检测应用程序代码的部分。 |
--from-file=file-path | 从文件创建交互式HTML报告,例如包含原始跟踪数据的TXT文件,而不是运行实时跟踪。 |
-e device-serial--serial=device-serial | 在特定连接设备上进行跟踪,由设备序列号标识。 |
categories | 包括您指定的系统进程的跟踪信息,例如用于呈现图形的系统进程的gfx。 您可以使用-l命令运行systrace,以查看连接设备可用的服务列表。 |
systrace对于检查应用程序的UI性能特别有用,因为它可以分析您的代码和帧速率,以识别问题区域并提出可能的解决方案建议。 首先,按以下步骤操作:
$ python systrace.py -t 10 [other-options] [categories]
通过与此报告交互,您可以在记录期间检查设备CPU使用情况。
以下部分介绍了如何检查报表中的信息以查找和修复UI性能问题。
如图2所示,报表列出呈现UI帧的每个进程,并指示沿着时间线呈现的每个帧。在16.6毫秒内渲染的帧需要保持每秒60帧的稳定性,用绿色帧圆表示。渲染时间大于16.6毫秒的帧用黄色或红色帧圆表示。
注意:在运行Android 5.0(API级别21)或更高版本的设备上,渲染帧的工作在UI线程和RenderThread之间分配。 在以前的版本中,创建帧的所有工作都在UI线程上完成。
单击框架圆圈会突出显示它,并提供有关系统完成该帧所做工作的其他信息,包括警报。 它还会向您显示系统在渲染该帧时执行的方法,因此您可以调查这些方法以获取UI jank的原因。
图3.选择有问题的帧,跟踪报告下方会出现一个警报,用于识别问题。
在选择慢帧之后,您可能会在报告的底部窗格中看到警报。 图3中显示的警报调用框架的主要问题是在ListView回收和重新绑定中花费了太多时间。 跟踪中的相关事件有链接,可以更详细地说明系统在此期间所执行的操作
要查看工具在跟踪中发现的每个警报,以及设备触发每个警报的次数,请单击窗口右侧的“Alert”选项卡,如图4所示。Alert面板帮助您了解跟踪中出现哪些问题,以及它们对jank有多频繁。 将面板视为要修复的错误列表。 通常,一个区域中的微小变化或改进可以从应用程序中消除整个类别的警报。
图4.单击右侧的Alert按钮显示警告选项卡。
如果你在UI线程上看到太多的工作,你需要找出哪些方法消耗了太多的CPU时间。 一种方法是将跟踪标记(请参阅仪器您的应用程序代码)添加到您认为导致这些瓶颈的方法中,以查看这些函数调用是否出现在systrace中。 如果您不确定哪些方法可能导致UI线程出现瓶颈,请使用Android Studio CPU分析器。 您可以使用CPU Profiler生成跟踪日志并导入和检查它们。
下表列出了查看systrace HTML报告时可用的键盘快捷键。
Key | 描述 |
---|---|
W | 放大跟踪时间线。 |
S | 缩小跟踪时间线。 |
A | 在追踪时间线上左移。 |
D | 在追踪时间线上右移。/td> |
E | 将跟踪时间轴置于当前鼠标位置的中心。/td> |
G | 在当前所选任务的开头显示网格。 |
Shift + G | 在当前所选任务的末尾显示网格。 |
Right Arrow | 选择当前所选时间轴上的下一个事件。 |
Left Arrow | 在当前选定的时间轴上选择上一个事件。 |
因为systrace只在系统级别显示关于进程的信息,所以很难在HTML报告中知道应用程序在给定时间执行了哪些方法。在Android 4.3(API级别18)和更高级别中,可以使用代码中的跟踪类来标记HTML报表中的执行事件。您不需要使用systrace来记录代码来记录跟踪,但是这样做可以帮助您了解应用程序代码的哪些部分可能对挂起线程或UI jank有所贡献。这种方法与使用Debug类不同——Trace类只是向systrace报告添加标记,而Debug类通过生成.trace文件帮助您检查详细的应用程序CPU使用情况。
要生成包含已检测跟踪事件的systrace HTML报告,您需要使用-a或–app命令行选项运行systrace,并指定应用程序的包名称。
下面的示例代码演示如何使用Trace类来标记方法的执行,该方法包括两个嵌套的代码块:
public class MyAdapter extends RecyclerView.Adapter {
...
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Trace.beginSection("MyAdapter.onCreateViewHolder");
MyViewHolder myViewHolder;
try {
myViewHolder = MyViewHolder.newInstance(parent);
} finally {
Trace.endSection();
}
return myViewHolder;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
Trace.beginSection("MyAdapter.onBindViewHolder");
try {
try {
Trace.beginSection("MyAdapter.queryDatabase");
RowItem rowItem = queryDatabase(position);
mDataset.add(rowItem);
} finally {
Trace.endSection();
}
holder.bind(mDataset.get(position));
} finally {
Trace.endSection();
}
}
...
}
注意:beginSection()和endSection()方法需要结对出现,并且在同一个线程中运行。