Matrix源码分析(七)之 StartupTracer 工作原理

512 阅读4分钟

关于启动方面也是大家都非常关注的地方,但是如何做到无侵入的监听的App的启动状态呢,这个问题就比较麻烦了,这里先分析一下Matrix的方案,

冷启动

1:使用反射代理 ActivityThread 中管理 四大组件生命周期事件的 mH 这个Handler 中的 mCallback

在App的启动过程中,是需要先经过Application 的初始化的过程的,在执行完这个Application初始化之后,才会通过AMS 再次调用 启动 Activity ,也就是说 Application 与 Activity 的创建是执行的不同的Message,那么是不是只要判断出来第一个被启动的 Service 或者 Activity 再或者 BroadCastReceiver 的Message 就证明Application 就已经初始化成功了,明明是四大组件为什么 Matrix 会区别对待 ContentProvider 呢,看下面图片

image.png

在 ActivityThread 中的 handleBindApplication 内,在调用 Application 初始化onCreate之前,就已经启动了ContentProvider,正常情况下写了也是没有作用的,

到了这里我们就知道 Matrix 如何收集 Application onCreate 与 attachBaseContext 这两个方法的耗时了,非常关键性的一步

2:如何区分是冷启动还是温启动呢

首先需要先明确一下冷启动与温启东的区别,冷启动会创建进程,进程内的变量应该都是默认值,温启动是进程还在,进程内的变量还被保留,只是Activity 处于销毁或者不可见的状态,

那我用一个long 类型的变量flag 来标识是否是冷启动,默认这个值给0, 并且使用 ActivityLifecycleCallbacks 来监听 Activity 生命周期,当执行到 onCreate时 ,如果现存的 activity 个数是 0,并且 这个 flag 是0代表的什么,没错就是冷启动,如果 现存的 activity 个数是0 ,但是 flag 不为初始值就代表着温启动 ,

3:如何获取 Activity onFocusedChange 方法的调用时机

在ASM字节码插装的过程中,向所有Activity的onFocusChange方法内插入了一个方法,当执行时就会回调 AppMethodBeat 中持有的回调列表

那么在 Activity 可见也就是Activity 的 onFocusChange =true 的情况下,就能判断出来从开始到结束的整个时长,同时配合着 AppMethodBeat 在app实际启动时间大于阈值时,就可以收集所有在启动过程中所有方法的耗时,

到了这里基本的流程基本就已经完成了,接下来我们再来分析一下代码

先来看他的初始化代码

public StartupTracer(TraceConfig config) {
    this.config = config;
    this.isStartupEnable = config.isStartupEnable();
    this.splashActivities = config.getSplashActivities();
    this.coldStartupThresholdMs = (long)config.getColdStartupThresholdMs();
    this.warmStartupThresholdMs = (long)config.getWarmStartupThresholdMs();
    this.isHasActivity = config.isHasActivity();
    ActivityThreadHacker.addListener(this);
}

这里会向我们前面介绍的 ActivityThreadHacker 内添加一个 onApplicationCreateEnd 回调,

再来看一下他的alive 方法

protected void onAlive() {
    super.onAlive();
    MatrixLog.i("Matrix.StartupTracer", "[onAlive] isStartupEnable:%s", new Object[]{this.isStartupEnable});
    if (this.isStartupEnable) {
        // 监听 onFocusChange状态的监听
        AppMethodBeat.getInstance().addListener(this);
        // 监听Activity的生命周期状态
        Matrix.with().getApplication().registerActivityLifecycleCallbacks(this);
    }
}

在这里他就是添加了两个监听,先去看 Activity 生命周期状态的监听

