在移动应用性能优化领域,帧率(FPS)是衡量 UI 流畅度的核心指标,而腾讯 Matrix 框架的FrameTracer
模块,通过跨版本兼容设计与细粒度监控能力,成为 Android 端帧率监控的标杆实现。本文基于 Matrix 开源源码(含FrameTracer
、UIThreadMonitor
、LooperMonitor
等核心类),从技术架构、关键实现到源码解析,揭示其底层逻辑。
一、FrameTracer 的核心定位与分层架构
- 基础监控层 :低版本(API<24)依赖
UIThreadMonitor
+Choreographer
捕获帧周期,高版本(API≥24)叠加FrameMetrics
获取渲染阶段耗时; - 数据收集层 :通过
SceneFrameCollector
按界面(Activity)隔离帧率数据,避免跨场景干扰; - 卡顿溯源层 :
LooperMonitor
监听主线程消息耗时,定位阻塞渲染的具体任务; - 上报层 :
AllSceneFrameListener
按周期聚合数据并上报,支撑性能问题分析。
这种分层设计既覆盖了 99% 以上 Android 设备,又实现了 "从帧率异常到根因定位" 的闭环。
二、关键实现与源码解析
1. 低版本兼容:UIThreadMonitor+Choreographer 的帧跟踪(API<24)
Android 7.0(API 24)前无FrameMetrics
,FrameTracer
通过UIThreadMonitor
反射 HookChoreographer
的核心回调队列,实现帧周期监控。
1.1 UIThreadMonitor 初始化:反射获取 Choreographer 资源
UIThreadMonitor
是低版本监控的核心,通过反射突破Choreographer
的隐藏 API 限制,获取帧回调队列:
csharp
// 源码位置:com.tencent.matrix.trace.core.UIThreadMonitor.java
public void init(TraceConfig config) {
this.config = config;
// 必须在主线程初始化(UI渲染逻辑绑定主线程)
if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
throw new AssertionError("must be init in main thread!");
}
// 1. 注册Looper监控(后续卡顿溯源用)
LooperMonitor.register(new LooperMonitor.LooperDispatchListener(
config.historyMsgRecorder, config.denseMsgTracer) {
@Override
public boolean isValid() { return isAlive; }
@Override
public void dispatchStart() { UIThreadMonitor.this.dispatchBegin(); }
@Override
public void dispatchEnd() { UIThreadMonitor.this.dispatchEnd(); }
});
this.isInit = true;
// 2. 获取Choreographer实例(系统帧协调器)
choreographer = Choreographer.getInstance();
// 3. 反射获取Choreographer隐藏字段:帧间隔、锁、回调队列
frameIntervalNanos = ReflectUtils.reflectObject(
choreographer, "mFrameIntervalNanos", Constants.DEFAULT_FRAME_DURATION); // 默认16.6ms(60Hz)
callbackQueueLock = ReflectUtils.reflectObject(choreographer, "mLock", new Object());
callbackQueues = ReflectUtils.reflectObject(choreographer, "mCallbackQueues", null);
// 4. 反射获取三个核心回调队列的add方法(输入、动画、绘制)
if (null != callbackQueues) {
addInputQueue = ReflectUtils.reflectMethod(
callbackQueues[CALLBACK_INPUT], "addCallbackLocked", long.class, Object.class, Object.class);
addAnimationQueue = ReflectUtils.reflectMethod(
callbackQueues[CALLBACK_ANIMATION], "addCallbackLocked", long.class, Object.class, Object.class);
addTraversalQueue = ReflectUtils.reflectMethod(
callbackQueues[CALLBACK_TRAVERSAL], "addCallbackLocked", long.class, Object.class, Object.class);
}
MatrixLog.i(TAG, "[init] frameIntervalNanos:%s", frameIntervalNanos);
}
核心逻辑:
- 通过
ReflectUtils
反射获取Choreographer
的mFrameIntervalNanos
(帧间隔)、mCallbackQueues
(回调队列数组); - 注册
LooperDispatchListener
,关联主线程消息调度与帧监控,用于后续卡顿溯源。
1.2 帧回调注入:监听 Choreographer 三阶段耗时
UIThreadMonitor
通过addFrameCallback
向Choreographer
的三个核心队列(输入、动画、绘制)注入自定义回调,拆分每帧各阶段耗时:
typescript
// 源码位置:com.tencent.matrix.trace.core.UIThreadMonitor.java
private synchronized void addFrameCallback(int type, Runnable callback, boolean isAddHeader) {
if (callbackExist[type]) {
MatrixLog.w(TAG, "[addFrameCallback] this type %s callback has exist!", type);
return;
}
try {
synchronized (callbackQueueLock) { // Choreographer的回调队列锁
Method method = null;
// 按类型选择对应的队列add方法
switch (type) {
case CALLBACK_INPUT: method = addInputQueue; break;
case CALLBACK_ANIMATION: method = addAnimationQueue; break;
case CALLBACK_TRAVERSAL: method = addTraversalQueue; break;
}
if (null != method) {
// 注入回调:isAddHeader=true表示插入队列头部,确保优先执行
method.invoke(callbackQueues[type],
!isAddHeader ? SystemClock.uptimeMillis() : -1, callback, null);
callbackExist[type] = true;
}
}
} catch (Exception e) {
MatrixLog.e(TAG, e.toString());
}
}
// 帧回调核心逻辑(UIThreadMonitor实现Runnable)
@Override
public void run() {
final long start = System.nanoTime();
try {
doFrameBegin(token); // 标记帧开始
doQueueBegin(CALLBACK_INPUT); // 记录输入阶段开始时间
// 1. 注入动画阶段回调:输入阶段结束后触发
addFrameCallback(CALLBACK_ANIMATION, new Runnable() {
@Override
public void run() {
doQueueEnd(CALLBACK_INPUT); // 计算输入阶段耗时
doQueueBegin(CALLBACK_ANIMATION); // 标记动画阶段开始
}
}, true);
// 2. 注入绘制阶段回调:动画阶段结束后触发
addFrameCallback(CALLBACK_TRAVERSAL, new Runnable() {
@Override
public void run() {
doQueueEnd(CALLBACK_ANIMATION); // 计算动画阶段耗时
doQueueBegin(CALLBACK_TRAVERSAL); // 标记绘制阶段开始
}
}, true);
} finally {
if (config.isDevEnv()) {
MatrixLog.d(TAG, "[run] inner cost:%sns", System.nanoTime() - start);
}
}
}
核心逻辑:
- 向
CALLBACK_INPUT
(输入)、CALLBACK_ANIMATION
(动画)、CALLBACK_TRAVERSAL
(绘制)三个队列注入回调,通过doQueueBegin
/doQueueEnd
记录各阶段时间戳; - 最终通过
queueCost[type] = 结束时间 - 开始时间
,得到每帧各阶段的耗时(如输入处理耗时、布局绘制耗时)。
1.3 低版本帧率计算
UIThreadMonitor
在dispatchEnd
中计算帧率与掉帧次数,核心逻辑如下:
scss
// 源码位置:com.tencent.matrix.trace.core.UIThreadMonitor.java
private void dispatchEnd() {
if (config.isFPSEnable()) {
long startNs = token; // 帧开始时间戳
long intendedFrameTimeNs = startNs;
if (isVsyncFrame) {
doFrameEnd(token); // 标记帧结束,计算各阶段总耗时
intendedFrameTimeNs = getIntendedFrameTimeNs(startNs); // 获取理想帧时间
}
long endNs = System.nanoTime(); // 帧实际结束时间
// 计算掉帧数:(实际总耗时 - 理想帧间隔) / 理想帧间隔
long inputCost = queueCost[CALLBACK_INPUT];
long animationCost = queueCost[CALLBACK_ANIMATION];
long traversalCost = queueCost[CALLBACK_TRAVERSAL];
// 通知FrameTracer处理数据
synchronized (observers) {
for (LooperObserver observer : observers) {
if (observer.isDispatchBegin()) {
observer.doFrame(
AppActiveMatrixDelegate.INSTANCE.getVisibleScene(), // 当前界面
startNs, endNs, isVsyncFrame, intendedFrameTimeNs,
inputCost, animationCost, traversalCost
);
}
}
}
}
// 省略其他逻辑...
}
2. 高版本增强:FrameMetrics+Activity 生命周期(API≥24)
Android 7.0 引入FrameMetrics
,可直接获取 12 个渲染阶段耗时(如LAYOUT_MEASURE_DURATION
、GPU_DURATION
),FrameTracer
通过它实现更细粒度的监控。
2.1 FrameMetrics 监听注册
FrameTracer
在Activity
resumed 时动态注册FrameMetrics
监听,确保仅监控前台界面:
java
// 源码位置:com.tencent.matrix.trace.tracer.FrameTracer.java
@RequiresApi(Build.VERSION_CODES.N)
@Override
public void onActivityResumed(Activity activity) {
if (frameListenerMap.containsKey(activity.hashCode())) {
return;
}
// 1. 获取设备刷新率(适配120Hz/90Hz屏幕)
defaultRefreshRate = getRefreshRate(activity.getWindow());
MatrixLog.i(TAG, "default refresh rate is %dHz", (int) defaultRefreshRate);
// 2. 创建FrameMetrics监听
Window.OnFrameMetricsAvailableListener frameListener = new Window.OnFrameMetricsAvailableListener() {
private float cachedRefreshRate = defaultRefreshRate;
private float cachedThreshold = dropFrameListenerThreshold / 60f * cachedRefreshRate; // 动态掉帧阈值
@Override
public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
if (!isForeground()) return; // 过滤后台状态
// 3. 过滤异常数据(部分设备会产生无效值)
for (int i = FrameDuration.UNKNOWN_DELAY_DURATION.ordinal();
i <= FrameDuration.TOTAL_DURATION.ordinal(); i++) {
long v = frameMetrics.getMetric(FrameDuration.indices[i]);
if (v < 0 || v >= HALF_MAX) return;
}
// 4. 深拷贝FrameMetrics(避免系统回收数据)
FrameMetrics frameMetricsCopy = new FrameMetrics(frameMetrics);
// 5. 计算掉帧数:(实际总耗时 - 理想帧间隔) / 理想帧间隔
long totalDuration = frameMetricsCopy.getMetric(FrameMetrics.TOTAL_DURATION);
float frameIntervalNanos = Constants.TIME_SECOND_TO_NANO / cachedRefreshRate;
float droppedFrames = Math.max(0f, (totalDuration - frameIntervalNanos) / frameIntervalNanos);
droppedSum += droppedFrames;
// 6. 掉帧阈值判断(超过阈值则通知监听器)
if (dropFrameListener != null && droppedFrames >= cachedThreshold) {
dropFrameListener.onFrameMetricsAvailable(
ProcessUILifecycleOwner.INSTANCE.getVisibleScene(),
frameMetricsCopy, droppedFrames, cachedRefreshRate
);
}
// 7. 分发数据到收集器(如SceneFrameCollector)
synchronized (listeners) {
for (IFrameListener observer : listeners) {
observer.onFrameMetricsAvailable(
ProcessUILifecycleOwner.INSTANCE.getVisibleScene(),
frameMetricsCopy, droppedFrames, cachedRefreshRate
);
}
}
}
};
// 8. 注册监听并缓存(避免重复注册)
this.frameListenerMap.put(activity.hashCode(), frameListener);
activity.getWindow().addOnFrameMetricsAvailableListener(
frameListener, MatrixHandlerThread.getDefaultHandler()
);
}
核心逻辑:
- 动态适配设备刷新率(如 120Hz 屏幕对应 8.3ms 理想帧间隔),避免固定阈值导致的误判;
- 过滤异常数据(如负数或超大值),提升监控准确性;
- 通过
FrameMetrics.TOTAL_DURATION
计算总渲染耗时,结合理想帧间隔得到掉帧数。
2.2 渲染阶段耗时解析
FrameTracer
定义FrameDuration
枚举,映射FrameMetrics
的 10 个核心阶段,便于定位掉帧根源:
java
// 源码位置:com.tencent.matrix.trace.tracer.FrameTracer.java
public enum FrameDuration {
UNKNOWN_DELAY_DURATION, INPUT_HANDLING_DURATION, ANIMATION_DURATION,
LAYOUT_MEASURE_DURATION, DRAW_DURATION, SYNC_DURATION,
COMMAND_ISSUE_DURATION, SWAP_BUFFERS_DURATION, TOTAL_DURATION, GPU_DURATION;
// 映射FrameMetrics的原生指标索引
@SuppressLint("InlinedApi")
static final int[] indices = {
FrameMetrics.UNKNOWN_DELAY_DURATION, FrameMetrics.INPUT_HANDLING_DURATION,
FrameMetrics.ANIMATION_DURATION, FrameMetrics.LAYOUT_MEASURE_DURATION,
FrameMetrics.DRAW_DURATION, FrameMetrics.SYNC_DURATION,
FrameMetrics.COMMAND_ISSUE_DURATION, FrameMetrics.SWAP_BUFFERS_DURATION,
FrameMetrics.TOTAL_DURATION, FrameMetrics.GPU_DURATION
};
// 格式化输出各阶段耗时
public static String stringify(long[] durations) {
StringBuilder sb = new StringBuilder();
sb.append('{');
for (FrameDuration item : values()) {
sb.append(item.name()).append('=').append(durations[item.ordinal()]).append("; ");
}
sb.setLength(sb.length() - 2);
sb.append("}");
return sb.toString();
}
}
作用 :通过LAYOUT_MEASURE_DURATION
可快速定位 "布局层级过深" 问题,通过GPU_DURATION
可识别 "GPU 渲染阻塞"。
3. 场景化数据处理:SceneFrameCollector
FrameTracer
通过SceneFrameCollector
按界面(Activity)隔离帧率数据,避免跨场景数据混淆,核心是 "按场景聚合、定期上报"。
3.1 数据聚合逻辑
java
// 源码位置:com.tencent.matrix.trace.tracer.FrameTracer.java
@RequiresApi(Build.VERSION_CODES.N)
private class SceneFrameCollector implements IFrameListener {
private final Handler frameHandler = new Handler(MatrixHandlerThread.getDefaultHandlerThread().getLooper());
// 按场景名缓存数据(指定场景+未指定场景)
private final HashMap<String, SceneFrameCollectItem> specifiedSceneMap = new HashMap<>();
private final HashMap<ISceneFrameListener, SceneFrameCollectItem> unspecifiedSceneMap = new HashMap<>();
// 注册场景监听器(如AllSceneFrameListener)
public synchronized void register(@NonNull ISceneFrameListener listener) {
if (listener.getIntervalMs() < 1 || listener.getThreshold() < 0) {
MatrixLog.e(TAG, "Illegal value, intervalMs=%d, threshold=%d",
listener.getIntervalMs(), listener.getThreshold());
return;
}
String scene = listener.getName();
SceneFrameCollectItem collectItem = new SceneFrameCollectItem(listener);
if (scene == null || scene.isEmpty()) {
unspecifiedSceneMap.put(listener, collectItem);
} else {
specifiedSceneMap.put(scene, collectItem);
}
}
// 接收FrameMetrics数据并聚合
@Override
public void onFrameMetricsAvailable(final String sceneName, final FrameMetrics frameMetrics,
final float droppedFrames, final float refreshRate) {
frameHandler.post(new Runnable() { // 异步处理,避免阻塞主线程
@Override
public void run() {
synchronized (SceneFrameCollector.this) {
// 1. 匹配指定场景的数据项
SceneFrameCollectItem collectItem = specifiedSceneMap.get(sceneName);
if (collectItem != null) {
collectItem.append(sceneName, frameMetrics, droppedFrames, refreshRate);
}
// 2. 分发到未指定场景的监听器(如全局监控)
for (SceneFrameCollectItem item : unspecifiedSceneMap.values()) {
item.append(sceneName, frameMetrics, droppedFrames, refreshRate);
}
}
}
});
}
}
3.2 掉帧等级划分与上报触发
SceneFrameCollectItem
负责单一场景的帧率统计,按配置阈值划分掉帧等级(如正常、中等、严重、冻结),并在达到时间间隔后触发上报:
ini
// 源码位置:com.tencent.matrix.trace.tracer.FrameTracer.java
@RequiresApi(Build.VERSION_CODES.N)
private class SceneFrameCollectItem {
private final long[] durations = new long[FrameDuration.values().length]; // 各阶段耗时累计
private final int[] dropLevel = new int[DropStatus.values().length]; // 掉帧等级计数
private final int[] dropSum = new int[DropStatus.values().length]; // 掉帧次数累计
private float dropCount; // 总掉帧数
private float refreshRate; // 平均刷新率
private float totalDuration; // 平均总耗时
private long beginMs; // 统计开始时间
private String lastScene; // 最后一次场景名
private int count = 0; // 统计帧数
private ISceneFrameListener listener; // 关联的监听器
public SceneFrameCollectItem(ISceneFrameListener listener) {
this.listener = listener;
}
// 追加帧数据
public void append(String scene, FrameMetrics frameMetrics, float droppedFrames, float refreshRate) {
// 跳过首帧(部分场景首帧渲染耗时异常)
if ((listener.skipFirstFrame() && frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1)
|| droppedFrames < (refreshRate / 60) * listener.getThreshold()) {
return;
}
if (count == 0) {
beginMs = SystemClock.uptimeMillis(); // 初始化统计开始时间
}
// 1. 累计各阶段耗时
for (int i = FrameDuration.UNKNOWN_DELAY_DURATION.ordinal();
i <= FrameDuration.TOTAL_DURATION.ordinal(); i++) {
durations[i] += frameMetrics.getMetric(FrameDuration.indices[i]);
}
// 2. 累计掉帧数与刷新率
dropCount += droppedFrames;
this.refreshRate += refreshRate;
// 3. 累计总耗时(取实际耗时与理想帧间隔的最大值)
float frameIntervalNanos = Constants.TIME_SECOND_TO_NANO / refreshRate;
totalDuration += Math.max(frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION), frameIntervalNanos);
// 4. 统计掉帧等级
collect(Math.round(droppedFrames));
count++;
lastScene = scene;
// 5. 达到统计间隔,触发上报并重置
if (SystemClock.uptimeMillis() - beginMs >= listener.getIntervalMs()) {
tryCallBackAndReset();
}
}
// 划分掉帧等级(按配置的阈值:frozenThreshold>highThreshold>middleThreshold>normalThreshold)
private void collect(int droppedFrames) {
if (droppedFrames >= frozenThreshold) {
dropLevel[DropStatus.DROPPED_FROZEN.ordinal()]++;
dropSum[DropStatus.DROPPED_FROZEN.ordinal()] += droppedFrames;
} else if (droppedFrames >= highThreshold) {
dropLevel[DropStatus.DROPPED_HIGH.ordinal()]++;
dropSum[DropStatus.DROPPED_HIGH.ordinal()] += droppedFrames;
} else if (droppedFrames >= middleThreshold) {
dropLevel[DropStatus.DROPPED_MIDDLE.ordinal()]++;
dropSum[DropStatus.DROPPED_MIDDLE.ordinal()] += droppedFrames;
} else if (droppedFrames >= normalThreshold) {
dropLevel[DropStatus.DROPPED_NORMAL.ordinal()]++;
dropSum[DropStatus.DROPPED_NORMAL.ordinal()] += droppedFrames;
} else {
dropLevel[DropStatus.DROPPED_BEST.ordinal()]++;
dropSum[DropStatus.DROPPED_BEST.ordinal()] += Math.max(droppedFrames, 0);
}
}
// 触发上报并重置统计数据
void tryCallBackAndReset() {
if (count > 20) { // 过滤样本量过小的情况
// 计算平均值
dropCount /= count;
this.refreshRate /= count;
totalDuration /= count;
for (int i = 0; i < durations.length; i++) {
durations[i] /= count;
}
// 通知监听器上报(如AllSceneFrameListener)
listener.onFrameMetricsAvailable(
lastScene, durations, dropLevel, dropSum,
dropCount, this.refreshRate, Constants.TIME_SECOND_TO_NANO / totalDuration
);
}
reset(); // 重置累计数据
}
private void reset() {
dropCount = 0;
refreshRate = 0;
totalDuration = 0;
count = 0;
Arrays.fill(durations, 0);
Arrays.fill(dropLevel, 0);
Arrays.fill(dropSum, 0);
}
}
4. 卡顿溯源:LooperMonitor 的主线程消息监控
帧率下降的本质是 "主线程被耗时任务阻塞",LooperMonitor
通过 HookLooper
的Printer
,监听消息执行耗时,定位具体阻塞任务。
4.1 Looper Printer 替换
typescript
// 源码位置:com.tencent.matrix.trace.core.LooperMonitor.java
private synchronized void resetPrinter() {
Printer originPrinter = null;
try {
// 反射获取Looper的原生Printer(避免覆盖系统日志)
originPrinter = ReflectUtils.get(looper.getClass(), "mLogging", looper);
if (originPrinter == printer && null != printer) {
return;
}
} catch (Exception e) {
isReflectLoggingError = true;
Log.e(TAG, "[resetPrinter] %s", e);
}
// 替换为自定义Printer,监听消息开始/结束
looper.setMessageLogging(printer = new LooperPrinter(originPrinter));
if (null != originPrinter) {
MatrixLog.i(TAG, "reset printer, originPrinter[%s] in %s",
originPrinter, looper.getThread().getName());
}
}
// 自定义Printer,通过系统日志格式识别消息生命周期
class LooperPrinter implements Printer {
public Printer origin; // 原生Printer,用于转发日志
boolean isHasChecked = false;
boolean isValid = false; // 验证日志格式是否符合预期(">"开头为消息开始,"<"为结束)
LooperPrinter(Printer printer) {
this.origin = printer;
}
@Override
public void println(String x) {
// 1. 转发日志到原生Printer
if (null != origin) {
origin.println(x);
if (origin == this) {
throw new RuntimeException(TAG + " origin == this");
}
}
// 2. 验证日志格式(首次调用时)
if (!isHasChecked) {
isValid = x.charAt(0) == '>' || x.charAt(0) == '<';
isHasChecked = true;
if (!isValid) {
MatrixLog.e(TAG, "[println] Printer is inValid! x:%s", x);
}
}
// 3. 分发消息开始/结束事件
if (isValid) {
dispatch(x.charAt(0) == '>', x);
}
}
}
4.2 消息耗时统计与慢消息记录
scss
// 源码位置:com.tencent.matrix.trace.core.LooperMonitor.java
private void dispatch(boolean isBegin, String log) {
if (isBegin) {
// 消息开始:记录开始时间
if (historyMsgRecorder) {
messageStartTime = System.currentTimeMillis();
latestMsgLog = log;
recentMCount++;
}
// 通知监听器(如UIThreadMonitor)
synchronized (oldListeners) {
for (LooperDispatchListener listener : oldListeners) {
if (listener.isValid()) {
listener.onDispatchStart(log);
}
}
}
synchronized (listeners) {
for (DispatchListenerWrapper listener : listeners.values()) {
if (listener.isValid()) {
listener.onDispatchBegin(log);
}
}
}
} else {
// 消息结束:计算耗时并记录慢消息
if (historyMsgRecorder) {
long duration = System.currentTimeMillis() - messageStartTime;
recordMsg(log, duration); // 异步记录到历史队列
}
// 通知监听器
synchronized (oldListeners) {
for (LooperDispatchListener listener : oldListeners) {
if (listener.isValid()) {
listener.onDispatchEnd(log);
}
}
}
synchronized (listeners) {
for (DispatchListenerWrapper listener : listeners.values()) {
if (listener.isValid()) {
listener.onDispatchEnd(log);
}
}
}
}
}
// 异步记录慢消息(避免阻塞主线程)
private void recordMsg(final String log, final long duration) {
historyMsgHandler.post(new Runnable() {
@Override
public void run() {
enqueueHistoryMQ(new M(log, duration)); // 记录到ANR历史队列
}
});
if (denseMsgTracer) {
historyMsgHandler.post(new Runnable() {
@Override
public void run() {
enqueueRecentMQ(new M(log, duration)); // 记录到近期消息队列
}
});
}
}
核心逻辑 :当FrameTracer
检测到帧率下降时,LooperMonitor
已记录下主线程所有消息的耗时,可快速定位到 "耗时超过 16.6ms 的任务"(如主线程网络请求、复杂 JSON 解析),实现 "帧率异常→卡顿任务" 的溯源。
5. 数据上报:AllSceneFrameListener 上报监听
AllSceneFrameListener
是默认的上报监听器,按固定间隔(如 5 秒)聚合全场景帧率数据,构建Issue
并上报到 Matrix 监控平台:
typescript
// 源码位置:com.tencent.matrix.trace.tracer.FrameTracer.java
@RequiresApi(Build.VERSION_CODES.N)
static class AllSceneFrameListener implements ISceneFrameListener {
@Override
public int getIntervalMs() {
return Constants.DEFAULT_FPS_TIME_SLICE_ALIVE_MS; // 默认5000ms(5秒)
}
@Override
public String getName() { return null; } // 未指定场景,监控所有界面
@Override
public boolean skipFirstFrame() { return false; }
@Override
public int getThreshold() { return 0; } // 不过滤掉帧数据
@Override
public void onFrameMetricsAvailable(@NonNull String sceneName, long[] avgDurations,
int[] dropLevel, int[] dropSum, float avgDroppedFrame,
float avgRefreshRate, float avgFps) {
MatrixLog.i(TAG, "[report] FPS:%s scene:%s", avgFps, sceneName);
try {
// 获取TracePlugin实例,用于上报Issue
TracePlugin plugin = Matrix.with().getPluginByClass(TracePlugin.class);
if (null == plugin) return;
// 构建JSON上报数据
JSONObject dropLevelObject = new JSONObject();
JSONObject dropSumObject = new JSONObject();
for (DropStatus dropStatus : DropStatus.values()) {
dropLevelObject.put(dropStatus.name(), dropLevel[dropStatus.ordinal()]);
dropSumObject.put(dropStatus.name(), dropSum[dropStatus.ordinal()]);
}
JSONObject resultObject = new JSONObject();
DeviceUtil.getDeviceInfo(resultObject, plugin.getApplication()); // 设备信息
resultObject.put(SharePluginInfo.ISSUE_SCENE, sceneName); // 当前场景
resultObject.put(SharePluginInfo.ISSUE_DROP_LEVEL, dropLevelObject); // 掉帧等级
resultObject.put(SharePluginInfo.ISSUE_DROP_SUM, dropSumObject); // 掉帧次数
resultObject.put(SharePluginInfo.ISSUE_FPS, avgFps); // 平均帧率
// 追加各渲染阶段耗时
for (FrameDuration frameDuration : FrameDuration.values()) {
resultObject.put(frameDuration.name(), avgDurations[frameDuration.ordinal()]);
if (frameDuration.equals(FrameDuration.TOTAL_DURATION)) break;
}
resultObject.put("DROP_COUNT", Math.round(avgDroppedFrame)); // 平均掉帧数
resultObject.put("REFRESH_RATE", (int) avgRefreshRate); // 屏幕刷新率
// 构建Issue并上报
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);
}
}
}
三、FrameTracer 的亮点价值
- 跨版本兼容性 :低版本用
UIThreadMonitor
+Choreographer
保证基础监控,高版本用FrameMetrics
增强细粒度分析,覆盖 API 16 + 所有设备; - 低侵入性 :通过反射 Hook 系统 API(而非修改源码)。异步处理数据(如
SceneFrameCollector
用MatrixHandlerThread
),避免影响应用性能; - 场景化与可追溯 :按
Activity
隔离数据,结合LooperMonitor
定位卡顿任务,实现 "从帧率异常到代码级根因" 的闭环; - 动态适配:自动识别设备刷新率(60Hz/90Hz/120Hz),动态调整理想帧间隔,过滤异常数据,提升监控准确性。
以上是Matrix FrameTracer对Android 帧率监控的实现与源码剖析。欢迎关注公众号度熊君,一起分享交流。