Android性能优化系列-腾讯matrix-TracePlugin启动优化之StartupTracer源码分析
前言
StartupTracer是matrix中用来监控启动速度的一个trace类,代码位于matrix-trace-canary模块,属于TracePlugin中的专门针对启动场景的一种监控能力。在TracePlugin中还有各种类型的tracer,它们肩负着对不同场景下问题的监控,但核心都是卡顿监控,如帧率监控、慢方法监控、anr监控等等。这篇文章主要分析启动场景下的速度监控。
TracePlugin
由于StartupTracer依附于TracePlugin,所以我们需要先了解一下TracePlugin,TracePlugin继承自Plugin, Plugin是matrix中所有类型插件(插件指matrix中针对各方向的监控能力如io监控、memory监控,matrix将其定义为一个插件,因为每个功能都是可插拔的)的基类,负责定义插件运行的生命周期。其中生命周期包括init、start、onForeground、stop和destroy。matrix运行每个插件时,先执行init,再执行start。
init
TracePlugin初始化时,会先执行父类Plugin的init方法。简单看下父类的init,init中一个关键点,ProcessUILifecycleOwner将当前plugin加入到集合,可以监听到onForeground方法(在ProcessUILifecycleOwner初始化的时候通过app.registerActivityLifecycleCallbacks注册页面生命周期回调的方式)。
public void init(Application app, PluginListener listener) {
//插件运行的状态
status = PLUGIN_INITED;
//保存信息
this.application = app;
this.pluginListener = listener;
//回调生命周期
listener.onInit(this);
//将当前plugin加入到集合,可以监听到onForeground方法
ProcessUILifecycleOwner.INSTANCE.addListener(this);
}
回到TracePlugin自己的init方法,可以看到有多个tracer的初始化,本次我们只关注StartupTracer,init的时候将StartupTracer对象创建出来,看下构造方法。
@Override
public void init(Application app, PluginListener listener) {
super.init(app, listener);
if (sdkInt >= Build.VERSION_CODES.O) {
//默认为false,8.0以上为true,这里涉及到一个比较关键的点,先有个印象
supportFrameMetrics = true;
}
...
//启动监控
startupTracer = new StartupTracer(traceConfig);
}
StartupTracer构造方法。其中ActivityThreadHacker.addListener(this)是一个关键的操作, 通过将当前plugin加入到监听中,可以拿到application创建完成的回调-onApplicationCreateEnd()。
public StartupTracer(TraceConfig config) {
...
ActivityThreadHacker.addListener(this);
}
留一个疑问?ActivityThreadHacker是怎么监听到application的创建完成的? 为了使流程分析更清晰,这里会单独列一篇文章来讲。
继续看看onApplicationCreateEnd方法做了什么。
@Override
public void onApplicationCreateEnd() {
if (!isHasActivity) {
//拿到application创建的时长,进入分析
long applicationCost = ActivityThreadHacker.getApplicationCost();
analyse(applicationCost, 0, applicationCost, false);
}
}
analyse方法中,第一个参数表示application创建消耗的时长,第二个参数表示应用启动第一个页面消耗的时长,第三个总时长,第四个参数表示是否是热启动,很明显这里是false。
private void analyse(long applicationCost, long firstScreenCost, long allCost, boolean isWarmStartUp) {
long[] data = new long[0];
//冷启动,如果时长超过冷启动设置的阈值(默认10s),则将这段时间内运行的所有方法的耗时信息取出来进行分析。
if (!isWarmStartUp && allCost >= coldStartupThresholdMs) { // for cold startup
data = AppMethodBeat.getInstance().copyData(ActivityThreadHacker.sApplicationCreateBeginMethodIndex);
ActivityThreadHacker.sApplicationCreateBeginMethodIndex.release();
}
//热启动时,如果时长超过热启动设置的阈值(默认4s),则将这段时间内运行的所有方法的耗时信息取出来进行分析。
else if (isWarmStartUp && allCost >= warmStartupThresholdMs) {
data = AppMethodBeat.getInstance().copyData(ActivityThreadHacker.sLastLaunchActivityMethodIndex);
ActivityThreadHacker.sLastLaunchActivityMethodIndex.release();
}
//具体分析的操作
MatrixHandlerThread.getDefaultHandler().post(new AnalyseTask(data, applicationCost, firstScreenCost, allCost, isWarmStartUp, ActivityThreadHacker.sApplicationCreateScene));
}
从上边的方法中,我们提取出两个关键点问题:
- AppMethodBeat.getInstance().copyData()做了什么,什么逻辑?
- AnalyseTask内部是怎么实现的?
这两个问题后边单独来解答,先一窥全貌,然后再细化分析。
start
初始化完成,开始start方法,父类中做的很简单,不再多看,直接看TracePlugin的start方法。这里只保留跟本次分析有关的内容。
@Override
public void start() {
super.start();
Runnable runnable = new Runnable() {
@Override
public void run() {
if (willUiThreadMonitorRunning(traceConfig)) {
if (!UIThreadMonitor.getMonitor().isInit()) {
try {
UIThreadMonitor.getMonitor().init(traceConfig, supportFrameMetrics);
} catch (java.lang.RuntimeException e) {
return;
}
}
}
if (traceConfig.isAppMethodBeatEnable()) {
AppMethodBeat.getInstance().onStart();
} else {
AppMethodBeat.getInstance().forceStop();
}
UIThreadMonitor.getMonitor().onStart();
...
if (traceConfig.isStartupEnable()) {
//启动插件
startupTracer.onStartTrace();
}
}
};
...
}
其中关键的几项:
UIThreadMonitor
UIThreadMonitor与StartupTracer关联性不强,这里先有个印象。它是traceplugin中非常关键的一个类,它的内部通过setMessageLogging的方式来实现监听主线程每一条消息起始的回调,从而获取到每一条消息执行的耗时信息;以及通过对Choreographer的操作来获取消息队列中不同类型的消息(input、animation、traversal)执行的消耗总时长。
AppMethodBeat
onStartTrace
StartupTracer终于要开始启动了。onStartTrace执行后会进入StartupTracer的onAlive方法,从下边可以看出,StartupTracer的start方法做的工作并不多,只是做了监听,
protected void onAlive() {
super.onAlive();
MatrixLog.i(TAG, "[onAlive] isStartupEnable:%s", isStartupEnable);
if (isStartupEnable) {
//注册Listener,可以拿到onActivityFocused的回调
AppMethodBeat.getInstance().addListener(this);
//注册activity生命周期监听
Matrix.with().getApplication().registerActivityLifecycleCallbacks(this);
}
}
既然如此,我们就要把目光转移到监听的回调方法上去了。
onActivityCreated
Matrix.with().getApplication().registerActivityLifecycleCallbacks的回调方法是activity的各个生命周期方法,而这里使用到的也就只有onActivityCreated和下边的onActivityDestroyed。
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
MatrixLog.i(TAG, "activeActivityCount:%d, coldCost:%d", activeActivityCount, coldCost);
//activity数量为0,但是coldCost有值,表示热启动
if (activeActivityCount == 0 && coldCost > 0) {
lastCreateActivity = uptimeMillis();
isWarmStartUp = true;
}
activeActivityCount++;
if (isShouldRecordCreateTime) {
//记录当前activity的创建时间
createdTimeMap.put(activity.getClass().getName() + "@" + activity.hashCode(), uptimeMillis());
}
}
onActivityFocused
AppMethodBeat的listener的回调方法是onActivityFocused。首先要了解的是这个方法究竟是什么时候回调的,matrix是将什么时机作为页面focus的时机的。找到调用的位置可以看到下边的代码。
public static void at(Activity activity, boolean isFocus) {
synchronized (listeners) {
for (IAppMethodBeatListener listener : listeners) {
listener.onActivityFocused(activity);
}
}
}
好了,了解了它的来源,我们再看看它做了什么,只保留关键代码。可以看到这里是分为冷启动和热启动两种情况来分析的。具体的计算过程代码被我删掉了,冷启动最终会计算出这几个参数:
- applicationCost: application消耗的时长。
- firstScreenCost: 从application创建到第一个页面可见消耗的时长,一般第一个页面就是splash页面。
- coldCost:冷启动的总时长,是从application创建到主页页面可见消耗的时长。
而热启动只有warmCost一个值。
- warmCost:表示activity创建到页面可见消耗的时长。
public void onActivityFocused(Activity activity) {
if (isColdStartup()) {
...
analyse(ActivityThreadHacker.getApplicationCost(), firstScreenCost, coldCost, false);
} else if (isWarmStartUp()) {
...
analyse(0, 0, warmCost, true);
}
}
然后进入analyse进行分析,analyse方法上边提到过一次,就是在onApplicationCreateEnd方法中,也就是application执行完成后的一个时机,同样进行过分析,最终会开启一个AnalyseTask进行分析。所以AnalyseTask我们稍后统一看源码,这里先了解是在分析数据即可。
onActivityDestroyed
Matrix.with().getApplication().registerActivityLifecycleCallbacks注册后的一个回调方法之一。
@Override
public void onActivityDestroyed(Activity activity) {
//activeActivityCount是未销毁的activity的数量,每次destroy后-1
activeActivityCount--;
}
onForeground
跟启动分析关联不大,不深入探讨了。
stop
TracePlugin的stop方法调用了StartupTracer的onCloseTrace
final synchronized public void onCloseTrace() {
if (isAlive) {
this.isAlive = false;
onDead();
}
}
没什么关键操作,其实对StartupTracer来说也没有需要释放的资源。
@CallSuper
protected void onDead() {
MatrixLog.i(TAG, "[onDead] %s", this.getClass().getName());
}
destroy
更新插件状态,是生命周期的处理,没有特别操作。
AnalyseTask
最后再来看一下AnalyseTask,它到底是怎么分析问题的?看一下run方法。它会将收集到的方法信息进行统计,并过滤掉部分方法,然后report,app启动的时长等信息都会被携带,最关键的还是运行期间的一系列的方法耗时信息,通过这些信息才能真正定位到执行缓慢的逻辑。
@Override
public void run() {
LinkedList<MethodItem> stack = new LinkedList();
if (data.length > 0) {
//data是long数组,将它转到stack中,
TraceDataUtils.structuredDataToStack(data, stack, false, -1);
//按照时间排序,过滤
TraceDataUtils.trimStack(stack, Constants.TARGET_EVIL_METHOD_STACK, new TraceDataUtils.IStructuredDataFilter() {
@Override
public boolean isFilter(long during, int filterCount) {
return during < filterCount * Constants.TIME_UPDATE_CYCLE_MS;
}
@Override
public int getFilterMaxCount() {
return Constants.FILTER_STACK_MAX_COUNT;
}
@Override
public void fallback(List<MethodItem> stack, int size) {
MatrixLog.w(TAG, "[fallback] size:%s targetSize:%s stack:%s", size, Constants.TARGET_EVIL_METHOD_STACK, stack);
Iterator iterator = stack.listIterator(Math.min(size, Constants.TARGET_EVIL_METHOD_STACK));
while (iterator.hasNext()) {
iterator.next();
iterator.remove();
}
}
});
}
StringBuilder reportBuilder = new StringBuilder();
StringBuilder logcatBuilder = new StringBuilder();
long stackCost = Math.max(allCost, TraceDataUtils.stackToString(stack, reportBuilder, logcatBuilder));
String stackKey = TraceDataUtils.getTreeKey(stack, stackCost);
// for logcat
if ((allCost > coldStartupThresholdMs && !isWarmStartUp)
|| (allCost > warmStartupThresholdMs && isWarmStartUp)) {
MatrixLog.w(TAG, "stackKey:%s \n%s", stackKey, logcatBuilder.toString());
}
// report
report(applicationCost, firstScreenCost, reportBuilder, stackKey, stackCost, isWarmStartUp, scene);
}
总结
至此,StartupTracer的源码就分析完了,StartupTracer的逻辑比较简单,它借助了系统的一些回调方法,如Activity生命周期的方法,onCreate, onWindowFocusChanged等,当然也借助了hook,从而统计app启动的时长,分别记录application启动时长,第一个activity启动的时长,以及最终冷启动热启动的时长。核心点在于,StartupTracer依赖于字节码插桩,在分析启动慢问题时需要借助字节码插桩记录的方法执行耗时信息,将耗时信息完整的展现出来,方便分析人员正确的定位到问题所在。
回顾一下,这篇文章中我们遗留了几个问题未处理:
转载自:https://juejin.cn/post/7276675247597633547