深入 Android 性能优化二:谈谈 systrace

文章目录
  1. 1. 简要介绍
  2. 2. 具体使用
  3. 3. 更多内容

干就完了。

这段时间,在币圈和技术圈,引言上这句话看到过好几次,怎么理解,见仁见智。个人觉得,对任何事,早早确立方向,不用想太多,生活形成一种习惯,莫忘初心,干就完了。好,不废话了,最近接触性能监控和优化方面的活,之前也作过一篇性能优化方面的概要介绍,这次来安利一款性能优化工具即 systrace。

以下大多内容翻译及加工自官网

简要介绍

systrace 命令可以允许我们在系统层面上,对设备里所有进程的耗时信息进行收集和调研。systrace 结合的分析数据来源于 Android 内核,如 CPU 调度器、磁盘活动和 App 的线程等,会生成一份网页报告如下所示:

这是一份简单的 systrace 网页报告,显示了和 App 5 秒钟的交互,报告中高亮的帧是 systrace 认为没有合理渲染的地方。

事实上,生成的报告在给定的时间区间,描述了 Android 设备系统进程的全局图。同时,报告检查了抓到的 tracing 信息,并高亮化其观察到的问题,如展示动作或动画时的 UI 卡顿,会提供一些如何修复的建议。此外,systrace 也有它自身的局限性,其无法收集到 App 进程内的代码执行信息。为了获取更多的详细信息,如 App 运行时正在执行的方法、App 使用的 CPU 资源等,使用 Android Studio 内置的 CPU profiler,或生成的 trace 日志,然后使用 Traceview 查看。

为了运行 systrace,需要完成下面这些:

  • Android Studio 上,下载安装最新的 Android SDK Tools
  • 安装 Python,并包括在工作的执行路径下
  • 连接 Android 4.3 (API level 18) 及以上的设备,打开 USB 调试模式

systrace 工具在 Android SDK Tools 的包里,位于android-sdk/platform-tools/systrace/

具体使用

了解了 systrace 后,下面来看其具体的使用。

命令

为了生成所需要的网页报告,需要在命令行使用下面的命令运行 systrace,即进入android-sdk/platform-tools/systrace/后,执行:

1
python systrace.py [options] [categories]

举个例子,下面运行 systrace 来记录 10 秒期间设备的进程,包括图像进程,然后生成一份命名为 mynewtrace 的网页报告,执行:

1
python systrace.py --time=10 -o mynewtrace.html gfx

若没指定任何类别或者选项,systrace 会生成一份报告,包括所有可用的种类,同时使用默认的设置。

通用的选项

通用选项 描述
-h 或 - -help 显示帮助信息
-l 或 - -list-categories 列出所连设备可用的 tracing 种类

命令和命令选项

命令和选项 描述
-o file 将 trace 的网页报告写到指定的文件中。若未指定该选项,systrace 会保存报告至与 systrace.py 相同的目录下,同时命名 trace.html
-t N 或 - -time=N Trace 设备活动的 N 秒时间。若未指定该选项,命令行中,通过敲 Enter 回车键来终止 trace 进程
-b N 或 - -buf-size=N 使用 N 千字节的 trace 缓冲区大小。这个选项可以限制在 trace 期间总共收集数据的大小
-k functions 或 - -ktrace=functions Trace 指定内核方法的活动,指定于逗号分隔的列表
-a app-name或 - -app=app-name Apps 能够 tracing,指定为以逗号分隔进程名字的列表。Apps 必须包含来自 Trace 类的 tracing 工具调用。无论何时,如以 RecyclerView 等许多库构建的 App,都应该指定该选项,包括在 App 层面 tracing 时跟踪工具调用提供的有用信息。为了获取更多的信息,参考下面植入你 App 的代码一栏
- -from-file=file-path 从一份文件里创建交互式的网页报告,如从包括原始 trace 数据的 TXT 文件,而不是运行着动态的 trace
-e device-serial或 - -serial=device-serial 根据设备的 serial number,在指定连接的设备上执行 trace
categories 包括指定系统进程的 tracing 信息,如系统进程的 gfx 渲染图像。事实上,连上设备后,可以执行 -l 命令运行 trace,来查看可用的服务种类列表

调研 UI 的性能问题

