Systrace 是Android平台提供的一款工具,用于记录短期内的设备活动。该工具会生成一份报告,其中汇总了Android内核中的数据,例如CPU调度程序、磁盘活动和应用线程。这份报告可帮助我们了解如何以最佳方式改善应用或游戏的性能。
Systrace 工具用于显示整个设备在做些什么,不过也可用于识别应用中的卡顿。Systrace 的系统开销非常小,因此你可以在插桩测试期间体验实际卡顿情况。
这份报告提供了 Android 设备在给定时间段内的系统进程的总体情况,还会检查所捕获的跟踪信息,以突出显示它所观察到的问题(例如界面卡顿或耗电量高)。
要想使用Systrace分析性能,我们首先需要找到Systrace工具,以及使用该工具生成一份.html的报告文件。
systrace命令会调用Systrace工具,以收集和检查设备上在系统一级运行的所有进程的时间信息。
systrace命令是一个Python脚本,所以需要进行如下准备:
systrace 命令在 Android SDK Tools 工具包中提供,位于 android-sdk/platform-tools/systrace/。
例如,作者电脑上sstrace位置为:/Users/apple/Library/Android/sdk/platform-tools/systrace。
要为应用生成HTML报告,我们需要使用以下语法从命令行运行systrace:
python systrace.py [options] [categories]
python ./systrace.py -t 5 -o mynewtrace.html
python systrace.py -o mynewtrace.html sched freq idle am wm gfx view \
binder_driver hal dalvik camera input res
mynewtrace.html之后的参数,表示类别列表。
提示:如果要在跟踪输出中查看任务名称,必须在命令参数中添加 sched 类别。
命令如下:
python systrace.py --list-categories
示例:
通过以上操作,会生成一个HTML文件,我们可以使用chrome浏览器,地址栏输入chrome://tracing/ 来查看报告。
Systrace 会生成包含多个部分的输出 HTML 文件。该报告列出了每个进程的线程。如果给定线程会渲染界面帧,该报告还会沿时间轴指明所渲染的帧。当您在报告中从左向右移动时,时间会向前推移。
报告从上到下包含以下几个部分。
第一部分包含表示应用或游戏中的具体用户互动(例如点按设备屏幕)的条形图。这些互动可用作有用的时间标记。
下一部分显示了表示每个 CPU 中的线程活动的条形图。这些条形会显示所有应用(包括你的应用或游戏)中的 CPU 活动。
CPU 活动部分可以展开,展开后您就可以查看每个 CPU 的时钟频率。图 1 展示了一个收起后的 CPU 活动部分示例,图 2 展示了显示时钟频率的展开后版本:
此部分中的直方图会显示特定的系统级事件,例如特定对象的纹理计数和总大小。
值得仔细检查的直方图是标记为 SurfaceView 的直方图。计数表示已传递到显示管道并等待显示在设备屏幕上的组合帧缓冲区的数量。由于大多数设备都会进行双重或三重缓冲,因此该计数几乎总为 0、1 或 2。
描绘 Surface Flinger 进程(包括 VSync 事件和界面线程交换工作)的其他直方图,如图所示:
描绘了一条多色线条,后面是成堆的条形。这些形状表示已创建的特定线程的状态和帧堆栈。堆栈的每个层级代表对 beginSection() 的一次调用,或您为应用或游戏定义的自定义跟踪事件的开头。
每个条形堆上方的多色线条表示特定线程随时间变化的一组状态。每段线条可以包含以下颜色之一:
绿色:正在运行
线程正在完成与某个进程相关的工作或正在响应中断。
蓝色:可运行
线程可以运行但目前未进行调度。
白色:休眠
线程没有可执行的任务,可能是因为线程在遇到斥锁定时被阻止。
橙色:不可中断的休眠
线程在遇到 I/O 操作时被阻止或正在等待磁盘操作完成。
紫色:可中断的休眠
线程在遇到另一项内核操作(通常是内存管理)时被阻止。
注意:在Systrace报告中,你可以点击该线条以确定该线程在给定时间由哪个CPU控制。
下表列出了查看 Systrace 报告时可以使用的键盘快捷键:
浏览 Systrace 报告时,您可以通过执行以下一项或多项操作来更轻松地识别性能问题:
如图所示,Systrace 报告列出了渲染界面帧的每个进程,并指明了沿时间轴渲染的每个帧。在 16.6 毫秒内渲染的必须保持每秒 60 帧稳定帧速率的帧会以绿色圆圈表示。渲染时间超过 16.6 毫秒的帧会以黄色或红色帧圆圈表示。
点击某个帧圆圈可将其突出显示,并提供有关系统为渲染该帧所做工作的其他信息,包括提醒。此报告还会显示系统在渲染该帧时执行的方法。您可以调查这些方法以确定界面卡顿的可能原因。
如果所示,选择有问题的帧后,跟踪报告下方会显示一条提醒,用于指明问题所在。
选择运行速度慢的帧后,您可能会在报告的底部窗格中看到一条提醒。
点击窗口最右侧的Alerts标签页可以查看此工具在你的跟踪记录中发现的每条提醒以及设备触发每条提醒的次数,如下图所示。Alerts 面板可帮助你了解跟踪记录中出现的问题以及这些问题导致出现卡顿的频率。我们也可以将此面板视为要修正的错误列表。通常情况下,只需对一个区域进行细微改动或改进即可移除整组提醒。
Systrace(系统跟踪)仅在系统级别显示进程的相关信息,这样导致有时很难知道APP的哪些方法是在给定时间针对系统事件执行的。
例如,我们在解决卡顿问题,当查看系统跟踪信息输出后,你可能会怀疑应用中的某些方法是导致卡顿的因素。例如,如果时间轴显示某个帧的呈现速度较慢是因为 RecyclerView 花费很长时间导致的,这时我们需要更多的信息来进行判断。
如何做呢?
我们可以在相关代码中添加跟踪标记(定义自定义事件),然后重新运行 systrace 以获取更多信息。在新的系统跟踪信息中,时间轴会显示应用中的方法的调用时间和执行时长。
Android 平台提供了一个跟踪 API,可用于为特定的代码段添加标签。如果您捕获应用的“调试”版本的新系统跟踪并添加 -a 选项(如以下代码段所示),这些自定义事件便会显示在 Systrace 报告中:
python systrace.py -a com.example.myapp -b 16384 \
-o my_systrace_report.html sched freq idle am wm gfx view binder_driver hal \
dalvik camera input res
必须提供 -a 选项才能跟踪应用;如果没有此选项,应用的方法将不会显示在 Systrace 报告中。
注意:该方法与使用 Debug 类不同,后者可帮助您通过生成 .trace 文件来检查应用 CPU 的详细使用情况。
在 Android 4.3(API 级别 18)及更高版本中,我们可以在代码中使用 Trace 类来定义随后会出现在 Perfetto 和 Systrace 报告中的自定义事件,如以下代码段所示。
public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Trace.beginSection("MyAdapter.onCreateViewHolder");
MyViewHolder myViewHolder;
try {
myViewHolder = MyViewHolder.newInstance(parent);
} finally {
// In try and catch statements, always call "endSection()" in a
// "finally" block. That way, the method is invoked even when an
// exception occurs.
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);
dataset.add(rowItem);
} finally {
Trace.endSection();
}
holder.bind(dataset.get(position));
} finally {
Trace.endSection();
}
}
}
注意:如果多次调用 beginSection(),调用 endSection() 只会结束最后调用的 beginSection() 方法。因此,对于嵌套调用(如以下代码段中所示),请务必将每次对 beginSection() 的调用与一次对 endSection() 的调用正确匹配。
此外,我们不能在一个线程上调用 beginSection(),而在另一个线程上结束它;而是必须在同一个线程上调用这两个方法。
通过本文学习了:
PS:性能优化专栏:《Android性能》持续更新中……