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
这个时候,在设备上操作应用,使应用进入到待调试的状态,比如需要调试某个页面 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,浏览器打开界面如下:
(图源来自官网)
颜色块
每块颜色占据的长度即为该系统或者自定义trace等执行所占据的时间长度
Alerts
含有三角状的圆圈图标,对应出现警告的位置,点击可以在右边栏Alerts查看具体警告内容;
警告会告诉你可能丢帧或者卡顿等的原因
Frame
含有F字母的圆圈图标,对应每一帧开始的位置,不同颜色有不同意义;
绿色表示正常,当颜色为橙色或者红色时,意味着这一帧超过16.6ms(即发现丢帧);
Kernel
(上图为四核CPU)显示每个CPU各自执行的系统方法或自定义trace块,以及占据的时间长度
SurfaceFlinger
surfaceFilnger,进程id为118,显示系统方法以及占据的时间长度
com.android.janktown
应用进程,进程id为13409,显示应用进程内各个线程等信息
每个线程有颜色表示各自不同的状态
- 灰色:正在休眠。
- 蓝色:可运行(它可以运行,但是调度程序尚未选择让它运行)。
- 绿色:正在运行(调度程序认为它正在运行)。
- 红色:不可中断休眠(通常在内核中处于休眠锁定状态)。可以指示 I/O 负载,在调试性能问题时非常有用。
- 橙色:由于 I/O 负载而不可中断休眠。
分析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,则呈现出黄色或者红色
图片来源 systrace
在上图中,选中存在问题的黄色帧以后,需要注意两部分,如下所示
第一个红色框中,高亮的部分是这一帧在 UI 线程和 RenderThread 线程中都调用了哪些方法
第二个红色框中,展示了一些信息,包括非常有用的该帧出问题的原因(Alert & Description),这些都是系统给出的存在的问题和优化建议
点击F,使用快捷键f放大该帧,可以选择m高亮该选区,查看该帧的所有系统trace块执行时间
查看下面面板的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