关于启动方面也是大家都非常关注的地方,但是如何做到无侵入的监听的App的启动状态呢,这个问题就比较麻烦了,这里先分析一下Matrix的方案,
冷启动
1:使用反射代理 ActivityThread 中管理 四大组件生命周期事件的 mH 这个Handler 中的 mCallback
在App的启动过程中,是需要先经过Application 的初始化的过程的,在执行完这个Application初始化之后,才会通过AMS 再次调用 启动 Activity ,也就是说 Application 与 Activity 的创建是执行的不同的Message,那么是不是只要判断出来第一个被启动的 Service 或者 Activity 再或者 BroadCastReceiver 的Message 就证明Application 就已经初始化成功了,明明是四大组件为什么 Matrix 会区别对待 ContentProvider 呢,看下面图片
在 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 肯定会被赋值, 剩下的冷启动的耗时分析就是各种计算了,
到了这里启动耗时分析就完成了