版本
v0.6.5
溫馨提示
- 在讀這篇文章之前墻裂建議先讀騰訊 Apm 框架 Matrix 源碼閱讀 - TracePlugin 架構(gòu)解析
- TracePlugin 是比較復雜的,很多東西文章中可能講的不是很清楚,配合 推薦 Matrix 源碼完整注釋
可能會有更好的效果
概述
本篇文章是 騰訊開源的 APM 框架 Matrix 系列文章的第四篇,將對matrix-trace-canary這個模塊種的FrameTracer類進行解析。這個類主要是對UIThreadMonitor提供的數(shù)據(jù)進行簡單的整理,并分發(fā)給各個IDoFrameListener,FrameTracer自身攜帶了一個FPS的收集器FPSCollector。上一篇為騰訊 Apm 框架 Matrix 源碼閱讀 - TracePlugin 架構(gòu)解析
1. FrameTracer.<init>
首先我們來看一下FrameTracer的構(gòu)造方法
public FrameTracer(TraceConfig config) {
this.config = config;
//每幀間隔時間 一般就是16.7
this.frameIntervalMs = TimeUnit.MILLISECONDS.convert(UIThreadMonitor.getMonitor().getFrameIntervalNanos(), TimeUnit.NANOSECONDS) + 1;
//fps 的上報時間閾值
this.timeSliceMs = config.getTimeSliceMs();
//FPS 監(jiān)控是否開啟
this.isFPSEnable = config.isFPSEnable();
//一秒鐘 掉幀 42幀 為 FROZEN
this.frozenThreshold = config.getFrozenThreshold();
//一秒鐘 掉幀 24幀 為 HIGH
this.highThreshold = config.getHighThreshold();
//一秒鐘 掉幀 3幀 為 NORMAL
this.normalThreshold = config.getNormalThreshold();
//一秒鐘 掉幀 9幀 為 MIDDLE
this.middleThreshold = config.getMiddleThreshold();
MatrixLog.i(TAG, "[init] frameIntervalMs:%s isFPSEnable:%s", frameIntervalMs, isFPSEnable);
if (isFPSEnable) {
//添加 FPS 收集器 詳見【2.1】
addListener(new FPSCollector());
}
}
構(gòu)造方法中就是對配置的記錄,然后就是添加了一個FPS的收集器到FrameTracer中
1.1 Tracer.onStartTrace
從騰訊 Apm 框架 Matrix 源碼閱讀 - TracePlugin 架構(gòu)解析中我們知道當TracePlugin在啟動的時候(執(zhí)行自己的start()方法)會調(diào)用各個Tracer的onStartTrace()方法,那么第一步我們先看看這個方法。
final synchronized public void onStartTrace() {
if (!isAlive) {
//標識當前Tracer是活著的
this.isAlive = true;
//詳見【1.2】
onAlive();
}
}
1.2 FrameTracer.onAlive
Tracer中onAlive()是一個空實現(xiàn),FrameTracer復寫了這個方法,所以我們直接進入到FrameTracer.onAlive
public void onAlive() {
super.onAlive();
//添加 Observer 到 UIThreadMonitor 詳見【1.3】
UIThreadMonitor.getMonitor().addObserver(this);
}
1.3 UIThreadMonitor.getMonitor().addObserver
關(guān)于Tracer.onCloseTrace方法就是Tracer.onStartTrace的反操作,所以我們就不廢話了直接跳過。讀過上一篇文章騰訊 Apm 框架 Matrix 源碼閱讀 - TracePlugin 架構(gòu)解析的同學都知道(如果沒看的同學建議先去看一下,上一篇文章其實就是TracePlugin這個插件的核心),UIThreadMonitor會配合LooperMonitor獲得每個刷新幀的各個階段的耗時時間,并回調(diào)dispatchBegin,doFrame,dispatchEnd這三個方法。FrameTracer復寫了doFrame這個方法所以我們直接進入到這個方法里。
public void doFrame(String focusedActivityName, long start, long end, long frameCostMs, long inputCostNs, long animationCostNs, long traversalCostNs) {
//處于前臺
if (isForeground()) {
//詳見【1.4】
notifyListener(focusedActivityName, end - start, frameCostMs, frameCostMs >= 0);
}
}
1.4 FrameTracer.notifyListener
/**
* @param visibleScene 當前Activity名
* @param taskCostMs 整個任務(wù)耗時
* @param frameCostMs 該幀耗時
* @param isContainsFrame 是否是幀刷新
*/
private void notifyListener(final String visibleScene, final long taskCostMs, final long frameCostMs, final boolean isContainsFrame) {
long start = System.currentTimeMillis();
try {
synchronized (listeners) {
for (final IDoFrameListener listener : listeners) {
if (config.isDevEnv()) {
listener.time = SystemClock.uptimeMillis();
}
//當前事件 消耗的幀數(shù)
final int dropFrame = (int) (taskCostMs / frameIntervalMs);
//同步 回調(diào) doFrameSync 方法
listener.doFrameSync(visibleScene, taskCostMs, frameCostMs, dropFrame, isContainsFrame);
//如果 listener.getExecutor()不為空,就執(zhí)行異步的回調(diào)方法
if (null != listener.getExecutor()) {
listener.getExecutor().execute(new Runnable() {
@Override
public void run() {
//異步回調(diào) doFrameAsync 方法
listener.doFrameAsync(visibleScene, taskCostMs, frameCostMs, dropFrame, isContainsFrame);
}
});
}
....
}
}
} finally {
long cost = System.currentTimeMillis() - start;
if (config.isDebug() && cost > frameIntervalMs) {
MatrixLog.w(TAG, "[notifyListener] warm! maybe do heavy work in doFrameSync! size:%s cost:%sms", listeners.size(), cost);
}
}
}
notifyListener就是計算出當前事件(任務(wù))消耗的幀數(shù)(事件總耗時/每幀間隔)然后將這些數(shù)據(jù)通過同步或者異步的方式傳遞給各個IDoFrameListener.下面我們具體分析一下FPSCollector這個IDoFrameListener是怎么工作的。
2.1 FPSCollector.doFrameAsync
我們看到FPSCollector的getExecutor()方法返回不為空,所以直接進入doFrameAsync()方法一探究竟。
/**
*
* @param visibleScene 當前Activity名
* @param taskCost 整個任務(wù)耗時
* @param frameCostMs 該幀耗時
* @param droppedFrames 消耗幀數(shù)
* @param isContainsFrame 是否屬于幀刷新
*/
@Override
public void doFrameAsync(String visibleScene, long taskCost, long frameCostMs, int droppedFrames, boolean isContainsFrame) {
super.doFrameAsync(visibleScene, taskCost, frameCostMs, droppedFrames, isContainsFrame);
if (Utils.isEmpty(visibleScene)) {
return;
}
FrameCollectItem item = map.get(visibleScene);
if (null == item) {
item = new FrameCollectItem(visibleScene);
map.put(visibleScene, item);
}
//詳見【2.2】
item.collect(droppedFrames, isContainsFrame);
//每個visibleScene(頁面)監(jiān)控的 總時間超過 預設(shè)閥值 就 進行報告,并重置
if (item.sumFrameCost >= timeSliceMs) {
map.remove(visibleScene);
//詳見【2.3】
item.report();
}
}
2.2 FrameCollectItem.collect
/**
* @param droppedFrames 消耗幀數(shù)
* @param isContainsFrame
*/
void collect(int droppedFrames, boolean isContainsFrame) {
long frameIntervalCost = UIThreadMonitor.getMonitor().getFrameIntervalNanos();
//積累的 總時間 ms值 ,這里不夠一幀當一幀計算
sumFrameCost += (droppedFrames + 1) * frameIntervalCost / Constants.TIME_MILLIS_TO_NANO;
//下降的總幀數(shù)
sumDroppedFrames += droppedFrames;
//doFrameAsync 回調(diào)次數(shù)
sumFrame++;
if (!isContainsFrame) {
//除過 刷新幀 事件外,其他 事件數(shù)
sumTaskFrame++;
}
if (droppedFrames >= frozenThreshold) {//frozen
dropLevel[DropStatus.DROPPED_FROZEN.index]++;// 凍結(jié)數(shù)+1
dropSum[DropStatus.DROPPED_FROZEN.index] += droppedFrames;
} else if (droppedFrames >= highThreshold) {
dropLevel[DropStatus.DROPPED_HIGH.index]++;
dropSum[DropStatus.DROPPED_HIGH.index] += droppedFrames;
} else if (droppedFrames >= middleThreshold) {
dropLevel[DropStatus.DROPPED_MIDDLE.index]++;
dropSum[DropStatus.DROPPED_MIDDLE.index] += droppedFrames;
} else if (droppedFrames >= normalThreshold) {
dropLevel[DropStatus.DROPPED_NORMAL.index]++;
dropSum[DropStatus.DROPPED_NORMAL.index] += droppedFrames;
} else {
dropLevel[DropStatus.DROPPED_BEST.index]++;
dropSum[DropStatus.DROPPED_BEST.index] += (droppedFrames < 0 ? 0 : droppedFrames);
}
}
這個方法中會計算并記錄當前頁面一段時間內(nèi)累積的執(zhí)行任務(wù)時間,使用幀數(shù),并對使用幀數(shù)進行分級記錄和記錄在dropLevel和dropSum中
2.3 FrameCollectItem.report
每個visibleScene(頁面)監(jiān)控的 總時間(sumFrameCost)超過 預設(shè)閥值就進行上報
void report() {
//計算 fps 一秒內(nèi)的平均幀率
float fps = Math.min(60.f, 1000.f * sumFrame / sumFrameCost);
MatrixLog.i(TAG, "[report] FPS:%s %s", fps, toString());
try {
TracePlugin plugin = Matrix.with().getPluginByClass(TracePlugin.class);
if (null == plugin) {
return;
}
//記錄卡頓級別,及其出現(xiàn)的次數(shù)
JSONObject dropLevelObject = new JSONObject();
dropLevelObject.put(DropStatus.DROPPED_FROZEN.name(), dropLevel[DropStatus.DROPPED_FROZEN.index]);
dropLevelObject.put(DropStatus.DROPPED_HIGH.name(), dropLevel[DropStatus.DROPPED_HIGH.index]);
dropLevelObject.put(DropStatus.DROPPED_MIDDLE.name(), dropLevel[DropStatus.DROPPED_MIDDLE.index]);
dropLevelObject.put(DropStatus.DROPPED_NORMAL.name(), dropLevel[DropStatus.DROPPED_NORMAL.index]);
dropLevelObject.put(DropStatus.DROPPED_BEST.name(), dropLevel[DropStatus.DROPPED_BEST.index]);
//記錄卡頓級別,及掉幀總次數(shù)
JSONObject dropSumObject = new JSONObject();
dropSumObject.put(DropStatus.DROPPED_FROZEN.name(), dropSum[DropStatus.DROPPED_FROZEN.index]);
dropSumObject.put(DropStatus.DROPPED_HIGH.name(), dropSum[DropStatus.DROPPED_HIGH.index]);
dropSumObject.put(DropStatus.DROPPED_MIDDLE.name(), dropSum[DropStatus.DROPPED_MIDDLE.index]);
dropSumObject.put(DropStatus.DROPPED_NORMAL.name(), dropSum[DropStatus.DROPPED_NORMAL.index]);
dropSumObject.put(DropStatus.DROPPED_BEST.name(), dropSum[DropStatus.DROPPED_BEST.index]);
JSONObject resultObject = new JSONObject();
resultObject = DeviceUtil.getDeviceInfo(resultObject, plugin.getApplication());
resultObject.put(SharePluginInfo.ISSUE_SCENE, visibleScene);
resultObject.put(SharePluginInfo.ISSUE_DROP_LEVEL, dropLevelObject);
resultObject.put(SharePluginInfo.ISSUE_DROP_SUM, dropSumObject);
resultObject.put(SharePluginInfo.ISSUE_FPS, fps);
resultObject.put(SharePluginInfo.ISSUE_SUM_TASK_FRAME, sumTaskFrame);
Issue issue = new Issue();
issue.setTag(SharePluginInfo.TAG_PLUGIN_FPS);
issue.setContent(resultObject);
plugin.onDetectIssue(issue);
} catch (JSONException e) {
MatrixLog.e(TAG, "json error", e);
} finally {
sumFrame = 0;
sumDroppedFrames = 0;
sumFrameCost = 0;
sumTaskFrame = 0;
}
}
這個方法中會計算出 具體的FPS值,并組建成Json通過TracePlugin進行上報。
總結(jié)
FrameTracer就是通過UIThreadMonitor提供的感知每幀耗時的能力。進行簡單的整合再通知給各個IDoFrameListener。Matrx中提供了兩個IDoFrameListener一個就是FPSCollector用于上報FPS,另一個是FrameDecorator用于直接顯示FPS。
FPSCollector上報數(shù)據(jù)解析
scene:當前可見的activity
dropLevel:記錄各個卡段級別出現(xiàn)的次數(shù),卡頓級別可分為DROPPED_FROZEN,DROPPED_HIGH,DROPPED_MIDDLE,DROPPED_NORMAL,DROPPED_BEST;例:
"DROPPED_MIDDLE":18,表示時間閾值內(nèi)共有 18此時 DROPPED_MIDDLE的情況
dropSum:記錄各個卡段級別掉幀總數(shù),例:
"DROPPED_MIDDLE":218, 表示時間閾值內(nèi)共有 218幀是 位于 DROPPED_MIDDLE
fps:時間閾值內(nèi)的平均幀率
dropTaskFrameSum:不太清楚
系列文章
- 騰訊 Apm 框架 Matrix 源碼閱讀 - gradle插件
- 騰訊 Apm 框架 Matrix 源碼閱讀 - 架構(gòu)解析
- 騰訊 Apm 框架 Matrix 源碼閱讀 - TracePlugin 架構(gòu)解析
- 騰訊 Apm 框架 Matrix 源碼閱讀 - TracePlugin 之 FrameTracer
- 騰訊 Apm 框架 Matrix 源碼閱讀 - TracePlugin 之 StartupTracer
- 騰訊 Apm 框架 Matrix 源碼閱讀 - TracePlugin 之 AnrTracer
- 騰訊 Apm 框架 Matrix 源碼閱讀 - TracePlugin 之 上報字段含義