本篇文章继续阅读 TracePlugin
源码,准备好了就开始。
EvilMethodTracer
EvilMethodTracer
的实现非常有意思,很多的地方也值得学习,它是用来记录方法栈中每个方法的耗时。如果是你有这个需求你要怎么实现呢???
Matrix
的 gradle
插件会在编译的时候为每个方法添加一个方法 ID
,方法的 ID
和方法名与签名映射会记录到一个 mapping
文件,通过这个文件就能够将方法的 ID
翻译成方法名。这和混淆的 mapping
文件类似的功能。这个功能还是挺麻烦的,如果全量编译还是要简单许多,但是如果是增量编译就要麻烦很多。如果是全量编译直接用一个自增的 ID
依次记录每个方法就好了,然后再写入到 mapping
中,它会跳过所有的 setter/getter
函数,构造函数和一些简单方法(也就是没有调用过其他方法的方法),还有跳过用户设置的不需要追踪的方法;如果是增量编译就还需要去解析上一次编译的 mapping
文件,这样可以获取到上次记录的 ID
的位置和已经记录过的方法 ID
,如果增量编译的类在原来的记录上就有就直接用,如果没有就生成新的 ID
。
在需要追踪的方法调用前调用 AppMethodBeat#i()
方法传入的参数就是编译过程中生成的方法 ID
,方法结束后调用 AppMethodBeat#o()
方法,同样也会传入方法 ID
。这里还会在 Activity#onWindowFocusChanged()
方法回调的时候调用 AppMethodBeat#at()
方法,这个方法会在计算启动耗时的时候会使用。
如果想要去看我上面说到的逻辑以 MatrixTrace#doTransform()
作为入口函数看就好了,我在前面的文章中有介绍过 AGP
的插件开发,感兴趣的可以往前翻翻,还有非常重要一点 Matrix
在我写这篇文章的时候是不支持 AGP 8
的,所以如果想在自己的项目上使用的就要小心了。
上面讲了这么多 Matrix
的部分插件功能,该来看源码了。
@Override
public void onAlive() {
super.onAlive();
if (isEvilMethodTraceEnable) {
LooperMonitor.register(this);
}
}
监控主线程 Messsage
的执行。我们再来看看对应的执行前和执行后的处理:
/**
* 主线程 Message 开始执行
* @param log
*/
@Override
public void onDispatchBegin(String log) {
// 生成一个新的链表节点
indexRecord = AppMethodBeat.getInstance().maskIndex("EvilMethodTracer#dispatchBegin");
}
/**
* 主线程 Message 结束执行
* @param log
* @param beginNs 开始的时间
* @param endNs 结束的时间
*/
@Override
public void onDispatchEnd(String log, long beginNs, long endNs) {
// 整个 message 耗时
long dispatchCost = (endNs - beginNs) / Constants.TIME_MILLIS_TO_NANO;
try {
// 如果整个 Message 执行时间超过了默认 700ms 就收集整个方法栈中方法执行的时间。
if (dispatchCost >= evilThresholdMs) {
// 收集证据
long[] data = AppMethodBeat.getInstance().copyData(indexRecord);
// 获取可见的 Activity
String scene = AppActiveMatrixDelegate.INSTANCE.getVisibleScene();
// 开启分析任务。
MatrixHandlerThread.getDefaultHandler().post(new AnalyseTask(isForeground(), scene, data, dispatchCost, endNs));
}
} finally {
indexRecord.release();
}
}
简单来说就是在消息开始执行的时候通过 AppMethodBeat#maskIndex()
添加获取数据的标记,如果 Message
执行时间超过了默认的 700ms 就开始收集和解析当前消息的方法的耗时。这里先简单说一下这个原理不然稍微有点懵,所有的方法耗时的数据都记录在一个 buffer
中,AppMethodBeat#maskIndex()
方法就相当于标记一下当前 buffer
的位置,当想要收集这个时间段的方法栈耗时的信息的时候就去通过 AppMethodBeat#copyData()
收集到上次标记的位置的数据,然后再去解析这个数据。
再来看看插桩调用的方法 AppMethodBeat#i()
方法:
/**
* hook method when it's called in.
* 字节码插桩会在追踪的方法执行前调用的方法,methodId 用来标记方法
* @param methodId
*/
public static void i(int methodId) {
if (status <= STATUS_STOPPED) {
return;
}
// ID 越界,跳过
if (methodId >= METHOD_ID_MAX) {
return;
}
// 将状态修改为 Ready
if (status == STATUS_DEFAULT) {
synchronized (statusLock) {
if (status == STATUS_DEFAULT) {
// 做一些初始化的操作
realExecute();
status = STATUS_READY;
}
}
}
long threadId = Thread.currentThread().getId();
// 方法开始回调
if (sMethodEnterListener != null) {
sMethodEnterListener.enter(methodId, threadId);
}
if (threadId == sMainThreadId) {
// 如果是主线程。
if (assertIn) {
android.util.Log.e(TAG, "ERROR!!! AppMethodBeat.i Recursive calls!!!");
return;
}
assertIn = true;
// 最多记录 100 * 10000,超过上限后重置 index
if (sIndex < Constants.BUFFER_SIZE) {
mergeData(methodId, sIndex, true);
} else {
// 重置 index
sIndex = 0;
mergeData(methodId, sIndex, true);
}
++sIndex;
assertIn = false;
}
}
如果是第一次执行 i()
方法,会调用 realExecute()
方法做一些初始化的操作。如果有 listener
还会做回调;如果是主线程的调用就通过 mergeData()
方法将这个数据记录下来。
我们先来看看 realExecute()
方法都做了哪些初始化的操作:
/**
* 第一个调用 i() 方法时会触发
*/
private static void realExecute() {
MatrixLog.i(TAG, "[realExecute] timestamp:%s", System.currentTimeMillis());
// 获取到 AppMethodBeat 类加载成功时到第一个追踪的方法调用时的时间间隔
sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
sHandler.removeCallbacksAndMessages(null);
// 开启时间计算任务
sHandler.postDelayed(sUpdateDiffTimeRunnable, Constants.TIME_UPDATE_CYCLE_MS);
// 状态检查
sHandler.postDelayed(checkStartExpiredRunnable = new Runnable() {
@Override
public void run() {
synchronized (statusLock) {
MatrixLog.i(TAG, "[startExpired] timestamp:%s status:%s", System.currentTimeMillis(), status);
if (status == STATUS_DEFAULT || status == STATUS_READY) {
status = STATUS_EXPIRED_START;
}
}
}
}, Constants.DEFAULT_RELEASE_BUFFER_DELAY);
// Hook ActivityThread#mH 的 mCallback 对象和添加第一个 AppMethodBeat 的节点。
ActivityThreadHacker.hackSysHandlerCallback();
// 监听主线程 Message 的开始和结束,用于更新时间,Message 开始时触发一次时间更新
LooperMonitor.register(looperMonitorListener);
}
上面的代码主要做了两件事:启动一个自定义的时钟;对 ActivityThread#mH
执行 Hook
,做一些事情。
我们来看看它的自定义时钟任务:
/**
* update time runnable
*/
/**
* 时间计算任务
*/
private static Runnable sUpdateDiffTimeRunnable = new Runnable() {
@Override
public void run() {
try {
// 该循环运行一次就计算一次当前到进程启动时的时间间隔,保存在 sCurrentDiffTime 中
while (true) {
while (!isPauseUpdateTime && status > STATUS_STOPPED) {
sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
SystemClock.sleep(Constants.TIME_UPDATE_CYCLE_MS);
}
synchronized (updateTimeLock) {
updateTimeLock.wait();
}
}
} catch (Exception e) {
MatrixLog.e(TAG, "" + e.toString());
}
}
};
它的时钟是当前时间到进程启动时的时间间隔,那它为什么不直接用 SystemClock.uptimeMillis()
系统的时间呢??很简单系统的时间占 64 bits 浪费内存,而 Matrix
的时间只需要 43 bits。上面的代码仔细看一下,可能有点绕。当 isPauseUpdateTime
为 false
表示当前需要计算时间,就会循环计算时间保存到 sCurrentDiffTime
参数中,每次循环的计算间隔是 5ms。当不需要计算时就会走到 updateTimeLock.wait()
这里等待下次需要计算时间的通知。
继续看看监听主线程的 Message
的回调:
/**
* 主线程 Message 开始
*/
private static void dispatchBegin() {
// 通知时间更新线程更新时间
sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
isPauseUpdateTime = false;
synchronized (updateTimeLock) {
updateTimeLock.notify();
}
}
/**
* 主线程 Message 结束
*/
private static void dispatchEnd() {
// 标记停止更新时间
isPauseUpdateTime = true;
}
在主线程 Message
消息开始执行时开始计算时间,结束时停止计算时间。
我们再来看看 ActivityThreadHacker.hackSysHandlerCallback()
如何对 ActivityThread
做的 Hook
。
public static void hackSysHandlerCallback() {
try {
// App 启动时的时间
sApplicationCreateBeginTime = SystemClock.uptimeMillis();
// 添加一个标记 Application 启动的节点,绝大多数情况下,它就是 Head
sApplicationCreateBeginMethodIndex = AppMethodBeat.getInstance().maskIndex("ApplicationCreateBeginMethodIndex");
// 获取 ActivityThread 的实例
Class<?> forName = Class.forName("android.app.ActivityThread");
Field field = forName.getDeclaredField("sCurrentActivityThread");
field.setAccessible(true);
Object activityThreadValue = field.get(forName);
// 获取 ActivityThread#mH 的 Handler 对象
Field mH = forName.getDeclaredField("mH");
mH.setAccessible(true);
Object handler = mH.get(activityThreadValue);
// 获取 Handler 的 Class 对象
Class<?> handlerClass = handler.getClass().getSuperclass();
if (null != handlerClass) {
// Hook ActivityThread#mH 的 mCallback 对象
Field callbackField = handlerClass.getDeclaredField("mCallback");
callbackField.setAccessible(true);
Handler.Callback originalCallback = (Handler.Callback) callbackField.get(handler);
HackCallback callback = new HackCallback(originalCallback);
callbackField.set(handler, callback);
}
MatrixLog.i(TAG, "hook system handler completed. start:%s SDK_INT:%s", sApplicationCreateBeginTime, Build.VERSION.SDK_INT);
} catch (Exception e) {
MatrixLog.e(TAG, "hook system handler err! %s", e.getCause().toString());
}
}
上面的代码就是将 ActivityThread#mH
的 Handler
的 Callback
给替换了,替换成了 HackCallback
,这样就能够拿到它的所有 Message
了。
我们来看看 HackCallback
的实现:
@Override
public boolean handleMessage(Message msg) {
// 解决 Android 5 到 Android 7 中 SP 可能导致的 ANR 问题,因为,没有完成的 SP 会在以下的任务中等待完成,就有可能导致 ANR,它的解决方案是把等待 SP 完成的任务移除掉。
if (IssueFixConfig.getsInstance().isEnableFixSpApply()) {
if (Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT <= 25) {
if (msg.what == SERIVCE_ARGS || msg.what == STOP_SERVICE
|| msg.what == STOP_ACTIVITY_SHOW || msg.what == STOP_ACTIVITY_HIDE
|| msg.what == SLEEPING) {
MatrixLog.i(TAG, "Fix SP ANR is enabled");
fix();
}
}
}
// 如果 AppMethodBeat 还没有 Ready,就跳过处理
if (!AppMethodBeat.isRealTrace()) {
return null != mOriginalCallback && mOriginalCallback.handleMessage(msg);
}
// 是否是 Activity 启动的任务
boolean isLaunchActivity = isLaunchActivity(msg);
if (hasPrint > 0) {
MatrixLog.i(TAG, "[handleMessage] msg.what:%s begin:%s isLaunchActivity:%s SDK_INT=%s", msg.what, SystemClock.uptimeMillis(), isLaunchActivity, Build.VERSION.SDK_INT);
hasPrint--;
}
if (!isCreated) {
// 第一个启动的 Android 基本组件的 任务,看注释还需要处理 Provider 的
if (isLaunchActivity || msg.what == CREATE_SERVICE
|| msg.what == RECEIVER) { // todo for provider
// 当前的时间标记为 Application 启动完成的时间点
ActivityThreadHacker.sApplicationCreateEndTime = SystemClock.uptimeMillis();
// 记录完成时的 Message 的消息
ActivityThreadHacker.sApplicationCreateScene = msg.what;
isCreated = true;
// 记录 Activity 是否是第一个启动的组件
sIsCreatedByLaunchActivity = isLaunchActivity;
MatrixLog.i(TAG, "application create end, sApplicationCreateScene:%d, isLaunchActivity:%s", msg.what, isLaunchActivity);
// 通知 Application 已经启动完成了
synchronized (listeners) {
for (IApplicationCreateListener listener : listeners) {
listener.onApplicationCreateEnd();
}
}
}
}
return null != mOriginalCallback && mOriginalCallback.handleMessage(msg);
}
上面的代码做了以下事情:
-
解决
Android 5
到Android 7
中SP
可能导致的ANR
问题,因为,没有完成的SP
会在某些组件的生命周期的任务中等待完成,就有可能导致ANR
,它的解决方案是把等待SP
完成的任务移除掉。 -
监控第一个基本组件的
onCreate()
生命周期,当第一个基本组件onCreate()
时就标志Application
已经创建完毕了,看它的注释可以知道它还没有处理ContentProvider
。
再来看看 AppMethodBeat#o()
方法的实现:
/**
* hook method when it's called out.
* 字节码插桩会在追踪的方法执行完成后调用的方法,methodId 用来标记方法
* @param methodId
*/
public static void o(int methodId) {
if (status <= STATUS_STOPPED) {
return;
}
if (methodId >= METHOD_ID_MAX) {
return;
}
// 只处理主线程
if (Thread.currentThread().getId() == sMainThreadId) {
if (sIndex < Constants.BUFFER_SIZE) {
mergeData(methodId, sIndex, false);
} else {
sIndex = 0;
mergeData(methodId, sIndex, false);
}
++sIndex;
}
}
和 i()
方法一样,他也只处理主线程,同样也是将数据传递给 mergeData()
方法
,用第三个参数来判断是 i()
方法还是 o()
方法。
看看 mergeData()
方法的实现:
/**
* merge trace info as a long data
*
* @param methodId
* @param index
* @param isIn
*/
private static void mergeData(int methodId, int index, boolean isIn) {
if (methodId == AppMethodBeat.METHOD_ID_DISPATCH) {
// 更新时间
sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
}
// TrueId:从高到低:
// 第 1 bit:1 表示 in,0 表示 out。(1 bit)
// 第 2 bit 到 21 bit:表示方法 ID (20 bits,最多 1048576 个方法)
// 第 22 bit 到 64 bit:表示当前的基准时间 (43 bits,最大 2443359 小时)
try {
long trueId = 0L;
// 如果是 in,最高一位是 1;如果是 out,最高一位是 0
if (isIn) {
trueId |= 1L << 63;
}
// 记录方法 id
trueId |= (long) methodId << 43;
// 记录当前时间
trueId |= sCurrentDiffTime & 0x7FFFFFFFFFFL; // 43 bits
// Buffer 大小为 100 * 10000,占用 7.4 MB 内存
sBuffer[index] = trueId;
// 检查 Record 链表中的 index 是否合法,如果不合法标记一下
checkPileup(index);
sLastIndex = index;
} catch (Throwable t) {
MatrixLog.e(TAG, t.getMessage());
}
}
它使用一个 Long
来表示一次方法的请求,一共 64 bits,最高一位如果是 1 就表示方法的进入,反之是方法的结束;20 bits 记录方法的的 ID
;43 bits 记录当前时间,时间就是用的我们上面说到的自定义的时钟的时间。
然后将数据存入到 sBuffer
中,它的大小是 100w,总共占用 7.4MB 的内存,所以我们想要获取某个时间段的方法栈耗时信息时只需要标记开始时的 sBuffer
的 index
,然后方法结束后再去拿最新的 index
,这两个 index
之间的信息就是我们需要的方法耗时信息。
我们再来看看开头说到的主线程 Message
开始执行时调用的 AppMethodBeat#maskIndex()
方法,如果忘了再往上翻翻,它的功能就是一个标记 index
的作用。
/**
* 在链表中新生成一个节点
* @param source
* @return
*/
public IndexRecord maskIndex(String source) {
if (sIndexRecordHead == null) {
sIndexRecordHead = new IndexRecord(sIndex - 1);
sIndexRecordHead.source = source;
return sIndexRecordHead;
} else {
IndexRecord indexRecord = new IndexRecord(sIndex - 1);
indexRecord.source = source;
// 当前链表查询的节点
IndexRecord record = sIndexRecordHead;
// 查询节点的前一个节点
IndexRecord last = null;
// 链表的排列是按照 index 从大到小排列
// 遍历链表,将新生成的节点按照 index 从大到小的顺序插入到链表中
while (record != null) {
if (indexRecord.index <= record.index) {
if (null == last) {
IndexRecord tmp = sIndexRecordHead;
sIndexRecordHead = indexRecord;
indexRecord.next = tmp;
} else {
IndexRecord tmp = last.next;
last.next = indexRecord;
indexRecord.next = tmp;
}
return indexRecord;
}
last = record;
record = record.next;
}
last.next = indexRecord;
return indexRecord;
}
}
它把这些标记点构建成一个链表,index
为 sIndex - 1
,链表的排列顺序是按照 index
由大到小的顺序。
AppMethodBeat#copyData()
根据 index
取出想要的栈方法耗时数据:
public long[] copyData(IndexRecord startRecord) {
return copyData(startRecord, new IndexRecord(sIndex - 1));
}
private long[] copyData(IndexRecord startRecord, IndexRecord endRecord) {
long current = System.currentTimeMillis();
long[] data = new long[0];
try {
// 记录必须有效的 Record
if (startRecord.isValid && endRecord.isValid) {
int length;
int start = Math.max(0, startRecord.index);
int end = Math.max(0, endRecord.index);
if (end > start) {
// 正常情况
length = end - start + 1;
data = new long[length];
// Copy 数据,其中的数据就是当前 Record 中所有的方法的耗时情况。
System.arraycopy(sBuffer, start, data, 0, length);
} else if (end < start) {
// 中途出现过 index 重置
length = 1 + end + (sBuffer.length - start);
data = new long[length];
System.arraycopy(sBuffer, start, data, 0, sBuffer.length - start);
System.arraycopy(sBuffer, 0, data, sBuffer.length - start, end + 1);
}
return data;
}
return data;
} catch (Throwable t) {
MatrixLog.e(TAG, t.toString());
return data;
} finally {
MatrixLog.i(TAG, "[copyData] [%s:%s] length:%s cost:%sms", Math.max(0, startRecord.index), endRecord.index, data.length, System.currentTimeMillis() - current);
}
}
就是简单复制 sBuffer
的数据,其中还还处理了 index
重置的情况。
再来看看 AnalyseTask
的实现:
void analyse() {
// process
// 获取进程状态
int[] processStat = Utils.getProcessPriority(Process.myPid());
LinkedList<MethodItem> stack = new LinkedList<>();
if (data.length > 0) {
// 解析 data 中的数据
TraceDataUtils.structuredDataToStack(data, stack, true, endMs);
// 裁剪方法栈,默认栈的最大数量为 60,过滤掉耗时过少的方法。
TraceDataUtils.trimStack(stack, Constants.TARGET_EVIL_METHOD_STACK, new TraceDataUtils.IStructuredDataFilter() {
@Override
public boolean isFilter(long during, int filterCount) {
return during < (long) 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<MethodItem> iterator = stack.listIterator(Math.min(size, Constants.TARGET_EVIL_METHOD_STACK));
while (iterator.hasNext()) {
iterator.next();
iterator.remove();
}
}
});
}
// 上报异常信息。
// ...
}
主要的就是解析 data
中的数据,然后裁剪掉耗时太少的方法,最后上报异常。
StartupTracer
用于计算应用的冷热启动耗时。它的一些基础功能需要借助上一节中提到的 AppMethodBeat
。
@Override
protected void onAlive() {
super.onAlive();
MatrixLog.i(TAG, "[onAlive] isStartupEnable:%s", isStartupEnable);
if (isStartupEnable) {
// 监听 Activity Window 的焦点的变化。
AppMethodBeat.getInstance().addListener(this);
// 监听 Activity 生命周期。
Matrix.with().getApplication().registerActivityLifecycleCallbacks(this);
}
}
初始化的代码中会监听 Activity
的 Window
获取到焦点和监听 Activity
的生命周期。
在前面说到在插桩的时候调用 Activity#onWindowFocusChanged()
方法的时候还会调用 AppMethodBeat#at()
方法,这样就可以获取到 Activity
的焦点的变化。
// Activity 创建
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
MatrixLog.i(TAG, "activeActivityCount:%d, coldCost:%d", activeActivityCount, coldCost);
if (activeActivityCount == 0 && coldCost > 0) {
// 暖启动的判断方式:如果当前是唯一的 Activity 启动,而且已经计算过冷启动。
lastCreateActivity = uptimeMillis();
MatrixLog.i(TAG, "lastCreateActivity:%d, activity:%s", lastCreateActivity, activity.getClass().getName());
isWarmStartUp = true;
}
activeActivityCount++;
if (isShouldRecordCreateTime) {
// 计算 Activity onCreate 回调时的时间戳
createdTimeMap.put(activity.getClass().getName() + "@" + activity.hashCode(), uptimeMillis());
}
}
// Activity 销毁
@Override
public void onActivityDestroyed(Activity activity) {
MatrixLog.i(TAG, "activeActivityCount:%d", activeActivityCount);
activeActivityCount--;
}
在创建 Activity
的回调中会判断是否是热启动,判断热启动的方式是当前已经计算过冷启动并且当前的 Activity
的数量为 0;记录当前 Activity
的创建的时间戳。
再看看 Activity
获取到焦点后怎么处理的:
/**
* Activity Window 获得焦点回调。
* @param activity
*/
@Override
public void onActivityFocused(Activity activity) {
if (ActivityThreadHacker.sApplicationCreateScene == Integer.MIN_VALUE) {
Log.w(TAG, "start up from unknown scene");
return;
}
String activityName = activity.getClass().getName();
if (isColdStartup()) {
// 如果还没有计算冷启动的耗时,那么当前的时间点就是冷启动结束,下面的代码就开始计算冷启动耗时。
// 进程的启动是否是由 Activity 触发。
boolean isCreatedByLaunchActivity = ActivityThreadHacker.isCreatedByLaunchActivity();
MatrixLog.i(TAG, "#ColdStartup# activity:%s, splashActivities:%s, empty:%b, "
+ "isCreatedByLaunchActivity:%b, hasShowSplashActivity:%b, "
+ "firstScreenCost:%d, now:%d, application_create_begin_time:%d, app_cost:%d",
activityName, splashActivities, splashActivities.isEmpty(), isCreatedByLaunchActivity,
hasShowSplashActivity, firstScreenCost, uptimeMillis(),
ActivityThreadHacker.getEggBrokenTime(), ActivityThreadHacker.getApplicationCost());
String key = activityName + "@" + activity.hashCode();
Long createdTime = createdTimeMap.get(key);
if (createdTime == null) {
createdTime = 0L;
}
// 算法是 Activity 的 Window 获取焦点的时间点 - Activity#onCreate() 生命周期的时间点
createdTimeMap.put(key, uptimeMillis() - createdTime);
if (firstScreenCost == 0) {
// 计算第一个 Activity Window 获取焦点的时间点 - 调用第一个 Hook 方法时的时间点
this.firstScreenCost = uptimeMillis() - ActivityThreadHacker.getEggBrokenTime();
}
if (hasShowSplashActivity) {
// 计算 Splash Activity 的下一个 Activity Window 获取焦点的时间点 - 调用第一个 Hook 方法时的时间点
// 这个时间作为冷启动时间
coldCost = uptimeMillis() - ActivityThreadHacker.getEggBrokenTime();
} else {
// 判断是不是 SplashActivity。
if (splashActivities.contains(activityName)) {
hasShowSplashActivity = true;
} else if (splashActivities.isEmpty()) { //process which is has activity but not main UI process
// 如果没有设置 SplashActivity
if (isCreatedByLaunchActivity) { // 是由 Activity 启动创建的进程
// 直接使用 第一个 Activity Window 获取焦点的时间点 - 调用第一个 Hook 方法时的时间点 的时间作为冷启动耗时
coldCost = firstScreenCost;
} else {
// 不是由 Activity 启动创建的进程。
firstScreenCost = 0;
// 直接使用 Application 创建的时间作为冷启动时间。
coldCost = ActivityThreadHacker.getApplicationCost();
}
} else { // 除了 SplashActivity 的其他 Activity,计算冷启动耗时的方式和 SplashActivity 为空时一样的
if (isCreatedByLaunchActivity) {
// MatrixLog.e(TAG, "pass this activity[%s] at duration of start up! splashActivities=%s", activity, splashActivities);
coldCost = firstScreenCost;
} else {
firstScreenCost = 0;
coldCost = ActivityThreadHacker.getApplicationCost();
}
}
}
if (coldCost > 0) {
Long betweenCost = createdTimeMap.get(key);
// 如果当前的 Activity 太长时间没有获取到焦点就直接丢弃这个数据,上限是 30s
if (null != betweenCost && betweenCost >= 30 * 1000) {
MatrixLog.e(TAG, "%s cost too much time[%s] between activity create and onActivityFocused, "
+ "just throw it.(createTime:%s) ", key, uptimeMillis() - createdTime, createdTime);
return;
}
// 执行分析
analyse(ActivityThreadHacker.getApplicationCost(), firstScreenCost, coldCost, false);
}
} else if (isWarmStartUp()) {
// 暖启动的判断方式:如果当前是唯一的 Activity 启动,而且已经计算过冷启动。
// 重置冷启动标记
isWarmStartUp = false;
// 暖启动的耗时计算就是:当前 Activity 获得焦点时的时间点 - Activity#onCreate() 时的时间点
long warmCost = uptimeMillis() - lastCreateActivity;
MatrixLog.i(TAG, "#WarmStartup# activity:%s, warmCost:%d, now:%d, lastCreateActivity:%d", activityName, warmCost, uptimeMillis(), lastCreateActivity);
if (warmCost > 0) {
// 执行分析
analyse(0, 0, warmCost, true);
}
}
}
上面的代码看着多,但是很简单。其实就是用来计算冷启动或者热启动的耗时。无论什么启动,它都是以第一个 Activity
获取到 Window
焦点作为启动结束。冷启动的判断很简单,如果当前进程没有计算过冷启动那么当前就是冷启动;热启动的判断是当前已经计算过冷启动同时当前的 Activity
只有一个创建了。
冷启动的耗时计算:
如果是 SplashActivity
就跳过计算,等下一个非 SplashActivity
获取到焦点时才计算。
冷启动耗时 = 非 SplashActivity
获取焦点的时间点 - 调用第一个 Hook
方法时的时间点
暖启动的耗时计算:
暖启动耗时 = 启动的 Activity
获取焦点的时间点 - 启动的 Activity
onCreate()
的时间点
Application
创建耗时计算:
Application
创建耗时 = 第一个组件 onCreate()
生命周期时间点 - 调用第一个 Hook
方法时的时间点
如果启动耗时过长,也会通过上面我们说到的方法去收集方法栈中的每个方法的耗时情况。这里就不再看那部分代码了。
最后
到这里用三篇文章介绍完了 TracePlugin
的源码,希望对你有所帮助。