版本
v0.6.5
温馨提示
- 在读这篇文章之前墙裂建议先读腾讯 Apm 框架 Matrix 源码阅读 - TracePlugin 架构解析
- TracePlugin 是比较复杂的,很多东西文章中可能讲的不是很清楚,配合 推荐 Matrix 源码完整注释
可能会有更好的效果
概述
本篇文章是 腾讯开源的 APM 框架 Matrix 系列文章的第四篇,将对matrix-trace-canary
这个模块种的FrameTracer
类进行解析。这个类主要是对UIThreadMonitor
提供的数据进行简单的整理,并分发给各个IDoFrameListener
,FrameTracer
自身携带了一个FPS的收集器FPSCollector
。上一篇为腾讯 Apm 框架 Matrix 源码阅读 - TracePlugin 架构解析
1. FrameTracer.<init>
首先我们来看一下FrameTracer
的构造方法
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 监控是否开启
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());
}
}
构造方法中就是对配置的记录,然后就是添加了一个FPS的收集器到FrameTracer
中
1.1 Tracer.onStartTrace
从腾讯 Apm 框架 Matrix 源码阅读 - TracePlugin 架构解析中我们知道当TracePlugin
在启动的时候(执行自己的start()方法)会调用各个Tracer
的onStartTrace()
方法,那么第一步我们先看看这个方法。
final synchronized public void onStartTrace() {
if (!isAlive) {
//标识当前Tracer是活着的
this.isAlive = true;
//详见【1.2】
onAlive();
}
}
1.2 FrameTracer.onAlive
Tracer
中onAlive()
是一个空实现,FrameTracer
复写了这个方法,所以我们直接进入到FrameTracer.onAlive
public void onAlive() {
super.onAlive();
//添加 Observer 到 UIThreadMonitor 详见【1.3】
UIThreadMonitor.getMonitor().addObserver(this);
}
1.3 UIThreadMonitor.getMonitor().addObserver
关于Tracer.onCloseTrace
方法就是Tracer.onStartTrace
的反操作,所以我们就不废话了直接跳过。读过上一篇文章腾讯 Apm 框架 Matrix 源码阅读 - TracePlugin 架构解析的同学都知道(如果没看的同学建议先去看一下,上一篇文章其实就是TracePlugin
这个插件的核心),UIThreadMonitor
会配合LooperMonitor
获得每个刷新帧的各个阶段的耗时时间,并回调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 整个任务耗时
* @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();
}
//当前事件 消耗的帧数
final int dropFrame = (int) (taskCostMs / frameIntervalMs);
//同步 回调 doFrameSync 方法
listener.doFrameSync(visibleScene, taskCostMs, frameCostMs, dropFrame, isContainsFrame);
//如果 listener.getExecutor()不为空,就执行异步的回调方法
if (null != listener.getExecutor()) {
listener.getExecutor().execute(new Runnable() {
@Override
public void run() {
//异步回调 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就是计算出当前事件(任务)消耗的帧数(事件总耗时/每帧间隔)然后将这些数据通过同步或者异步的方式传递给各个IDoFrameListener
.下面我们具体分析一下FPSCollector
这个IDoFrameListener
是怎么工作的。
2.1 FPSCollector.doFrameAsync
我们看到FPSCollector
的getExecutor()
方法返回不为空,所以直接进入doFrameAsync()
方法一探究竟。
/**
*
* @param visibleScene 当前Activity名
* @param taskCost 整个任务耗时
* @param frameCostMs 该帧耗时
* @param droppedFrames 消耗帧数
* @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(页面)监控的 总时间超过 预设阀值 就 进行报告,并重置
if (item.sumFrameCost >= timeSliceMs) {
map.remove(visibleScene);
//详见【2.3】
item.report();
}
}
2.2 FrameCollectItem.collect
/**
* @param droppedFrames 消耗帧数
* @param isContainsFrame
*/
void collect(int droppedFrames, boolean isContainsFrame) {
long frameIntervalCost = UIThreadMonitor.getMonitor().getFrameIntervalNanos();
//积累的 总时间 ms值 ,这里不够一帧当一帧计算
sumFrameCost += (droppedFrames + 1) * frameIntervalCost / Constants.TIME_MILLIS_TO_NANO;
//下降的总帧数
sumDroppedFrames += droppedFrames;
//doFrameAsync 回调次数
sumFrame++;
if (!isContainsFrame) {
//除过 刷新帧 事件外,其他 事件数
sumTaskFrame++;
}
if (droppedFrames >= frozenThreshold) {//frozen
dropLevel[DropStatus.DROPPED_FROZEN.index]++;// 冻结数+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);
}
}
这个方法中会计算并记录当前页面一段时间内累积的执行任务时间,使用帧数,并对使用帧数进行分级记录和记录在dropLevel
和dropSum
中
2.3 FrameCollectItem.report
每个visibleScene(页面)监控的 总时间(sumFrameCost)超过 预设阀值就进行上报
void report() {
//计算 fps 一秒内的平均帧率
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;
}
//记录卡顿级别,及其出现的次数
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]);
//记录卡顿级别,及掉帧总次数
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
进行上报。
总结
FrameTracer
就是通过UIThreadMonitor
提供的感知每帧耗时的能力。进行简单的整合再通知给各个IDoFrameListener
。Matrx中提供了两个IDoFrameListener
一个就是FPSCollector
用于上报FPS,另一个是FrameDecorator
用于直接显示FPS。
FPSCollector上报数据解析
scene:当前可见的activity
dropLevel:记录各个卡段级别出现的次数,卡顿级别可分为DROPPED_FROZEN,DROPPED_HIGH,DROPPED_MIDDLE,DROPPED_NORMAL,DROPPED_BEST;例:
"DROPPED_MIDDLE":18,表示时间阈值内共有 18此时 DROPPED_MIDDLE的情况
dropSum:记录各个卡段级别掉帧总数,例:
"DROPPED_MIDDLE":218, 表示时间阈值内共有 218帧是 位于 DROPPED_MIDDLE
fps:时间阈值内的平均帧率
dropTaskFrameSum:不太清楚
系列文章
- 腾讯 Apm 框架 Matrix 源码阅读 - gradle插件
- 腾讯 Apm 框架 Matrix 源码阅读 - 架构解析
- 腾讯 Apm 框架 Matrix 源码阅读 - TracePlugin 架构解析
- 腾讯 Apm 框架 Matrix 源码阅读 - TracePlugin 之 FrameTracer
- 腾讯 Apm 框架 Matrix 源码阅读 - TracePlugin 之 StartupTracer
- 腾讯 Apm 框架 Matrix 源码阅读 - TracePlugin 之 AnrTracer
- 腾讯 Apm 框架 Matrix 源码阅读 - TracePlugin 之 上报字段含义