Android SystemTrace实战

SystemTrace使用

在命令行输入如下命令:

python /Users/easyliu/Library/Android/sdk/platform-tools/systrace/systrace.py –time=10 -o mynewtrace.html

然后启动App,10s之后会在当前目录下面生成一个文件mynewtrace.html文件,这个文件只有使用chrome浏览器才能打开,如果使用chrome浏览器打不开,可以在chrome浏览器里面输入网址:https://ui.perfetto.dev/#!/viewer,在这个界面里面也可以打开这个trace文件

关于systrace更复杂的命令使用方式,参考:
https://developer.android.com/topic/performance/tracing/custom-events?hl=zh-cn

在很多情况下,我们需要加入一些自己的event,比如我们想统计自定义Activity的onCreate方法的耗时,可以使用如下方式添加自定义的event:

1
2
3
Trace.beginSection("SplashVideoDetailActivity#onCreate");
super.onCreate(savedInstanceState);
Trace.endSection();

要想自定义的event能够在trace文件里面展示出来,还需要在运行systrace命令的时候通过-a指定包名:

1
python systrace.py -a com.example.myapp --time=10 -o mynewtrace.html

还有一点,默认情况下,只有debug才支持自定义的event,原因是在ActivityThread的handleBindApplication方法里面有如下代码:

1
2
boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
Trace.setAppTracingAllowed(isAppDebuggable);

为了让release包也支持自定义的event,可以使用反射的方式修改:

1
2
ReflectUtil.invokeMethod(Trace.class, "setAppTracingAllowed",
null, new Class[]{boolean.class}, new Object[]{true});

SystemTrace锁耗时分析

抓取到trace之后,一般情况下我们都是直接分析UI线程的执行情况,如下所示:
system_trace
通过这个图可以很清晰的看到整个app启动过程的一个耗时情况,包括各个阶段的具体耗时情况,比如从上面的图我们发现在SplashVideoDetailActivity#onCreate方法里面两段很耗时的操作,我看来看一下第一段的耗时情况:
system_trace_lock
选中这个区域,底部会展示出这段时间主线程耗时的具体的信息:

Name monitor contention with owner init-thread (32284) waiters=2 blocking from void java.lang.Runtime.load0(java.lang.Class, java.lang.String)(Runtime.java:-1)

从这个信息可以很明确,这里UI卡住是因为调用了java.lang.Runtime.load0方法,这个方法在等待线程init-thread (32284)释放锁,那说明这个方法有锁?我们来看下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
synchronized void load0(Class<?> fromClass, String filename) {
if (!(new File(filename).isAbsolute())) {
throw new UnsatisfiedLinkError(
"Expecting an absolute path of the library: " + filename);
}
if (filename == null) {
throw new NullPointerException("filename == null");
}
String error = nativeLoad(filename, fromClass.getClassLoader());
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
}

可以看到这个方法确实是有加锁的。既然是这样的话,我们应该可以在工程代码里面找到这个名字叫做init-thread的线程。通过全局查找,找到了这个线程就是启动Task线程池里面的,说明启动task里面有一个子线程的task有loadLibrary的动作,导致主线程去loadLibrary的时候就卡住了。然后再根据UI线程卡住的时间点,就可以缩小范围,最终就能找到loadLibrary的是哪个task,然后就可以再进一步分析了