systrace 尤其在调研 App 的 UI 性能上表现突出,因为其可以分析代码和帧率,来判定问题出现的区域,并给出可能的建议。大致步骤如下:

  • 连上设备,运行 App

  • android-sdk/platform-tools/systrace/路径下,执行跑 systrace 的命令如:

    1
    python systrace.py view --time=10
  • 手动与 App 交互,如滑动等。10 秒后,systrace 会生成一份网页报告

  • 使用浏览器打开生成的网页报告

可以点击报告,来查看记录期间设备 CPU 的使用。下面给出怎样在报告中调研信息,来找到并修复 UI 的性能问题。

审查帧和警告

如下图所示,报告中列出了渲染 UI 帧的每个进程,显示了时间线里每幅渲染的帧。绿色帧圈的表明,在要求的 16.6 毫秒内保持稳定的 60 帧/秒渲染帧像;黄色或红色帧圈的表明,渲染帧像时花费了超过 16.6 毫秒。

注意,在 Android 5.0 (API level 21) 及更高的设备上,渲染帧像的工作被分隔于 UI 主线程和渲染线程。以前的版本,创建帧像的工作都在 UI 主线程完成。

点击帧圈可以高亮化,提供系统完成渲染帧像额外的信息,包括警告。它也展示渲染帧像时系统正在执行的方法,因此可以根据这些方法来找出 UI 卡顿的原因。选择有问题的帧,trace 报告下面会展示问题详情的一个 alert。

一目了然,trace 中会给出相关事件的链接,来解释在此期间系统运行的详情。

要看 trace 中工具发现的 alert,以及设备触发每个警告的次数,可以点击右上边上的 Alerts tab。Alerts 栏可以显示 trace 里发生的每一个问题和它们导致卡顿的频次。考虑到栏目里一系列待修复的 bugs,一片区域里,一个微小的变化或改进,可以忽略 App 中整个类的警告。

若看到 UI 主线程中做了太多的工作,需要我们自己找出消耗 CPU 时间的那些方法,方法之一是在认为导致性能瓶颈的地方,添加 trace 标记,来查看 trace 中出现的调用方法。若不确定哪些方法或许导致 UI 主线程的瓶颈,使用 Android Studio 内置的 CPU profiler,或生成的 trace 日志,然后使用 Traceview 查看。

网页报告快捷键

下面列出查看 systrace 网页报告的一些快捷键:

快捷键 描述
W 放大 trace 的时间线
S 缩小 trace 的时间线
A 定位在 trace 时间线的左边
D 定位在 trace 时间线的右边
E 在当前鼠标的位置,居中于 trace 时间线
G 在当前选中任务的开始处,显示网格
Shift + G 在当前选中任务的结束处,显示网格
右箭头 在当前选中的时间线,选择下一个事件
左箭头 在当前选中的时间线,选择前一个事件

植入你 App 的代码

由于 systrace 只在系统层级显示进程的信息,因此很难通过网页报告知道给定的时间内,App 究竟执行了哪些方法。在 Android 4.3 (API level 18) 及以上,可以在代码中使用 Trace 类来在网页报告中标记执行的事件。事实上,不需要植入代码中,用 systrace 记录 traces,这样做能帮助我们定位,代码的哪一块也许是造成线程阻塞或 UI 卡顿的原因。这个方法和 Debug 类不同,Trace 类只是简单地在 systrace 报告中添加标记,而 Debug 类通过生成 .trace 文件帮助我们审查使用 App 时 CPU 详细的使用信息。

注意,为了生成包括 trace 事件的 systrace 网页报告,需要指定 -a- -app 的命令来运行 systrace,这样来指定 App 的包名。

下面给出如何使用 Trace 类来标记执行方法的示例,包括两个嵌套的代码块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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 {
// try...catch 语句中, 总是要调用 endSection(), 在 finally 语句
// 中调用以确保即使抛出异常时 endSection() 也能执行
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() 只终止最近一次调用的 beginSection() 方法。因此,对于嵌套调用,如上面的代码所示,确保每次调用 beginSection(),同样恰当地调用到 endSection()。此外,不能在一个线程调用 beginSection(),然后从另一个线程终止,而应该在相同的线程调用到 endSection() 方法

至此,谈谈 systrace 完毕。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流

更多内容

深入 Android 性能优化一:概要介绍