直接从FrameTracer的入口看起
@Override
public void onAlive() {
super.onAlive();
if (config.isFPSEnable()) {
forceEnable();
}
}
public void forceEnable() {
MatrixLog.i(TAG, "forceEnable");
//Android 7.0及以上
if (sdkInt >= Build.VERSION_CODES.N) {
//注册Activity生命周期回调
Matrix.with().getApplication().registerActivityLifecycleCallbacks(this);
sceneFrameCollector = new SceneFrameCollector();
//添加监听,对应observer.onFrameMetricsAvailable
addListener(sceneFrameCollector);
//设置SceneFrameCollectItem的listener,listener会回调上报
register(new AllSceneFrameListener());
} else {
//接收到信号的时候回调这些监听就可以让 FrameTracer 接收到每帧的回调
UIThreadMonitor.getMonitor().addObserver(looperObserver);
}
}
Android 7.0及以上
先注册了Activity生命周期回调
//线程安全ConcurrentHashMap,用来存放activity的window对应的渲染监听器
private final Map<Integer, Window.OnFrameMetricsAvailableListener>
frameListenerMap = new ConcurrentHashMap<>();
//在Activity可见时添加监听
@RequiresApi(Build.VERSION_CODES.N)
@Override
public void onActivityResumed(Activity activity) {
//避免重复添加
if (frameListenerMap.containsKey(activity.hashCode())) {
return;
}
//获取刷新频率
defaultRefreshRate = getRefreshRate(activity.getWindow());
MatrixLog.i(TAG, "default refresh rate is %dHz", (int) defaultRefreshRate);
//Google官方推荐使用 OnFrameMetricsAvailableListener 测量布局渲染时间(用于Android7.0及以上)
Window.OnFrameMetricsAvailableListener onFrameMetricsAvailableListener = new Window.OnFrameMetricsAvailableListener() {
//这里应该是 Build.VERSION_CODES.N
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
//只有在前台才会去做数据处理,应用不可见时不会进行UI渲染
if (isForeground()) {
//跳过无意义数据的回调
// skip not available metrics.
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) {
// some devices will produce outliers, especially the Honor series, eg: NTH-AN00, ANY-AN00, etc.
return;
}
}
//先copy一份, 之后从copy的数据中获取对应渲染时间参数
FrameMetrics frameMetricsCopy = new FrameMetrics(frameMetrics);
//TOTAL_DURATION: CPU 渲染到传递到 GPU 所用的总时间, 上述所花费的有意义的时间之和 , 单位纳秒
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;
//【掉帧收集】掉帧数>=阈值,收集起来(不指定dropFrameListener不会收集)
if (dropFrameListener != null && droppedFrames >= cachedThreshold) {
dropFrameListener.onFrameMetricsAvailable(
ProcessUILifecycleOwner.INSTANCE.getVisibleScene(),
frameMetricsCopy, droppedFrames, cachedRefreshRate);
}
synchronized (listeners) {
//【收集所有】回调的SceneFrameCollector的onFrameMetricsAvailable,对应上面addListener(sceneFrameCollector)
for (IFrameListener observer : listeners) {
observer.onFrameMetricsAvailable(
ProcessUILifecycleOwner.INSTANCE.getVisibleScene(),
frameMetricsCopy, droppedFrames, cachedRefreshRate);
}
}
}
}
};
//监听器存入map,每个Activity对应一个监听器
this.frameListenerMap.put(activity.hashCode(), onFrameMetricsAvailableListener);
//向Activity界面的Window窗口对象,添加监听器,同时设置Handler,测量渲染在这个Handler所在的线程执行
activity.getWindow().addOnFrameMetricsAvailableListener(
onFrameMetricsAvailableListener, MatrixHandlerThread.getDefaultHandler());
MatrixLog.i(TAG, "onActivityResumed addOnFrameMetricsAvailableListener");
}
@Override
public void onActivityDestroyed(Activity activity) {
try {
//onDestory时移除监听
activity.getWindow().removeOnFrameMetricsAvailableListener(
frameListenerMap.remove(activity.hashCode()));
} catch (Throwable t) {
MatrixLog.e(TAG, "removeOnFrameMetricsAvailableListener error : " + t.getMessage());
}
}
在Activity可见时调用activity.getWindow().addOnFrameMetricsAvailableListener
向Activity界面的Window窗口对象,添加监听器,onFrameMetricsAvailable回调中FrameMetrics 封装了渲染时间(包含总耗时/绘制耗时/布局耗时/动画耗时/测量耗等),计算掉帧数据并收集。
接着看有意义数据的收集observer.onFrameMetricsAvailable
@RequiresApi(Build.VERSION_CODES.N)
private class SceneFrameCollector implements IFrameListener {
//上述register(new AllSceneFrameListener());会调到这里
public synchronized void register(@NonNull ISceneFrameListener listener) {
//省略listener参数不合法判断。。。
//如果设置了场景(哪个页面),按照场景统计
String scene = listener.getName();
//创建SceneFrameCollectItem并把listener传入,存放到两个map中
SceneFrameCollectItem collectItem = new SceneFrameCollectItem(listener);
if (scene == null || scene.isEmpty()) {
//如果没设置场景,按照AllSceneFrameListener和FrameDecorator(帧率UI展示,如果设置了)
unspecifiedSceneMap.put(listener, collectItem);
} else {
//设置了场景,按照场景统计
specifiedSceneMap.put(scene, collectItem);
}
}
//有意义数据的收集`observer.onFrameMetricsAvailable`回调
@Override
public void onFrameMetricsAvailable(
final String sceneName, final FrameMetrics frameMetrics,
final float droppedFrames, final float refreshRate) {
frameHandler.post(new Runnable() {
@Override
public void run() {
String scene = sceneName.getClass().getName();
synchronized (SceneFrameCollector.this) {
SceneFrameCollectItem collectItem = specifiedSceneMap.get(scene);
//从specifiedSceneMap中取数据
if (collectItem != null) {
collectItem.append(sceneName, frameMetrics, droppedFrames, refreshRate);
}
//处理未设置场景的数据
for (SceneFrameCollectItem frameCollectItem : unspecifiedSceneMap.values()) {
frameCollectItem.append(sceneName, frameMetrics, droppedFrames, refreshRate);
}
}
}
});
}
}
append会调到SceneFrameCollectItem的append方法
@RequiresApi(Build.VERSION_CODES.N)
private class SceneFrameCollectItem {
//【维度1】每个区间的 掉帧次数,用来统计占比
private final int[] dropLevel = new int[DropStatus.values().length];
//【维度2】每个区间的 掉帧数之和,用来统计分布
private final int[] dropSum = new int[DropStatus.values().length];
//上述new SceneFrameCollectItem(listener)会把listener传入
SceneFrameCollectItem(ISceneFrameListener listener) {
this.listener = listener;
}
public void append(String scene, FrameMetrics frameMetrics, float droppedFrames, float refreshRate) {
//判断不统计的情况
//(设置跳过第一帧 && 当前是第一帧 FIRST_DRAW_FRAME:绘制的该帧是否是第一帧, 0 不是, 1 是 )|| 掉帧数 < 阈值(默认0,就是都统计)
if ((listener.skipFirstFrame() && frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1)
|| droppedFrames < (refreshRate / 60) * listener.getThreshold()) {
return;
}
//记录beginMs
if (count == 0) {
beginMs = SystemClock.uptimeMillis();
}
//记录所有的花费的时间
for (int i = FrameDuration.UNKNOWN_DELAY_DURATION.ordinal(); i <= FrameDuration.TOTAL_DURATION.ordinal(); i++) {
durations[i] += frameMetrics.getMetric(FrameDuration.indices[i]);
}
//Android12及以上记录GPU_DURATION:表示此帧在GPU上完成所用的总时间(以纳秒为单位)
if (sdkInt >= Build.VERSION_CODES.S) {
durations[FrameDuration.GPU_DURATION.ordinal()] += frameMetrics.getMetric(FrameMetrics.GPU_DURATION);
}
//掉帧数累加
dropCount += droppedFrames;
//统计流畅度的占比情况
collect(Math.round(droppedFrames));
this.refreshRate += refreshRate;
//不掉帧时的时间
float frameIntervalNanos = Constants.TIME_SECOND_TO_NANO / refreshRate;
//记录实际花费的总时间
totalDuration += Math.max(frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION), frameIntervalNanos);
//计数+1
++count;
lastScene = scene;
//10秒上报一次
if (SystemClock.uptimeMillis() - beginMs >= listener.getIntervalMs()) {
//计算+回调上报+重置参数
tryCallBackAndReset();
}
}
}
上述代码主要是将回调的FrameMetrics参数通过计算和统计并上报的过程。
Matrix定义了一套衡量流畅度的标准:DROPPED_BEST
, DROPPED_NORMAL
, DROPPED_MIDDLE
, DROPPED_HIGH
, DROPPED_FROZEN
;一共5个区间。
统计按照两个维度:
dropLevel
每个区间的 掉帧次数,用来统计占比dropSum
每个区间的 掉帧数之和,用来统计分布
Android7.0以下
UIThreadMonitor 拥有监听系统 VSync 信号的能力,在接收到信号的时候回调这些监听就可以让 FrameTracer 接收到每帧的doFrame回调。(这里暂时先不管UIThreadMonitor怎么实现的此功能,后面分析)
private LooperObserver looperObserver = new LooperObserver() {
/**
* 每一帧的回调
*
* @param focusedActivity 当前顶层Activity的名字
* @param startNs 消息开始分发的时间点,单位纳秒
* @param endNs 消息分发结束的时间点,单位纳秒
* @param isVsyncFrame 是否是UI渲染帧
* @param intendedFrameTimeNs 收到信号的时间点,单位纳秒。反射Choreographer内部类FrameDisplayEventReceiver中的mTimestampNanos
* @param inputCostNs input耗时
* @param animationCostNs animation耗时
* @param traversalCostNs traversal耗时
*
*/
@Override
public void doFrame(String focusedActivity, long startNs, long endNs, boolean isVsyncFrame, long intendedFrameTimeNs, long inputCostNs, long animationCostNs, long traversalCostNs) {
//同样只有前台才处理
if (isForeground()) {
notifyListener(focusedActivity, startNs, endNs, isVsyncFrame, intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs);
}
}
@Deprecated
private void notifyListener(final String focusedActivity, final long startNs, final long endNs, final boolean isVsyncFrame,
final long intendedFrameTimeNs, final long inputCostNs, final long animationCostNs, final long traversalCostNs) {
long traceBegin = System.currentTimeMillis();
try {
//实际耗时的纳秒
final long jitter = endNs - intendedFrameTimeNs;
//掉帧数 = 实际耗时的纳秒/每帧的纳秒
final int dropFrame = (int) (jitter / frameIntervalNs);
//可以设置oldDropFrameListener及阈值来监听掉帧
if (oldDropFrameListener != null && dropFrame > dropFrameListenerThreshold) {
try {
if (MatrixUtil.getTopActivityName() != null) {
oldDropFrameListener.dropFrame(dropFrame, jitter, MatrixUtil.getTopActivityName());
}
} catch (Exception e) {
MatrixLog.e(TAG, "dropFrameListener error e:" + e.getMessage());
}
}
//计算总的掉帧数和时间
droppedSum += dropFrame;
durationSum += Math.max(jitter, frameIntervalNs);
//可以添加listener到oldListeners来监听
synchronized (oldListeners) {
for (final IDoFrameListener listener : oldListeners) {
//这里只分析listener.collect,省略其它回调
//这里的listener可以传入FPSCollector(最新版没有),默认300帧一分析
if (null != listener.getExecutor()) {
if (listener.getIntervalFrameReplay() > 0) {
//收集上报
listener.collect(focusedActivity, startNs, endNs, dropFrame, isVsyncFrame,
intendedFrameTimeNs, inputCostNs, animationCostNs, traversalCostNs);
}
}
}
}
} finally {
//debug下日志打印掉帧
long cost = System.currentTimeMillis() - traceBegin;
if (config.isDebug() && cost > frameIntervalNs) {
MatrixLog.w(TAG, "[notifyListener] warm! maybe do heavy work in doFrameSync! size:%s cost:%sms", oldListeners.size(), cost);
}
}
}
};
listener.collect会走到IDoFrameListener的collect
@CallSuper
public void collect(String focusedActivity, long startNs, long endNs, int dropFrame,
boolean isVsyncFrame,long intendedFrameTimeNs, long inputCostNs,
long animationCostNs, long traversalCostNs) {
//从链表缓冲池中取FrameReplay(存每一帧的信息),没有则new一个
FrameReplay replay = FrameReplay.create();
//下面都是属性赋值
replay.focusedActivity = focusedActivity;
replay.startNs = startNs;
replay.endNs = endNs;
replay.dropFrame = dropFrame;
replay.isVsyncFrame = isVsyncFrame;
replay.intendedFrameTimeNs = intendedFrameTimeNs;
replay.inputCostNs = inputCostNs;
replay.animationCostNs = animationCostNs;
replay.traversalCostNs = traversalCostNs;
list.add(replay);
//到达阈值 & 有提供线程执行器 开始分析
if (list.size() >= intervalFrame && getExecutor() != null) {
//拷贝一份,不干扰原内存数据
final List<FrameReplay> copy = new LinkedList<>(list);
list.clear();
getExecutor().execute(new Runnable() {
@Override
public void run() {
//收集数据分析
doReplay(copy);
for (FrameReplay record : copy) {
//处理完成回收,用于复用
record.recycle();
}
}
});
}
}
//FrameReplay链表缓存池,用于复用
private final static LinkedList<FrameReplay> sPool = new LinkedList<>();
public static final class FrameReplay {
public String focusedActivity;
public long startNs;
public long endNs;
public int dropFrame;
public boolean isVsyncFrame;
public long intendedFrameTimeNs;
public long inputCostNs;
public long animationCostNs;
public long traversalCostNs;
//回收,将对象放入缓存池,只擦除属性,方便下次复用
public void recycle() {
//最大缓存1000条
if (sPool.size() <= 1000) {
this.focusedActivity = "";
this.startNs = 0;
this.endNs = 0;
this.dropFrame = 0;
this.isVsyncFrame = false;
this.intendedFrameTimeNs = 0;
this.inputCostNs = 0;
this.animationCostNs = 0;
this.traversalCostNs = 0;
synchronized (sPool) {
sPool.add(this);
}
}
}
//复用
public static FrameReplay create() {
FrameReplay replay;
synchronized (sPool) {
//先从缓存池中取
replay = sPool.poll();
}
if (replay == null) {
//取不到则new一个
return new FrameReplay();
}
return replay;
}
}
对于频繁创建对象的处理是使用一个链表缓冲池
- 使用时先从缓存池中取对象,没有则创建一个
- 回收时,限制缓存最大数量,只擦除属性,并添加到缓存池
接着看下v2.0.8的FrameTracer的内部类FPSCollector
FPSCollector复写IDoFrameListener的doReplay
方法
private class FPSCollector extends IDoFrameListener {
private Handler frameHandler = new Handler(MatrixHandlerThread.getDefaultHandlerThread().getLooper());
//提供分析数据的线程池
Executor executor = new Executor() {
@Override
public void execute(Runnable command) {
frameHandler.post(command);
}
};
//用来聚合页面的帧信息
private HashMap<String, FrameCollectItem> map = new HashMap<>();
//提供线程池
@Override
public Executor getExecutor() {
return executor;
}
//多少帧分析一次
@Override
public int getIntervalFrameReplay() {
return 300;
}
@Override
public void doReplay(List<FrameReplay> list) {
super.doReplay(list);
//遍历帧集合,对每一帧进行分析
for (FrameReplay replay : list) {
doReplayInner(replay.focusedActivity, replay.startNs, replay.endNs,
replay.dropFrame, replay.isVsyncFrame,replay.intendedFrameTimeNs,
replay.inputCostNs, replay.animationCostNs, replay.traversalCostNs);
}
}
public void doReplayInner(String visibleScene, long startNs, long endNs,
int droppedFrames,boolean isVsyncFrame,
long intendedFrameTimeNs, long inputCostNs,
long animationCostNs, long traversalCostNs) {
//当前顶层Activity为空
if (Utils.isEmpty(visibleScene)) return;
//isVsyncFrame表示是否是UI渲染帧,只关注帧率
if (!isVsyncFrame) return;
//从页面的维度聚合,将页面和每一帧关联
FrameCollectItem item = map.get(visibleScene);
if (null == item) {
item = new FrameCollectItem(visibleScene);
map.put(visibleScene, item);
}
//对每个页面的帧信息根据掉帧数划分等级
item.collect(droppedFrames);
//10秒上报一次
if (item.sumFrameCost >= timeSliceMs) { // report
map.remove(visibleScene);
//上报
item.report();
}
}
}
小结:
FrameTrace帧率监控:
- 监听帧率信息回调,Android7.0及以上用系统API addOnFrameMetricsAvailableListener的方式,Android7.0以下通过自己实现的UIThreadMonitor(UI 线程的监视器,LooperPrinter和Choreographer实现)
- 收集每一帧的信息,当前页面名字、input、animation、traversal阶段的耗时,Android7.0及以上用系统API,统计的更精细。
- 定义了衡量流畅度的5个标椎,从页面的维度聚合数据,计算每个区间的掉帧次数,用来统计占比,计算每个区间的掉帧数之和,用来统计分布