一、概述
保证系统流畅度,也就是保证系统能连续不间断地提供每秒60帧的运行状态。当出现掉帧时(也可称为Jank),需要知道当前整个系统所处的状态,systrace便是最佳的选择,它能手机检测Android系统各个组件随着时间的运行状态,并能提示该如何有效地修复问题,接下来说说systrace如何使用以及如何解读。
二、Systrace
第1步:如何启动Monitor:
1) 安装完Android SDK并下载platform tools后,在SDK的目录下即可看到Monitor工具:
2) 也可以通过eclipse/Android Studio 启动Monitor:
3) 启动后如下:
说明:如果无法看到设备信息的话,请检查adb连接是否正常。
第2步:如何配置和抓取:
1)点击上图中“用户进程”右上角的systrace按钮会来到如下界面:
说明1:trace输出的文件路径
说明2:配置抓取systrace的时间,通常设置5秒,并在5秒内重现问题,时间太短会导致问题重现时没有被抓到,时间太长会导致JavaHeap不够而无法保存,因此在能抓到问题点的情况下,时间越小越好。
说明3:Buffer Size是存储systrace的size,同样的,太小会导致信息丢失,时间太长会导致Java Heap不够而无法保存,建议20480。
说明4:如果用户有自己在应用程序中加入自己的systrace log,如下:
Trace.beginSection("newInstance");
Trace.endSection("newInstance");
那么此处必须选择这个应用对应的进程名字,否则新加的systrace log不会被抓到。
说明5:这部分TAG全部使能上。
说明6:针对UI相关的性能问题,以下TAG必须使能:Input/Window Manager/Activity Manager/Bionic C Library/ CPU Freq/CPU Idle/CPU load. 如果设备可以root,root后可以看到更多的TAG,root后请使能:eMMC commands/ Synchonization/Kernel Workqueues.
2) 设置完成后点击“OK"开始抓取systrace,请在此时重现性能问题,设置的时间到后,trace.html文件会被自动保存下来。
第3步:检查systrace是否可用:
使用浏览器打开抓下来的html文件。如果systrace中出现如下很多”Did Not Finish",就是因为BufferSize太小导致信息丢失,此时调整buffer size大小并重新抓取:
出错处理:
1)java heap space 错误:
这种错误通常是因为内存不足,请按如下步骤检查:
A. BufferSize是不是设置太大了,尽量减少,但是太小会导致信息丢失,以已抓到的systrace不丢失信息为前提,尽量减少bufferSize.
B. 抓的时间太长,在能重现性能问题的范围内,减少时间
C. 手机内存太低?建议用一键清理的软件清理后台任务后再试。
D. 抓取systrace前,点击右下图”垃圾“图标回收部分内存:
2) buffer size 错误:
这种错误是因为buffer size太大,减少buffer size.
如果生成的trace文件无法直接打开,请在chrome中输入:chrome://tracing/,然后选择load按钮,把生成的html load进来2.2 使用命令行启动Trace(4.3以上系统,api18以上):
命令行
Python systrace.py [options] [category1] [category2] ... [categoryN]
systrace.py在:
android-sdk/platform-tools/systrace
2.2.1 options
其中options可取值:
options | 解释 |
---|---|
-o <FILE> | 输出的目标文件 |
-t N, –time=N | 执行时间,默认5s |
-b N, –buf-size=N | buffer大小(单位kB),用于限制trace总大小,默认无上限 |
-k <KFUNCS>,–ktrace=<KFUNCS> | 追踪kernel函数,用逗号分隔 |
-a <APP_NAME>,–app=<APP_NAME> | 追踪应用包名,用逗号分隔 |
–from-file=<FROM_FILE> | 从文件中创建互动的systrace |
-e <DEVICE_SERIAL>,–serial=<DEVICE_SERIAL> | 指定设备 |
-l, –list-categories | 列举可用的tags |
2.2.2 category
category可取值:
category | 解释 |
---|---|
gfx | Graphics |
input | Input |
view | View System |
webview | WebView |
wm | Window Manager |
am | Activity Manager |
sm | Sync Manager |
audio | Audio |
video | Video |
camera | Camera |
hal | Hardware Modules |
app | Application |
res | Resource Loading |
dalvik | Dalvik VM |
rs | RenderScript |
bionic | Bionic C Library |
power | Power Management |
sched | CPU Scheduling |
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 |
2.2.3 示例
例如,在systrace.py所在目录下执行指令:
Python systrace.py -b 32768 -t 5 -o mytrace.html wm gfx input view sched freq
又例如,输出全部的trace信息
Python systrace.py -b 32768 -t 5 -o mytrace.html gfx input view webview wm am sm audio video camera hal app res dalvik rs bionic power sched irq freq idle disk mmc load sync workq memreclaim regulators
注:收集trace,需要提前安装Python,并且一定要注意必须是Python 2.x,而不是能3.x,否则可能会出现问题。另外,buffer大小不可过大,否则会出现oom异常。
2.3 在代码中加入trace (api18以上支持)
你可以自己在一些方法里加入trace 方便自己 跟踪调试 , 如下:
Trace.traceBegin("performTraversals");
try {
} finally {
Trace.traceEnd();
}
你需要保证 traceBegin 与 traceEnd 一定要成对出现。 并且一定要在同一个线程里面。
加入trace的好处在于,生成的trace文件中,会在跟踪的代码段执行对应时间轴区间打上一个tag标记(比如上例中的performTraversals)
如果在代码中加入了trace,在生成trace文件时,必须指定进程为trace所在的进程(前面图中的第四步)
2.4 trace图分析:
纵轴代表着时间线,事件记录按照进程分组,同一个进程内按线程进行纵向拆分,每个线程记录自己的工作,可收缩/展开,如下图:
在本例中,一共有三个组:Kernel, SurfaceFlinger, App,他们分别以包名为标识。每个应用进程都会包含其中所有线程的记录信号,你可以看到从InputEvent到RenderThread都有
除了进程和线程运行信息,还有两个重要信息:
Frame:
在每个app进程,都有一个Frames行,正常情况以绿色的圆点表示。当圆点颜色为黄色或者红色时,意味着这一帧超过16.6ms(即发现丢帧),这时需要通过放大那一帧进一步分析问题。对于Android 5.0(API level 21)或者更高的设备,该问题主要聚焦在UI Thread和Render Thread这两个线程当中。对于更早的版本,则所有工作在UI Thread。
Alerts:
Systrace能自动分析trace中的事件,并能自动高亮性能问题作为一个Alerts,建议调试人员下一步该怎么做。
比如对于丢帧是,点击黄色或红色的Frames圆点便会有相关的提示信息;另外,在systrace的最右侧,有一个Alerts tab可以展开,这里记录着所有的的警告提示信息。
当我们点击了Alerts或者点击右边的Alerts列表中的任何一点我们可以看到在界面的最底部会相对应的优化提示以及可能会出现优化的视频教程链接。
比如上面的提示说你View的draw绘制花的时间太长了,然后我们可以根据Description来很明白的看到提示的内容是什么。
然后我们可以点击一块Frames中的F来查看,同样的它会生成一份跟Alerts类似的报告结果并放在界面的最低端。我们可以通过按下m键查看这一帧到下一帧所花费的时间以及哪个方法被调用的最长。
可以明显看到这时间>16.6ms,系统要求UI的60fps水准所以系统会报出黄色的警告。照样我们从Description中可以读出到底是哪里出了问题
Description
ListView item recycling involved inflating views. Ensure your Adapter#getView() recycles the incoming View, instead of constructing a new one.
- 1
- 2
- 1
- 2
我们可以看出来系统提示你在getView中花费了太多时间没能很有效的复用机制。我们就能顺着这条路去找界面的代码哪里出现了不足从而优化完善。
当然Systrace无法帮你定位到代码里面的具体到某一行代码,但是我们可以通过Alerts和Frames来能基本上优化了不足的地方,然后我们可以根据TraceView来分析具体函数花了多长时间来进一步优化代码提高性能。
上面介绍了Systrace中不同区域的功能。当然最有趣的还是Alerts和Frames两栏,它们展示了通过手机来的数据而生成出来的可视化分析结果。让我们来选择最上方的alerts瞧瞧:
这个警告指出了,有一个View#draw()方法执行了比较长的时间。我们可以在下面看到问题的描述,链接,甚至是相关的视频。下面我们看Frames这一行,可以看到这里展示了被绘制出来的每一帧,并且用绿、黄、红三颜色来区分它们在绘制时的性能。我们选一个红色帧来瞅瞅:
在最下方,我们看到了与这一帧所相关的一些警告。在这三个警告中,有一个是我们上面所提到的(View#draw())。接下来我们在这一帧处放大并在下方展开“Inflation during ListView recycling”这条警告:
我们可以看到警告部分的总耗时,32毫秒,远高于了我们对保障60fps所需的16毫秒绘制时间。同时还有更多的ListView每个条目的绘制时间,大约是6毫秒每个条目,总共五个。而Description描述项中的内容会帮助我们理解问题,甚至提供问题的解决方案。回到我们上一张图片,我们可以在“inflate”这一个块区处放大,并且观察到底是哪些View在被填充过程中耗时比较严重。
下面是另外一个渲染过慢的实例:
在选择了某一帧之后,我们可以按“m”键来高亮这一帧,并且在上方看到了这一部分的耗时,如图,我们看到了这一阵的绘制总共耗时超过19毫秒。而当我们展开这一帧唯一的一个警告时,我们发现了“Scheduling delay”这条错误。
Scheduling delay(调度延迟)的意思就是一个线程在处理一块运算的时候,在很长一段时间都没有被分配到CPU上面做运算,从而导致这个线程在很长一段时间都没有完成工作。我们选择这一帧中最长的一块,从而得到更加详细的信息:
在红框区域内,我们看到了“Wall duration”,他代表着这一区块的开始到结束的耗时。之所以叫作“Wall duration”,是因为他就像是墙上的一个时钟,从线程的一开始就为你计时。
但是,CPU Duration一项中显示了实际CPU在处理这一区块所消耗的时间。
很显然,两个时间的差距还是非常大的。整个区块耗时18毫秒,而在这之中CPU只消耗了4毫秒的时间去运算。这就有点奇怪了,所以我们应该看一下在这整个过程之中,CPU去干吗了。
可以看到,所有四个线程都非常的繁忙。
选择其中的一个线程会告诉我们是哪个程序在占用他,在这里是一个包名为com.udinic.keepbusyapp的程序。在这里,由于另外一个程序占用CPU,导致了我们的程序未能获得足够的CPU资源。
但是这种情况其实是暂时的,因为被其他后台应用占用CPU的情况并不多见(- -),但仍有其他应用的线程或是主线程占用CPU。而Traceview也只能为我们提供一个概览,他的深度是有限的。所以要找到我们app中到底是什么让我们的CPU繁忙,我们还要借助另一个工具——Traceview。
三、 快捷操作
3.1 导航操作
导航操作 | 作用 |
---|---|
w | 放大,[+shift]速度更快 |
s | 缩小,[+shift]速度更快 |
a | 左移,[+shift]速度更快 |
d | 右移,[+shift]速度更快 |
3.2 快捷操作
常用操作 | 作用 |
---|---|
f | 放大当前选定区域 |
m | 标记当前选定区域 |
v | 高亮VSync |
g | 切换是否显示60hz的网格线 |
0 | 恢复trace到初始态,这里是数字0而非字母o |
一般操作 | 作用 |
---|---|
h | 切换是否显示详情 |
/ | 搜索关键字 |
enter | 显示搜索结果,可通过← →定位搜索结果 |
` | 显示/隐藏脚本控制台 |
? | 显示帮助功能 |
四、案例分析
在recyclerview的bindview中使用sleep模拟耗时操作,滚动时会出现明显卡顿
@Override public void onBindViewHolder(StaggredAdapter.MyHolder holder, int position) { holder.mivItem.setImageResource(mlstPics.get(position)); Trace.beginSection("aaa"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Trace.endSection(); }
使用systrace跟踪,生成如下分析图
找到分析进程对应的ui线程,线程的纵向信息记录的是调用关系,横向信息记录的是调用顺序,代码中的标记“aaa”标记了sleep的时间段,可以按m键高亮显示这个标记对应的时间段,可以看出以下信息:
1.丢帧,在aaa标记时段的前面,有一帧标红的frame,它跟下一个frame之间间隔事件很长(代码sleep中)
2.调用关系,RV滚动-》bindview-》aaa(sleep)