Matrix是微信開源的一套完整的APM解決方案,內部包含Resource Canary(資源監測)/Trace Canary(卡頓監測)/IO Canary(IO監測)等。android
本篇爲卡頓分析系列文章之二,分析Trace Canary相關的原理,基於版本0.5.2.43。文章有點長,建議你先大體瀏覽一遍再細看,對你必定有幫助。第一篇傳送門Android卡頓檢測工具(一)BlockCanary。git
可見Matrix做爲一個APM工具,在性能檢測方面仍是很是全面的,系列文章將會一一對它們進行分析。github
爲理清源代碼結構咱們先從初始化流程講起,項目地址Matrix。json
Matrix.Builder內部類配置Plugins。數組
//建立builder
Matrix.Builder builder = new Matrix.Builder(this);
//可選 感知插件狀態變化,onReportIssue獲取/處理issue
builder.patchListener(...);
//可選 配置插件
builder.plugin(tracePlugin);
builder.plugin(ioCanaryPlugin);
//完成初始化
Matrix.init(builder.build());
複製代碼
目前配置的pluginbash
本篇分析的是TracePlugin,它與卡頓/UI渲染效率相關。微信
Matrix.Builder調用build方法觸發Matrix構造函數。app
private Matrix(Application app, PluginListener listener, HashSet<Plugin> plugins) {
this.application = app;
this.pluginListener = listener;
this.plugins = plugins;
//(1)
AppActiveMatrixDelegate.INSTANCE.init(application);
for (Plugin plugin : plugins) {
//(2)
plugin.init(application, pluginListener);
pluginListener.onInit(plugin);
}
}
複製代碼
AppActiveMatrixDelegate是一個枚舉類(使人費解,枚舉性能並很差,此類做用跟普通的單例類同樣),在其init方法中爲application註冊了ActivityLifecycle和ComponentCallbacks2監聽,可見它是爲了拿到應用內部全部Activity生命週期狀態和內存緊缺狀態(onTrimMemory/onLowMemory)以供後續使用。框架
內部遍歷全部插件,並調用其init方法進行初始化,以後通知pluginListener生命週期方法onInit。異步
PluginListener包含的生命週期以下:
# -> PluginListener
public interface PluginListener {
//初始化
void onInit(Plugin plugin);
//插件開始運行
void onStart(Plugin plugin);
//插件中止運行
void onStop(Plugin plugin);
//插件銷燬
void onDestroy(Plugin plugin);
//插件捕捉到Issue,包括卡頓、ANR等等
void onReportIssue(Issue issue);
}
複製代碼
通常來講上層須要自定義一個的PluginListener,由於onReportIssue方法是具體處理Issue的關鍵方法,官方sample的作法是收到issue時彈出一個IssuesListActivity展現issue具體信息,而Matrix框架定義的DefaultPluginListener什麼都沒作。做爲接入方咱們可能會作更豐富的處理,好比序列化到本地、上傳雲端等等,全部的這一切都要從自定義PluginListener並實現onReportIssue方法開始。
patchListener方法簡單的爲成員變量賦值。
# -> Matrix.Builder
public Builder patchListener(PluginListener pluginListener) {
this.pluginListener = pluginListener;
return this;
}
複製代碼
最終來看Matrix的init方法,其實就是爲其靜態成員變量sInstance賦值。
# -> Matrix
public static Matrix init(Matrix matrix) {
if (matrix == null) {
throw new RuntimeException("Matrix init, Matrix should not be null.");
}
synchronized (Matrix.class) {
if (sInstance == null) {
sInstance = matrix;
} else {
MatrixLog.e(TAG, "Matrix instance is already set. this invoking will be ignored");
}
}
return sInstance;
}
複製代碼
能夠看到Matrix提供了日誌管理器MatrixLogImpl,以及操做其內部全部plugin的各類方法。
接下來進入正題,咱們來看看卡頓(UI渲染性能)分析模塊TracePlugin是如何工做的。
它是tracer管理器,其內部定義了四個跟蹤器。
來看一下類圖:
這些跟蹤器都繼承於Tracer,它是一個抽象類,但不含抽象方法,已對繼承來的接口都作了默認實現。
爲了瞭解這些Tracer能實現哪些功能,咱們先來看看Tracer繼承父類和實現的接口。
它是一個抽象類,內部定義了三個重要方法dispatchBegin/doFrame/dispatchEnd,但只是空實現,這三個方法都跟監聽主線程Handler的消息處理有關。當主線程處理一條消息前會回調dispatchBegin,消息處理完會先調用doFrame,而後再調用dispatchEnd。之因此這麼作是由於對於卡頓的檢測一般有兩種方式。
第一種方式是經過hook Looper內部的logger對象實現的。系統Looper分發處理消息先後會經過logger對象打印日誌,hook這個logger至關於拿到了一條消息的先後時間點,根據兩者的時間差能夠作不少卡頓的分析,BlockCanary就是用此方法實現卡頓檢測,具體參看Android卡頓檢測工具(一)BlockCanary
第二種方式是Choreographer開放API,上層可設置FrameCallback監聽,從而得到每一幀繪製完畢的onFrame回調。經常使用的幀率監測工具(FPS)就是經過分析兩幀以前的時間差完成FPS的計算,好比TinyDancer、Takt。
實際上Matrix早期版本用的是第二種方式,最新版使用了第一種方式,由於能夠拿到更完整更清晰的堆棧信息。
至此,咱們能夠推斷Tracer具備感知幀率變化、統計卡頓的能力,因此跟幀率、函數耗時統計相關的Tracer(FrameTracer/EvilMethodTracer/AnrTracer)必定會繼續複寫doFrame方法,以實現具體功能。
它是一個接口,繼承了IAppForeground接口,整體算下來一共四個抽象方法:onStartTrace、onCloseTrace、isAlive、onForeground。前三個方法是在描述Tracer自身的生命週期,由TracePlugin統一管理。當Activity先後臺狀態發生變化時回調Tracer的onForeground方法,所以Tracer具備感知Activity先後臺狀態變化的能力,它可用來作啓動分析。
在Tracer中大部分接口方法都是空實現,具體實現交由有需求的tracer完成。下面咱們來看TraceCanary包含的具體tracer實現。
咱們先來看FrameTracer,它複寫doFrame監聽每一幀的回調,並將時間戳、掉幀狀況、頁面名稱等信息發送給IDoFrameListener。
# -> FrameTracer -> doFrame
@Override
public void doFrame(final long lastFrameNanos, final long frameNanos) {
if (!isDrawing) {
return;
}
isDrawing = false;
final int droppedCount = (int) ((frameNanos - lastFrameNanos) / REFRESH_RATE_MS);
for (final IDoFrameListener listener : mDoFrameListenerList) {
//同步發送
listener.doFrameSync(lastFrameNanos, frameNanos, getScene(), droppedCount);
if (null != listener.getHandler()) {
//異步發送
listener.getHandler().post(new AsyncDoFrameTask(listener,
lastFrameNanos, frameNanos, getScene(), droppedCount));
}
}
}
複製代碼
能夠看到代碼中分別以同步和異步的方式將回調發送出去,上層可經過FrameTracer的register方法註冊監聽。
# FrameTracer
public void register(IDoFrameListener listener) {
if (FrameBeat.getInstance().isPause()) {
FrameBeat.getInstance().resume();
}
if (!mDoFrameListenerList.contains(listener)) {
mDoFrameListenerList.add(listener);
}
}
public void unregister(IDoFrameListener listener) {
mDoFrameListenerList.remove(listener);
if (!FrameBeat.getInstance().isPause() && mDoFrameListenerList.isEmpty()) {
FrameBeat.getInstance().removeListener(this);
}
}
複製代碼
它具備檢查耗時函數的功能,而ANR就是最嚴重的耗時狀況,那咱們先來看看ANR檢查是如何作到的。
先來看構造器
public EvilMethodTracer(TracePlugin plugin, TraceConfig config) {
super(plugin);
this.mTraceConfig = config;
//建立ANR延時檢測工具 定時5s
mLazyScheduler = new LazyScheduler(MatrixHandlerThread.getDefaultHandlerThread(), Constants.DEFAULT_ANR);
mActivityCreatedInfoMap = new HashMap<>();
}
複製代碼
LazyScheduler是一個延時任務工具類,構造時需設定HandlerThread和delay。
內部ILazyTask接口定義了延時任務執行時的回調方法onTimeExpire。setUp方法開始埋炸彈(ANR和耗時方法),cancel方法解除炸彈。也就是說調用setUp方法後5秒內若是沒有執行cancel,就會觸發onTimeExpire方法。
上面的內容理解以後,咱們來看doFrame方法。
# -> EvilMethodTracer
@Override
public void doFrame(long lastFrameNanos, long frameNanos) {
if (isIgnoreFrame) {
mActivityCreatedInfoMap.clear();
setIgnoreFrame(false);
getMethodBeat().resetIndex();
return;
}
int index = getMethodBeat().getCurIndex();
//兩幀時間差大於卡頓閾值(默認一秒)則發出buffer信息
//若知足一系列校驗工做則觸發卡頓檢測
if (hasEntered && frameNanos - lastFrameNanos > mTraceConfig.getEvilThresholdNano()) {
MatrixLog.e(TAG, "[doFrame] dropped frame too much! lastIndex:%s index:%s", 0, index);
handleBuffer(Type.NORMAL, 0, index - 1, getMethodBeat().getBuffer(), (frameNanos - lastFrameNanos) / Constants.TIME_MILLIS_TO_NANO);
}
getMethodBeat().resetIndex();
mLazyScheduler.cancel();
//埋ANR炸彈
mLazyScheduler.setUp(this, false);
}
複製代碼
若是5秒內還沒執行下一次doFrame,就會回調到EvilMethodTracer的onTimeExpire方法。
# -> EvilMethodTracer
@Override
public void onTimeExpire() {
// maybe ANR
if (isBackground()) {
MatrixLog.w(TAG, "[onTimeExpire] pass this time, on Background!");
return;
}
long happenedAnrTime = getMethodBeat().getCurrentDiffTime();
MatrixLog.w(TAG, "[onTimeExpire] maybe ANR!");
setIgnoreFrame(true);
getMethodBeat().lockBuffer(false);
//處於前臺就會發送ANR消息
handleBuffer(Type.ANR, 0, getMethodBeat().getCurIndex() - 1, getMethodBeat().getBuffer(), null, Constants.DEFAULT_ANR, happenedAnrTime, -1);
}
複製代碼
對於普通耗時函數又是如何檢測的呢?EvilMethodTracer的工做流程是這樣的:
MethodTracer的內部類TraceMethodAdapter負責爲每一個方法執行前插入MethodBeat的i方法,方法執行後插入o方法。插樁使用的是ASM實現的,ASM是一種經常使用的操做字節碼的動態化技術,能夠用作無侵入的埋點統計。EvilMethodTracer也是用它作耗時函數的分析。
# -> MethodTracer.TraceMethodAdapter
@Override
protected void onMethodEnter() {
TraceMethod traceMethod = mCollectedMethodMap.get(methodName);
if (traceMethod != null) {
traceMethodCount.incrementAndGet();
mv.visitLdcInsn(traceMethod.id);
//入口插樁
mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "i", "(I)V", false);
}
}
@Override
protected void onMethodExit(int opcode) {
TraceMethod traceMethod = mCollectedMethodMap.get(methodName);
if (traceMethod != null) {
if (hasWindowFocusMethod && mTraceConfig.isActivityOrSubClass(className, mCollectedClassExtendMap)
&& mCollectedMethodMap.containsKey(traceMethod.getMethodName())) {
TraceMethod windowFocusChangeMethod = TraceMethod.create(-1, Opcodes.ACC_PUBLIC, className,
TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD, TraceBuildConstants.MATRIX_TRACE_ON_WINDOW_FOCUS_METHOD_ARGS);
if (windowFocusChangeMethod.equals(traceMethod)) {
traceWindowFocusChangeMethod(mv);
}
}
traceMethodCount.incrementAndGet();
mv.visitLdcInsn(traceMethod.id);
//出口插樁
mv.visitMethodInsn(INVOKESTATIC, TraceBuildConstants.MATRIX_TRACE_CLASS, "o", "(I)V", false);
}
}
複製代碼
Matrix經過代理編譯期間的任務 transformClassesWithDexTask,將全局 class 文件做爲輸入,利用 ASM 工具,高效地對全部 class 文件進行掃描及插樁。爲了儘量的下降性能損耗掃描過程會過濾掉一些默認或匿名的構造函數以及get/set等簡單而不耗時的函數。
爲了方便及高效記錄函數執行過程,Matrix插件爲每一個插樁的函數分配一個獨立 ID,在插樁過程當中,記錄插樁的函數簽名及分配的 ID,在插樁完成後輸出一份 methodmap文件,做爲數據上報後的解析支持,該文件在apk構建時生成,目錄位於build/matrix_output下,名爲Debug_methodmap(debug構建),而那些被過濾掉的方法被記錄在Debug_ignoremethodmap文件中。文件生成規則在MethodCollector類中,感興趣的小夥伴能夠繼續研究。
那接下來咱們來看一下生成文件的內容。
文件每一行表明一個插樁方法。 以第一行爲例:
-1,1,sample.tencent.matrix.io.TestIOActivity onWindowFocusChanged (Z)V
複製代碼
接下來咱們來看一下實踐是什麼效果,咱們模擬了一個耗時函數,當點擊按鈕時調用。
//點擊按鈕觸發 爲放大耗時,循環執行200次
public void testJank(View view) {
for (int i = 0; i < 200; i++) {
wrapper();
}
}
//包裝方法用於測試調用深度
void wrapper() {
tryHeavyMethod();
}
//dump內存是耗時方法
private void tryHeavyMethod() {
Debug.getMemoryInfo(new Debug.MemoryInfo());
}
複製代碼
運行後獲得如下Issue:
咱們重點關心的是
例子中stack(0,28,1,1988\n 1,31,1,136)如何解讀呢?四個數爲一組每組用換行符分隔,其中一組四個數分別表示爲:
咱們經過反查methodmap函數可驗證結果。
實測發現stack存在bug,咱們的代碼中最終的耗時方法是tryHeavyMethod,只不過中間包了一層wrapper方法,stack就不能識別到了。這一點Matrix官方可能會後續修復吧。
stackKey就是耗時函數的入口。本例中testJank調用wrapper,wrapper調用tryHeavyMethod,統計stackKey時以深度爲0的函數爲準,28就對應testJank方法。
同其餘相似的fps檢測工具原理同樣,監聽Choreographer.FrameCallback回調,回調方法doFrame在每次Vsync信號即未來臨時被調用,上層監聽此回調接口並計算兩次回調以前的時間差,Android系統默認的刷新頻率是16.6ms一次,時間差除以刷新頻率即爲掉幀狀況。
FPSTracer不一樣的點在於其內部能統計一段時間的平均幀率,並定義了幀率好壞的梯度。
# -> FPSTracer.DropStatus
private enum DropStatus {
DROPPED_FROZEN(4), DROPPED_HIGH(3), DROPPED_MIDDLE(2), DROPPED_NORMAL(1), DROPPED_BEST(0);
int index;
DropStatus(int index) {
this.index = index;
}
}
複製代碼
核心方法代碼片斷
# FPSTracer -> doReport
private void doReport() {
LinkedList<Integer> reportList;
synchronized (this.getClass()) {
if (mFrameDataList.isEmpty()) {
return;
}
reportList = mFrameDataList;
mFrameDataList = new LinkedList<>();
}
//數據轉儲到mPendingReportSet集合中
for (int trueId : reportList) {
int scene = trueId >> 22;
int durTime = trueId & 0x3FFFFF;
LinkedList<Integer> list = mPendingReportSet.get(scene);
if (null == list) {
list = new LinkedList<>();
mPendingReportSet.put(scene, list);
}
list.add(durTime);
}
reportList.clear();
//統計分析
for (int i = 0; i < mPendingReportSet.size(); i++) {
int key = mPendingReportSet.keyAt(i);
LinkedList<Integer> list = mPendingReportSet.get(key);
if (null == list) {
continue;
}
int sumTime = 0;
int markIndex = 0;
int count = 0;
int[] dropLevel = new int[DropStatus.values().length]; // record the level of frames dropped each time
int[] dropSum = new int[DropStatus.values().length]; // record the sum of frames dropped each time
int refreshRate = (int) Constants.DEFAULT_DEVICE_REFRESH_RATE * OFFSET_TO_MS;
for (Integer period : list) {
sumTime += period;
count++;
int tmp = period / refreshRate - 1;
//將掉幀狀況寫入數組
if (tmp >= Constants.DEFAULT_DROPPED_FROZEN) {
dropLevel[DropStatus.DROPPED_FROZEN.index]++;
dropSum[DropStatus.DROPPED_FROZEN.index] += tmp;
} else if (tmp >= Constants.DEFAULT_DROPPED_HIGH) {
dropLevel[DropStatus.DROPPED_HIGH.index]++;
dropSum[DropStatus.DROPPED_HIGH.index] += tmp;
} else if (tmp >= Constants.DEFAULT_DROPPED_MIDDLE) {
dropLevel[DropStatus.DROPPED_MIDDLE.index]++;
dropSum[DropStatus.DROPPED_MIDDLE.index] += tmp;
} else if (tmp >= Constants.DEFAULT_DROPPED_NORMAL) {
dropLevel[DropStatus.DROPPED_NORMAL.index]++;
dropSum[DropStatus.DROPPED_NORMAL.index] += tmp;
} else {
dropLevel[DropStatus.DROPPED_BEST.index]++;
dropSum[DropStatus.DROPPED_BEST.index] += (tmp < 0 ? 0 : tmp);
}
//達到分片時間 sendReport一次
if (sumTime >= mTraceConfig.getTimeSliceMs() * OFFSET_TO_MS) { // if it reaches report time
float fps = Math.min(60.f, 1000.f * OFFSET_TO_MS * (count - markIndex) / sumTime);
MatrixLog.i(TAG, "scene:%s fps:%s sumTime:%s [%s:%s]", mSceneIdToSceneMap.get(key), fps, sumTime, count, markIndex);
try {
JSONObject dropLevelObject = new JSONObject();
...
JSONObject dropSumObject = new JSONObject();
...
JSONObject resultObject = new JSONObject();
resultObject = DeviceUtil.getDeviceInfo(resultObject, getPlugin().getApplication());
resultObject.put(SharePluginInfo.ISSUE_SCENE, mSceneIdToSceneMap.get(key));
resultObject.put(SharePluginInfo.ISSUE_DROP_LEVEL, dropLevelObject);
resultObject.put(SharePluginInfo.ISSUE_DROP_SUM, dropSumObject);
resultObject.put(SharePluginInfo.ISSUE_FPS, fps);
sendReport(resultObject);
} catch (JSONException e) {
MatrixLog.e(TAG, "json error", e);
}
dropLevel = new int[DropStatus.values().length];
dropSum = new int[DropStatus.values().length];
markIndex = count;
sumTime = 0;
}
}
// delete has reported data
if (markIndex > 0) {
for (int index = 0; index < markIndex; index++) {
list.removeFirst();
}
}
...
}
}
複製代碼
整個流程以下
這裏有一個細節問題須要處理,好比頁面沒有靜止沒有UI繪製任務,這段時間的幀率統計也沒意義。事實上,FPSTracer對上述用於存儲每幀耗時信息的mFrameDataList的插入作個一個過濾。
# FPSTracer -> doFrame
@Override
public void doFrame(long lastFrameNanos, long frameNanos) {
//知足判斷條件才handleDoFrame
if (!isInvalid && isDrawing && isEnterAnimationComplete() && mTraceConfig.isTargetScene(getScene())) {
handleDoFrame(lastFrameNanos, frameNanos, getScene());
}
isDrawing = false;
}
private void handleDoFrame(long lastFrameNanos, long frameNanos, String scene) {
int sceneId;
... //獲取scene信息
int trueId = 0x0;
//位運算,將sceneId和耗時信息寫入一個int
trueId |= sceneId;
trueId = trueId << 22;
long offset = frameNanos - lastFrameNanos;
trueId |= ((offset / FACTOR) & 0x3FFFFF);
if (offset >= 5 * 1000000000L) {
MatrixLog.w(TAG, "[handleDoFrame] WARNING drop frame! offset:%s scene%s", offset, scene);
}
//添加到mFrameDataList
synchronized (this.getClass()) {
mFrameDataList.add(trueId);
}
}
複製代碼
看條件!isInvalid && isDrawing && isEnterAnimationComplete() && mTraceConfig.isTargetScene(getScene())
getDecorView().getViewTreeObserver().addOnDrawListener()
)監聽view的繪製,當回調onDraw時將此變量設爲true,onFrame結束設置爲false。所以處於靜止狀態的時間段不會統計幀信息。這樣真個fps檢測流程也就結束了,咱們來看一下官方sample彙總的report展示。
首先要明確的是統計的是應用的啓動,這包括application建立過程而不單純是activity啓動。統計觸發一次就會銷燬,所以若是想統計activity之間跳轉的狀況需手動獲取StartUpTrace並調用onCreate方法。
具體的統計指標以下:
統計項目 | 含義 |
---|---|
appCreateTime | application建立時長 |
betweenCost | application建立完成到第一個Activity create完成 |
activityCreate | activity 執行完super.oncreate()至window獲取焦點 |
splashCost | splash界面建立時長 |
allCost | 到主界面window focused總時長 |
isWarnStartUp | 是否爲熱啓動(application存在) |
時間軸大體是這樣的:
爲了實現上述統計指標須要hook ActivityThread中消息處理內部類H(成員變量mH),它是一個Handler對象,activity的建立與生命週期的處理都是經過它完成的,若是你熟悉activity的啓動流程那麼對mH成員變量必定不陌生。ApplicationThread做爲binder通訊的信使,接收AMS的調度事件,好比scheduleLaunchActivity,此方法內部會經過mH對象發送 H.LAUNCH_ACTIVITY消息,mH接收到此消息便會調用handleLaunchActivity建立activity對象。
這屬於Activity啓動流程範疇,本篇再也不討論。重點關注hook動做。
# -> StartUpHacker
public class StartUpHacker {
private static final String TAG = "Matrix.Hacker";
public static boolean isEnterAnimationComplete = false;
public static long sApplicationCreateBeginTime = 0L;
public static int sApplicationCreateBeginMethodIndex = 0;
public static long sApplicationCreateEndTime = 0L;
public static int sApplicationCreateEndMethodIndex = 0;
public static int sApplicationCreateScene = -100;
//此方法被靜態代碼塊調用 在被類resolve時執行
public static void hackSysHandlerCallback() {
try {
sApplicationCreateBeginTime = System.currentTimeMillis();
sApplicationCreateBeginMethodIndex = MethodBeat.getCurIndex();
Class<?> forName = Class.forName("android.app.ActivityThread");
Field field = forName.getDeclaredField("sCurrentActivityThread");
field.setAccessible(true);
Object activityThreadValue = field.get(forName);
Field mH = forName.getDeclaredField("mH");
mH.setAccessible(true);
Object handler = mH.get(activityThreadValue);
Class<?> handlerClass = handler.getClass().getSuperclass();
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", sApplicationCreateBeginTime);
} catch (Exception e) {
MatrixLog.e(TAG, "hook system handler err! %s", e.getCause().toString());
}
}
}
複製代碼
代碼比較簡單,就是取出mH對象內部原有的Handler.Callback,將它換成成新的HackCallback。
# StartUpHacker.HackCallback
private final static class HackCallback implements Handler.Callback {
private final Handler.Callback mOriginalCallback;
HackCallback(Handler.Callback callback) {
this.mOriginalCallback = callback;
}
@Override
public boolean handleMessage(Message msg) {
...
//優先處理 設置一些值
boolean isLaunchActivity = isLaunchActivity(msg);
if (isLaunchActivity) {
StartUpHacker.isEnterAnimationComplete = false;
} else if (msg.what == ENTER_ANIMATION_COMPLETE) {
//記錄activity轉場動畫結束標誌
StartUpHacker.isEnterAnimationComplete = true;
}
if (!isCreated) {
if (isLaunchActivity || msg.what == CREATE_SERVICE || msg.what == RECEIVER) {
//以第一個Activity LAUNCH_ACTIVITY消息爲止,記錄application建立結束時間
StartUpHacker.sApplicationCreateEndTime = SystemClock.uptimeMillis();
StartUpHacker.sApplicationCreateEndMethodIndex = MethodBeat.getCurIndex();
StartUpHacker.sApplicationCreateScene = msg.what;
isCreated = true;
}
}
if (null == mOriginalCallback) {
return false;
}
//最終讓原有的callback處理消息
return mOriginalCallback.handleMessage(msg);
}
}
複製代碼
瞭解了hook原理,咱們來看一下統計時間的幾個關鍵節點是如何得到的。
寫到這,整個Trace Canary的內容就算大體講完了,其中涉及的知識點很是多,包括UI繪製流程、Activity啓動流程、應用啓動流程、打包流程、ASM插樁等等。筆者只是按源碼流程大體理出了最核心的內容,分支的技術點大多一筆略過,須要讀者自行補充,但願你們一塊兒加油,補足分支的技術棧。