这是功能优化系列之matrix框架的第4篇文章,我将在功能优化专栏中对matrix apm框架做一个全面的代码剖析,功能优化是Android高级工程师必知必会的点,也是面试进程中的高频标题,对功能优化感兴趣的小伙伴能够去我主页检查所有关于matrix的分享。

前语

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));
}

从上边的办法中,咱们提取出两个要害点问题:

  1. AppMethodBeat.getInstance().copyData()做了什么,什么逻辑?
  2. 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

Android功能优化系列-腾讯matrix-卡顿监控-gradle插件- 字节码插桩代码剖析中剖析字节码插桩时提到过,编译期为符合条件的办法进口刺进AppMethodBeat.i(), 出口刺进AppMethodBeat.o()用于计算办法履行耗时,这儿便是从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);
       }
   }
}

调用途是AppMethodBeat的at办法,在前边文章剖析matrix字节码插桩时,Android功能优化系列-腾讯matrix-卡顿监控-gradle插件- 字节码插桩代码剖析中提到过,AppMethodBeat的at办法是被刺进到Activity的onWindowFocusChanged办法中的,所以这儿的onActivityFocused其实便是对应于
Activity的onWindowFocusChanged办法,matrix将这一机遇作为页面可见的点,所以onActivityFocused履行时,onActivityCreated现已履行过了。

好了,了解了它的来历,咱们再看看它做了什么,只保留要害代码。能够看到这儿是分为冷发动和热发动两种情况来剖析的。详细的计算进程代码被我删掉了,冷发动终究会计算出这几个参数:

  1. applicationCost: application耗费的时长。
  2. firstScreenCost: 从application创立到第一个页面可见耗费的时长,一般第一个页面便是splash页面。
  3. coldCost:冷发动的总时长,是从application创立到主页页面可见耗费的时长。

而热发动只要warmCost一个值。

  1. 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依赖于字节码插桩,在剖析发动慢问题时需求凭借字节码插桩记载的办法履行耗时信息,将耗时信息完好的展示出来,方便剖析人员正确的定位到问题所在。

回顾一下,这篇文章中咱们遗留了几个问题未处理:

  • ActivityThreadHacker是怎样监听到application的创立完结的?
    Android功能优化系列-腾讯matrix-ActivityThreadHacker完结原理
  • AppMethodBeat.getInstance().copyData()做了什么,什么逻辑?
    Android功能优化系列-腾讯matrix-AppMethodBeat专项剖析
  • 别的,咱们对AnalyseTask的剖析其实不行深入,这儿的逻辑也比较要害