Systrace
简单的性能优化,可能很多人都会。比如以下几个优化 UI 渲染的方法,想必很多人都知道
-
使用“设置 --> 开发者选项 --> 调试 GPU 过度绘制”,根据屏幕显示的不同颜色来区分是存在过度绘制,从而排查该界面的 xml 文件,去除不必要的 background,消除过度绘制
-
通过 Layout Inspector 查看布局层级,排查是否存在多层无用的嵌套(由于 Hierarchy Viewer 已经被废弃,如果使用 3.1 及更新版本的 Android Studio,使用 Layout Inspector 查看布局会更加方便)
-
在 xml 中使用 ViewStub & merge 标签,优化布局层级
-
......
上面的这些点当然很重要,但是在某种程度下,上面的这些做法已经力不从心了,我们需要通过其他方式来达到优化性能的目的
俗话说的好,工欲善其事,必先利其器,使用一个好的工具当然可以让我们事半功倍,由于 TraceView 过于严重的运行时开销,使得 TraceView 测量的很多数据偏差较大,所以 Google 现在强推 systrace,systrace 是一个非常强大的性能分析工具。
systrace 可以从系统层面上,收集并分析设备运行时的所有进程的时间信息,它从 Android 内核中,比如:CPU 调度、磁盘活动和 app 线程中收集信息,然后生成如下图所示的 html 文件,需要说明的是:生成的 trace.html 文件必须用 Chrome 浏览器打开才可以正常的浏览使用.
图片来源:systrace。如上图所示,‘Frames’ 那一行里面的每一个小圆圈就代表着每一帧,用不同的颜色来代表是否正常的渲染,如果某一个小圆圈用黄色/红色表示,则表明这一帧的渲染可能存在问题
好,接下来我们来看下如何使用 systrace 工具
二. 如何使用
我使用的 Mac 电脑,所以以下操作都是在 Mac 上进行的,在 Windows 系统上应该也大同小异。
2.1 准备工作
在使用 systrace 之前,需要做以下几个准备工作
-
较新的 Android SDK Tools
-
需要 PC 端配合,PC 端安装了 Python 且配置在了系统环境变量中
-
调试的设备需要是 4.3(API Level 18)以上的,系统越高,可以收集到的信息越多,越有利于分析,分析的应用需要是 debug 包
-
通过 usb 将 Android 设备和 PC 连接成功,处于可调试的状态
至此,准备工作已完成
2.2 使用
2.2.1 使用方法
通过 Terminal 进入到 /Android/sdk/platform-tools/systrace/ 目录下:
cd /Users/(个人电脑用户名)/Library/Android/sdk/platform-tools/systrace
或者直接,不知道systrace的目录的情况下用以下方法,然后使用命令pwd
,即可知道当前所在的完整路径:
cd $ANDROID_HOME/platform-tools/systrace
![](https://img.haomeiwen.com/i6213579/66d9933ab8cc9c67.png)
这个时候,在设备上操作应用,使应用进入到待调试的状态,比如需要调试某个页面 RecyclerView ,则进入该页面
然后在 Terminal 里面运行如下命令,其中 [options] [categories]
都是需要输入的参数:
./systrace.py [options] [categories]
举例:
调用systrace来记录10秒钟内的设备进程,包括图形进程,并生成mynewtrace.html报告:
python systrace.py --time=10 -o /Users/didi/Downloads/mynewtrace.html gfx
参数信息说明
那么 [options] 和 [categories] 都包括哪些参数呢?
options参数表
options | description |
---|---|
-o < FILE > | 指定输出的文件,如:-o mynewtrace.html。如果没有指定此参数,systrace会将您的报告保存到systrace.py所在的同一目录中,并将其命名为trace.html |
-t N, –time=N | 指定 systrace 的持续时间,如 -t 10,表示记录 10s 钟,<T>的单位是 s 秒。如果没有指定此参数,在按下回车键 Enter 健时结束 systrace |
-b N, –buf-size=N | buffer大小(单位kB),用于限制trace总大小,默认无上限 |
-a < APP_NAME >,–app=< APP_NAME > | 指定特定的应用,比如:-a com.lijiankun24.shadowlayout。如果在此应用中使用了 Trace.beginSection("tag") 和 Trace.endSection,默认情况下,这些标签是不会生效的,除非你通过此命令指定该应用,在 systrace 输出的 html 文件中才会记录该标签标记的方法的信息 |
-h , --help | 显示帮助信息 |
-l,--list-categories | 列出可用于连接设备的跟踪categories类别 |
-k functions,--ktrace=functions | 跟踪中指定的特定内核函数的活动,以逗号分隔的列表 |
-e device-serial,--serial=device-serial | 跟踪指定的设备序列号标识的特定连接设备 |
catagories参数表
category | description |
---|---|
sched | CPU 的调度信息,可以看到 CPU 的每个核在具体的时间点执行了什么线程 |
gfx | Graphics 渲染系统,包括 SurfaceFlinger、VSync、Texture、RenderThread 的信息 |
input | 输入事件系统,记录键盘输入、触摸等事件信息 |
view | View 视图系统,常见的 View 的 onMeasure、onLayout、onDraw 都记录在此系统中 |
webview | WebView |
wm | WindowManager 的调用信息记录在此模块中 |
am | ActivityManager 的调用信息记录在此模块中 |
sm | Sync Manager |
audio | Audio |
video | Video |
camera | Camera |
hal | Hardware Modules |
app | Application |
res | Resource Loading |
dalvik | 虚拟机相关信息,比如 GC 垃圾回收信息 |
rs | RenderScript |
bionic | Bionic C Library |
power | Power Management |
irq IRQ | Events |
freq | CPU Frequency |
idle | CPU Idle |
disk | Disk I/O |
mmc | eMMC commands |
load | CPU Load |
sync | Synchronization |
workq | Kernel Workqueues |
memreclaim | Kernel Memory Reclaim |
regulators | Voltage and Current Regulators |
生成 trace.html 文件大概就是这样,并不复杂,下面介绍几个查看此 html 文件的快捷键,通过下面几个常用的快捷键,可以方便的查看 html 文件
快捷键
查看Systrace生成的trace.html,浏览器打开界面如下:
![](https://img.haomeiwen.com/i6213579/8f8aac090d624031.png)
(图源来自官网)
颜色块
每块颜色占据的长度即为该系统或者自定义trace等执行所占据的时间长度
Alerts
含有三角状的圆圈图标,对应出现警告的位置,点击可以在右边栏Alerts查看具体警告内容;
警告会告诉你可能丢帧或者卡顿等的原因
Frame
含有F字母的圆圈图标,对应每一帧开始的位置,不同颜色有不同意义;
绿色表示正常,当颜色为橙色或者红色时,意味着这一帧超过16.6ms(即发现丢帧);
Kernel
(上图为四核CPU)显示每个CPU各自执行的系统方法或自定义trace块,以及占据的时间长度
SurfaceFlinger
surfaceFilnger,进程id为118,显示系统方法以及占据的时间长度
com.android.janktown
应用进程,进程id为13409,显示应用进程内各个线程等信息
每个线程有颜色表示各自不同的状态
- 灰色:正在休眠。
- 蓝色:可运行(它可以运行,但是调度程序尚未选择让它运行)。
- 绿色:正在运行(调度程序认为它正在运行)。
- 红色:不可中断休眠(通常在内核中处于休眠锁定状态)。可以指示 I/O 负载,在调试性能问题时非常有用。
- 橙色:由于 I/O 负载而不可中断休眠。
![](https://img.haomeiwen.com/i6213579/d4ae956641f1f8aa.png)
分析trace.html图形信息之前,先了解下快捷键
点击浏览器界面上左上角“?”,可以查看到各个快捷键提示
快捷键 | 作用 |
---|---|
w | 放大时间轴,[+shift]速度更快 |
s | 缩小时间轴,[+shift]速度更快 |
a | 左移时间轴,[+shift]速度更快 |
d | 右移时间轴,[+shift]速度更快 |
f | 放大当前选定区域 |
m | 标记当前选定区域 |
Right Arrow | 选中所选时间轴上的下一个事件 |
Left Arrow | 选中所选时间轴上的上一个事件 |
v | 高亮VSync |
g | 切换是否显示60hz的网格线 |
0 | 恢复trace到初始态,这里是数字0而非字母o |
h | 切换是否显示详情 |
/ | 搜索关键字 |
enter | 显示搜索结果,可通过← →定位搜索结果 |
` | 显示/隐藏脚本控制台 |
? | 显示帮助功能 |
分析trace.html
Systrace可以直观的看到掉帧引起的界面卡顿
如下图所示,是一个放大后的 trace.html 的局部图。我们都知道 Android 系统中的 60 fps 概念,也就是 1s 内会渲染 60 帧,渲染一帧需要 16.6 ms,下图中用红色框起来的就是每一个 frame,如果在 16.6 ms 内完成了渲染,则该帧是绿色的,如果渲染超过了 16.6 ms,则呈现出黄色或者红色
![](https://img.haomeiwen.com/i6213579/aed29f1a6d7128ba.png)
图片来源 systrace
在上图中,选中存在问题的黄色帧以后,需要注意两部分,如下所示
-
第一个红色框中,高亮的部分是这一帧在 UI 线程和 RenderThread 线程中都调用了哪些方法
-
第二个红色框中,展示了一些信息,包括非常有用的该帧出问题的原因(Alert & Description),这些都是系统给出的存在的问题和优化建议
点击F,使用快捷键f放大该帧,可以选择m高亮该选区,查看该帧的所有系统trace块执行时间
![](https://img.haomeiwen.com/i6213579/804ed7bb30ac710b.png)
查看下面面板的Frame里的信息
ListView recycling takiing too mush time per frame.Ensure your Adapter#getView() binds data efficiently
Alerts选项卡可以查看每个警报以及设备触发每个警报的次数。如果我们在上图中,选中右上角的 Alerts tab,会出现如下图所示的信息,它告诉我们在这段时间内该问题出现的频次,比如下图所示的:Inefficient ListView recycling/rebinding
共出现了 55 次。
主要问题是在ListView回收和重新绑定中花费了太多时间。
如果你在UI线程上看到了太多的工作,你需要找出哪些方法消耗了太多的CPU时间。
可以把 Alerts tab 当做一个需要处理的 bug 列表,这个列表中的问题都不同程度上的对我们的帧渲染造成了问题。有时候可能只是几行代码的微小改动和优化,却可以优化我们很多的问题
3.2 为自己的应用添加 Trace 信息
默认情况下,systrace 都只能记录、收集系统层面的信息,比如 WindowManager
、ActivityManager
、以及 Dalvik 等等模块的,有没有什么办法也记录收集自己应用中的一些信息呢?
Android 是提供了这样的 Api 的,这个类是 Trace
类,使用 Trace
类记录自己应用中的信息其实并不难,如下所示,有如下几点需要注意
-
Trace.beginSection(String sectionName)
和Trace.endSection()
需要成对出现,为保证每个Trace.beginSection(String sectionName)
都会有对应的Trace.endSection()
,建议使用try {……} finally {……}
-
如果在
Trace.endSection()
之前有多个Trace.beginSection(String sectionName)
,Trace.endSection()
会匹配离它最近的一个未匹配过的Trace.beginSection(String sectionName)
-
Trace.beginSection(String sectionName)
和Trace.endSection()
需要在同一线程中
public class CardViewListActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Trace.beginSection("CardViewListActivity_onCreate");
try {
setContentView(R.layout.activity_card_view_list);
RecyclerView recyclerView = findViewById(R.id.rv_card_view);
recyclerView.setLayoutManager(new LinearLayoutManager(CardViewListActivity.this));
recyclerView.setAdapter(new CardViewListAdapter());
} finally {
Trace.endSection();
}
}
private static class CardViewListAdapter extends RecyclerView.Adapter<CardViewHolder> {
@NonNull
@Override
public CardViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
Trace.beginSection("CardViewListAdapter_onCreateViewHolder");
CardViewHolder viewHolder;
try {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_card_view_list, null);
Trace.beginSection("CardViewListAdapter_onCreateViewHolder_newHolder");
try {
viewHolder = new CardViewHolder(view);
} finally {
Trace.endSection();
}
} finally {
Trace.endSection();
}
return viewHolder;
}
}
}
在自己应用的代码中添加如上代码之后并没有结束,还有一点非常重要,在执行 systrace 命令的时候,需要通过 -a <package_name>
指定应用包名,这样才会记录、收集到自己应用中添加的 trace 信息,如下所示:
./systrace.py -t 10 -o mytrace.html -a com.lijiankun24.shadowlayout sched freq idle am wm gfx view binder_driver hal dalvik camera input res
在生成的 trace.html 文件中,可以通过右上角的查找,找到 sectionName,就可以查到该 Trace 的记录信息
3.3 原理浅析
其实 systrace 的思想很简单,就是在一些关键路径中打 log,通过 log 的开始和结束就可以得到一个方法的执行时间信息,然后将这些 log 收集起来,就可以得到关键路径的运行时间信息,进而得到整个系统的运行性能信息。
在 Android 应用、Android Framework 和 native 层通过不同的方法或类打 log
3.3.1 Android Framework
import android.os.Trace;
Trace.traceBegin(long traceTag, String methodName)
Trace.traceEnd(long traceTag)
比如在 ActivityThread
中的内部类 H.handleMessage(Message msg)
方法如下所示
在 Android Framework 中是通过 Trace.traceBegin(long traceTag, String methodName)
方法打 log 的,传入的 traceTag 是 Trace
类中的常量类,如下所示
其实这里的 Trace
常量值,和我们在执行 ./systrace [options] [categories]
时,传入的 [categories]
值对应的
3.3.2 Android 应用
对应的 traceTag 名称是 TRACE_TAG_APP
,在使用 systrace.py 命令运行时,需要通过 -a <package-name>
指定应用的包名,才可以收集到埋的 tag
import android.os.Trace;
Trace.beginSection(String sectionName)
Trace.EndSection()
Trace
类的源码如下,可见
traceBegin(long traceTag, String methodName)
、
traceEnd(long traceTag)
、
beginSection(String sectionName)
、
endSection()
最后都调用了 native 方法
nativeTraceBegin(long tag, String name)
和
nativeTraceEnd(long tag)
public final class Trace {
@FastNative
private static native void nativeTraceBegin(long tag, String name);
@FastNative
private static native void nativeTraceEnd(long tag);
private Trace() {
}
public static void traceBegin(long traceTag, String methodName) {
if (isTagEnabled(traceTag)) {
nativeTraceBegin(traceTag, methodName);
}
}
public static void traceEnd(long traceTag) {
if (isTagEnabled(traceTag)) {
nativeTraceEnd(traceTag);
}
}
public static void beginSection(String sectionName) {
if (isTagEnabled(TRACE_TAG_APP)) {
if (sectionName.length() > MAX_SECTION_NAME_LEN) {
throw new IllegalArgumentException("sectionName is too long");
}
nativeTraceBegin(TRACE_TAG_APP, sectionName);
}
}
public static void endSection() {
if (isTagEnabled(TRACE_TAG_APP)) {
nativeTraceEnd(TRACE_TAG_APP);
}
}
}
3.3.3 native 层
其实 systrace 本质上是对其他工具的封装,包括 PC 端的 atrace
和设备端的 ftrace
,ftrace
是 Linux 内核中的主要跟踪机制。systrace 使用 atrace
开启追踪,然后读取 ftrace
的缓存,并且把它重新转换成HTML格式
#include<utils/Trace.h>ATRACE_CALL();
其他
extra:
什么是atrace?什么是ftrace?
ftrace 是一种调试工具,用于了解 Linux 内核中的情况;而 atrace (frameworks/native/cmds/atrace) 使用 ftrace 来捕获内核事件;
官网简单的介绍地址:https://source.android.google.cn/devices/tech/debug/ftrace
参考:https://zhuanlan.zhihu.com/p/27331842
转载于:https://blog.csdn.net/chuyouyinghe/article/details/115657537
网友评论