前言
编写本文时使用的操作系统环境为 Ubuntu 20.04.3 LTS ,使用的 Android Studio 为最新版的 Arctic Fox。
1. Systrace 简介
1.1 简介
Systrace 就是 System Trace 的简称,可以记录一段时间内系统的活动状态,并可以生成HTML格式的报告。开发者可以通过分析这份报告,发现应用和系统的性能瓶颈。
1.2 原理
Systrace 的原理很简单,Google 的开发人员在 Android 系统的一些关键组件(System Server、虚拟机等)已经预先做了埋点,Java 层使用 TraceCompat 进行埋点, Native 层可以使用使用 ATrace 。通过这些埋点就可以得到核心方法的执行开始和结束时间。
2. 启动 Systrace
在 Systrace 推出之初有以下两种启动方式:
2.1 使用 Android Device Monitor 启动 Systrace (过时)
在以往的Android Studio 版本中我们可以通过Android Device Monitor 来捕获System trace。但是这种方式已经过时了,捕获的Trace.html在新版的Chrome中打开会出现以下错误。
想搞明白什么会有这样的错误,可以参考这里的回答:https://stackoverflow.com/questions/36865899/react-native-android-systrace-html-is-blank-in-ubuntu-14-04
2.2 使用 Python 命令启动 Systrace (推荐)
使用Python 命令执行Systrace 共分三步。
- 从 Android Studio 下载并安装最新的 Android SDK 工具
2.安装 Python2 环境,Systrace 不支持 Python3
3.使用将 Android 4.3(API 级别 18)或更高版本的设备连接到电脑
4.在控制台中执行 Systrace 指令(下一节具体介绍)
systrace
命令在 Android SDK 工具软件包中提供,并且可以在 android/Sdk/platform-tools/systrace/
中找到。
-
Systrace 指令
Sysrace 在命令行中的语法结构如下
python systrace.py [options] [categories]
例如,以下命令会调用 systrace
来记录设备活动,并生成一个名为 mynewtrace.html
的 HTML 报告。此类别列表是大多数设备的合理默认列表。
# 使用前需要先cd到systrace.py所在的目录
python systrace.py -o mynewtrace.html sched freq idle am wm gfx view \
binder_driver hal dalvik camera input res
提示:如果要在跟踪输出中查看任务名称,必须在命令参数中添加
sched
类别。
3.1 Systrace 支持的 options
在控制台输入python systrace.py -h
可以查看Systrace
支持的所有options
以及对应的作用。st
是python systrace.py
的别名,需要预先在环境变量中定义,之后会介绍。
3.1.1 systrce -o
将 HTML 跟踪报告写入指定的文件。如果未指定此选项,systrace
会将报告保存到 systrace.py
所在的目录中,并将其命名为 trace.html
。'
python systrace.py -o trace.html
3.1.2 systrce -t N
跟踪设备活动 N 秒。如果未指定此选项,systrace
会提示在命令行中按 Enter 键结束跟踪.
python systrace.py -t 5
3.1.3 systrce -b N
使用 N KB 的跟踪缓冲区大小。使用此选项,可以限制跟踪期间收集到的数据的总大小。
python systrace.py -b 2048
3.1.4 systrce -k functions
跟踪逗号分隔列表中指定的特定内核函数的活动。
python systrace.py -k
3.1.5 systrce -a app-name
启用对应用的跟踪,指定为包含进程名称的逗号分隔列表。这些应用必须包含 Trace
类中的跟踪检测调用。应在分析应用时指定此选项。很多库(例如 RecyclerView
)都包括跟踪检测用,这些调用可在启用应用级跟踪时提供有用的信息。如需了解详情,请参阅定义自定义事件。如需跟踪搭载 Android 9(API 级别 28)或更高版本的设备上的所有应用,请传递用添加引号的通配符字符 "*"
。
python systrace.py -a
# 或
python systrace.py -a "*"
3.1.6 systrce --from-file=file-path
根据文件(例如包含原始跟踪数据的 TXT 文件)创建交互式 HTML 报告。
python systrace.py --form-file = trace.txt
3.1.7 systrce -e device-serial
在已连接的特定设备(由对应的设备序列号标识)上进行跟踪。
python systrace.py -e emulator-5554
设备序列号可以使用 adb devices 查看
3.2 Systrace 支持的 categories
使用 -l
指令可以查已连接设备可用的服务列表,如下所示,部分服务只能在取得root权限时才能追踪。
3.3 快捷启动Systrace
在Ubuntu系统的home目录中打开.bashrc文件,在最底部加入以下代码,
# Systrace
alias st='python /home/link/Android/Sdk/platform-tools/systrace/systrace.py'
alias st-start='python /home/link/Android/Sdk/platform-tools/systrace/systrace.py -o /home/link/桌面/TraceReport/trace.html'
然后命令行中执行 source .bashrc,使配置生效。
source .bashrc
这样我们就可以使用 st
和 st-start
来快速执行 systrace
脚本。
4. 浏览 Systrace 报告
以下内容部分摘录自Android官方文档 - 浏览Systrace报告 | Android开发者,为了便于阅读,在官方文档的基础上有一定修改和删减。
在Chrome浏览器(或是基于Chromium的Edge浏览器)的地址栏输入chrome://tracing
命令,然后将生成的trace.html
文件拖进来,或者通过load
按钮导入,可以看到如下所示的报告:
Systrace
会生成包含多个部分的输出 HTML 文件。该报告列出了每个进程的线程。如果给定线程会渲染界面帧,该报告还会沿时间轴指明所渲染的帧。将报告从左向右移动时,时间会向前推移。报告从上到下包含以下几个部分:
4.1 用户互动
第一部分包含表示应用或游戏中的具体用户互动(例如点按设备屏幕)的条形图。这些互动可用作有用的时间标记。
4.2 CPU 活动
下一部分显示了表示每个 CPU 中的线程活动的条形图。这些条形会显示所有应用(包括应用或游戏)中的 CPU 活动。
CPU 活动部分可以展开,展开后就可以查看每个 CPU 的休眠状态,C-State可以简单看做CPU的工作状态。更进一步的含义,可以参考这篇文章:https://www.cnblogs.com/apnpc/p/13780146.html
4.3 系统事件
此部分中的直方图会显示特定的系统级事件,例如特定对象的纹理计数和总大小。
值得仔细检查的直方图是标记为 SurfaceView 的直方图。计数表示已传递到显示管道并等待显示在设备屏幕上的组合帧缓冲区的数量。由于大多数设备都会进行双重或三重缓冲,因此该计数几乎总为 0、1 或 2。
描绘 Surface Flinger 进程(包括 VSync 事件和界面线程交换工作)的其他直方图,如图所示:
4.4 显示帧
这一部分通常是报告中最顶部的部分,描绘了一条多色线条,后面是成堆的条形。这些形状表示已创建的特定线程的状态和帧堆栈。堆栈的每个层级代表对 beginSection()
的一次调用,或为应用或游戏定义的自定义跟踪事件的开头。
注意:界面线程(即通常运行应用或游戏的主线程)始终会显示为第一个线程。
每个条形堆上方的多色线条表示特定线程随时间变化的一组状态。每段线条可以包含以下颜色之一:
绿色:正在运行
线程正在完成与某个进程相关的工作或正在响应中断。
蓝色:可运行
线程可以运行但目前未进行调度。
白色:休眠
线程没有可执行的任务,可能是因为线程在遇到斥锁定时被阻止。
橙色:不可中断的休眠
线程在遇到 I/O 操作时被阻止或正在等待磁盘操作完成。
紫色:可中断的休眠
线程在遇到另一项内核操作(通常是内存管理)时被阻止。
注意:在 Systrace 报告中,可以点击该线条以确定该线程在给定时间由哪个 CPU 控制。
4.4 键盘快捷键
下表列出了查看 Systrace
报告时可以使用的键盘快捷键:
快捷键 | 说明 |
---|---|
W | 放大跟踪时间轴。 |
A | 在跟踪时间轴上向左平移。 |
S | 缩小跟踪时间轴。 |
D | 在跟踪时间轴上向右平移。 |
E | 以当前鼠标位置为中定位跟踪时间轴。 |
M | 高亮当前选区。 |
1 | 将当前正在使用中的选择模型更改为“选择”模式。对应于鼠标选择器工具栏中显示的第 1 个按钮 |
2 | 将当前正在使用中的选择模型更改为“平移”模式。对应于鼠标选择器工具栏中显示的第 2 个按钮 |
3 | 将当前正在使用中的选择模型更改为“缩放”模式。对应于鼠标选择器工具栏中显示的第 3 个按钮 |
4 | 将当前正在使用中的选择模型更改为“计时”模式。对应于鼠标选择器工具栏中显示的第 4 个按钮 |
G | 在当前所选任务的开头显示网格。 |
Shift + G | 在当前所选任务的末尾显示网格。 |
向左箭头 | 在当前选定的时间轴上选择上一个事件。 |
向右箭头 | 在当前选定的时间轴上选择下一个事件。 |
4.5 调查性能问题
与 Systrace 报告互动时,可以检查记录期间的设备 CPU 使用情况。如需浏览 HTML 报告方面的帮助,请查看键盘快捷键部分,或点击报告右上角的 ? 按钮。
以下各部分介绍了如何检查报告中的信息以查找和修复性能问题。
4.6 识别性能问题
浏览 Systrace 报告时,可以通过执行以下一项或多项操作来更轻松地识别性能问题:
通过在时间间隔周围绘制一个矩形来选择所需的时间间隔。
使用标尺工具标记或突出显示问题区域。
依次点击 View Options > Highlight VSync,以显示每项显示屏刷新操作。
4.7 检查界面帧和提醒
注意:本节中的内容只面向托管代码,因为 Systrace 是通过查看系统基于 Java 的 Choreographer 来提供帧信息。如需面向原生代码(特别是游戏)的指导,请参阅关于帧速率一致性的讨论。
如所示,Systrace 报告列出了渲染界面帧的每个进程,并指明了沿时间轴渲染的每个帧。在 16.6 毫秒内渲染的必须保持每秒 60 帧稳定帧速率的帧会以绿色圆圈表示。渲染时间超过 16.6 毫秒的帧会以黄色或红色帧圆圈表示
注意:在搭载 Android 5.0(API 级别 21)或更高版本的设备上,渲染帧的工作拆分为界面线程和渲染线程。在以前的版本中,创建帧的所有工作都是在界面线程中完成的。
点击某个帧圆圈可将其突出显示,并提供有关系统为渲染该帧所做工作的其他信息,包括提醒。此报告还会显示系统在渲染该帧时执行的方法。可以调查这些方法以确定界面卡顿的可能原因。
选择运行速度慢的帧后,可能会在报告的底部窗格中看到一条提醒。图中显示的提醒指明帧的主要问题是在[ListView]
回收和重新绑定上花费了太多时间。指向跟踪记录中相关事件的链接可详细说明系统在此期间执行的操作。
如需查看此工具在的跟踪记录中发现的每条提醒以及设备触发每条提醒的次数,请点击窗口最右侧的 Alerts 标签页,如图所示。Alerts 面板可帮助了解跟踪记录中出现的问题以及这些问题导致出现卡顿的频率。可以将此面板视为要修正的错误列表。通常情况下,只需对一个区域进行细微改动或改进即可移除整组提醒。
如果发现在界面线程上执行的工作太多,请使用以下方法之一来帮助确定哪些方法占用了过多的 CPU 时间:
如果想了解哪些方法可能会导致瓶颈,请在这些方法中使用TraceCompat添加跟踪标记。具体的使用方式会在5.使用Systrace实战 中介绍。
如果不确定界面瓶颈的来源,请使用 Android Studio 中提供的CPU profiler。可以生成跟踪日志,然后使用 CPU 分析器导入和检查这些日志。
CPU Profiler 的使用方式可以参考这篇文章,APP 性能优化与分析CPU篇(一)-CPU Profiler
5. Systrace 实战
5.1 应用启动耗时分析
App冷启动耗时是衡量应用启动性能的一个重要指标,获取App启动耗时一般分为
- adb命令获取
可以获取到一个不足够严谨的启动时间,大部分情况下够用,但是只能在线下使用。
- 手动打点
需要自行应用中埋点,相对繁琐,但是时间足够精确,线上、线下都可以使用。
- 利用高速摄影机,逐帧计算
需要自己逐帧计算,而且获取到的时间也不是十分精确。但是使用非常方便,只需要一部支持延迟摄影功能的手机即可(例如iphone),适用于手边没有电脑的时候计算启动耗时。
这里演示在应用中埋点,并利用Systrace
计算启动耗时。在应用中埋点,主要基于TraceCompat类来实现。TraceCompat API 如下:
方法名 | 作用 |
---|---|
static void beginAsyncSection(String methodName, int cookie) | 开始异步追踪。之后必须使用相同的methodName和cookie调用endAsyncSection(String,int),结束这个追踪。与beginSection(String)和endSection()不同,异步事件不需要嵌套。 |
static void beginSection(String sectionName) | 开始追踪。之后必须在同一线程中调用endSection()结束追踪。 |
static void endAsyncSection(String methodName, int cookie) | 结束异步追踪。 |
static void endSection() | 结束追踪。 |
static boolean isEnabled() | 检查当前是否启用了跟踪。这有助于避免为需要格式化的跟踪节创建中间字符串。没有必要保护所有跟踪方法调用,因为它们在内部已经检查了这一点。但是,建议使用此选项来防止创建任何临时对象,这些临时对象在未启用跟踪时会传递给这些方法以降低运行时成本。 |
static void setCounter(String counterName, int counterValue) | 写入跟踪消息以指示给定计数器的值。 |
- 在应用代码中设置埋点。
public class JniApp extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// APP 启动 埋点
TraceCompat.beginAsyncSection("JNI-app-start",10);
}
}
public class MainActivity extends AppCompatActivity {
...
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// MainActivity 首帧绘制结束后添加埋点
TraceCompat.endAsyncSection("JNI-app-start",10);
}
...
}
注意:认为Activity的首帧绘制完毕就是APP启动完毕的情况仅限于首页相对简单的情况,多数情况也确实如此。但是如果开屏页很复杂,应该将开屏页最后一个组件绘制结束作为应用启动完毕的信号。示例如下:
xxxView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
TraceCompat.endAsyncSection("JNI-app-start",10);
return true;
}
});
- 在命令行运行
Systrace
指令,并指定应用的包名
st -a com.android.car.demo
得到如下的报告
- 选择应用的包名,过滤掉不需要的进程。
- 检索我们设定的标签,点击 报告中
->
切换到自定义事件。
- 按下
m
键,锁定事件。
从上图就可以看出,整个JNI-app-start
事件总共耗时 2.07s,而这就是应用的启动耗时。
如果想进一步分析为何启动耗时如此之长,需要继续在可疑的地方埋点,逐一排查。如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 第一处 可疑点,布局加载耗时过长?
TraceCompat.beginSection("JNI-layout");
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
TraceCompat.endSection();
// 第二处 可疑点,初始化耗时过长?
TraceCompat.beginSection("JNI-init");
initSomeThing();
TraceCompat.endSection();
TextView tv = binding.sampleText;
// 第三处 可疑点,JNI方法返回值耗时过长?
TraceCompat.beginSection("JNI-txt");
String txt = stringFromJNI();
TraceCompat.endSection();
tv.setText(txt);
}