public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    MatrixLog.i("Matrix.StartupTracer", "activeActivityCount:%d, coldCost:%d", new Object[]{this.activeActivityCount, this.coldCost});
    // 如果 Activity 个数是 0 ,并且 这个 coldCost >0 ,证明进程是还在的,他就是温启动
    if (this.activeActivityCount == 0 && this.coldCost > 0L) {
        this.lastCreateActivity = SystemClock.uptimeMillis();
        MatrixLog.i("Matrix.StartupTracer", "lastCreateActivity:%d, activity:%s", new Object[]{this.lastCreateActivity, activity.getClass().getName()});
        this.isWarmStartUp = true;
    }

    ++this.activeActivityCount;
    if (this.isShouldRecordCreateTime) {
        this.createdTimeMap.put(activity.getClass().getName() + "@" + activity.hashCode(), SystemClock.uptimeMillis());
    }

}

这里就做了一个简单的判断,将温启动 与 (冷启动 + 启动Activity)区分开了,

我们再来看看他在 onActivityFocused 获取到焦点后是如何将普通的 启动Activity 与 冷启动也区分开的

public void onActivityFocused(Activity activity) {
    // 如果 ActivityThreadHacker.sApplicationCreateScene 这里面是默认值,那么就证明 Hook失败了,
    // 那么就不能进行数据分析了
    if (ActivityThreadHacker.sApplicationCreateScene == -2147483648) {
        
    } else {
        String activityName = activity.getClass().getName();
        // 重点 判断是否是冷启动
        if (this.isColdStartup()) {
            boolean isCreatedByLaunchActivity = ActivityThreadHacker.isCreatedByLaunchActivity();
            
            String key = activityName + "@" + activity.hashCode();
            Long createdTime = (Long)this.createdTimeMap.get(key);
            if (createdTime == null) {
                createdTime = 0L;
            }

            this.createdTimeMap.put(key, SystemClock.uptimeMillis() - createdTime);
            if (this.firstScreenCost == 0L) {
                this.firstScreenCost = SystemClock.uptimeMillis() - ActivityThreadHacker.getEggBrokenTime();
            }

            if (this.hasShowSplashActivity) {
                this.coldCost = SystemClock.uptimeMillis() - ActivityThreadHacker.getEggBrokenTime();
            } else if (this.splashActivities.contains(activityName)) {
                this.hasShowSplashActivity = true;
            } else if (this.splashActivities.isEmpty()) {
                if (isCreatedByLaunchActivity) {
                    this.coldCost = this.firstScreenCost;
                } else {
                    this.firstScreenCost = 0L;
                    this.coldCost = ActivityThreadHacker.getApplicationCost();
                }
            } else if (isCreatedByLaunchActivity) {
                this.coldCost = this.firstScreenCost;
            } else {
                this.firstScreenCost = 0L;
                this.coldCost = ActivityThreadHacker.getApplicationCost();
            }

            if (this.coldCost > 0L) {
                Long betweenCost = (Long)this.createdTimeMap.get(key);
                if (null != betweenCost && betweenCost >= 30000L) {
                    MatrixLog.e("Matrix.StartupTracer", "%s cost too much time[%s] between activity create and onActivityFocused, just throw it.(createTime:%s) ", new Object[]{key, SystemClock.uptimeMillis() - createdTime, createdTime});
                    return;
                }

                this.analyse(ActivityThreadHacker.getApplicationCost(), this.firstScreenCost, this.coldCost, false);
            }
            // 重点是否是温启动
        } else if (this.isWarmStartUp()) {
            this.isWarmStartUp = false;
            long warmCost = SystemClock.uptimeMillis() - this.lastCreateActivity;
            MatrixLog.i("Matrix.StartupTracer", "#WarmStartup# activity:%s, warmCost:%d, now:%d, lastCreateActivity:%d", new Object[]{activityName, warmCost, SystemClock.uptimeMillis(), this.lastCreateActivity});
            if (warmCost > 0L) {
                this.analyse(0L, 0L, warmCost, true);
            }
        }

    }
}

他这里使用了一个 isColdStartup 方法来将冷启动 与 普通启动区分开来 ,我们来看看 他是如何判断的

private boolean isColdStartup() {
    return this.coldCost == 0L;
}

非常简单的明了,只要 coldCost!=0 ,就证明他不是冷启动,那也就证明进入冷启动后这个 coldCost 肯定会被赋值, 剩下的冷启动的耗时分析就是各种计算了,

到了这里启动耗时分析就完成了