第四篇FrameTrace帧率监控中提到,Android7.0及以上用系统API addOnFrameMetricsAvailableListener的方式,Android7.0以下通过自己实现的UIThreadMonitor(UI 线程的监视器,LooperPrinter和Choreographer实现),调用UIThreadMonitor.getMonitor().addObserver(looperObserver)
来监控帧率。
UIThreadMonitor是在TracePlugin启动的时候初始化的
Java
public void init(TraceConfig config) {
//必须在主线程初始化
if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
throw new AssertionError("must be init in main thread!");
}
//消息开始和结束回调(第二篇LooperAnrTracer卡顿ANR监控有分析过)
LooperMonitor.register(new LooperMonitor.LooperDispatchListener(historyMsgRecorder, denseMsgTracer) {
@Override
public boolean isValid() {
return isAlive;
}
//消息开始
@Override
public void dispatchStart() {
super.dispatchStart();
UIThreadMonitor.this.dispatchBegin();
}
//消息结束
@Override
public void dispatchEnd() {
super.dispatchEnd();
UIThreadMonitor.this.dispatchEnd();
}
});
//初始化标记
this.isInit = true;
//获取主线程的Choreographer
choreographer = Choreographer.getInstance();
//反射获取Choreographer的一帧对应的纳秒数
frameIntervalNanos = ReflectUtils.reflectObject(choreographer, "mFrameIntervalNanos", Constants.DEFAULT_FRAME_DURATION);
//反射获取Choreographer中mLock对象锁
callbackQueueLock = ReflectUtils.reflectObject(choreographer, "mLock", new Object());
//反射获取Choreographer中的mCallbackQueues数组(数组中有5个链表,每个链表存放input、animation、traversal事件的callback回调)
callbackQueues = ReflectUtils.reflectObject(choreographer, "mCallbackQueues", null);
if (null != callbackQueues) {
//获取3种回调的CallbackQueue链表的addCallbackLocked(long dueTime, Object action, Object token)(往链表中添加任务的方法)
addInputQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_INPUT], ADD_CALLBACK, long.class, Object.class, Object.class);
addAnimationQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_ANIMATION], ADD_CALLBACK, long.class, Object.class, Object.class);
addTraversalQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_TRAVERSAL], ADD_CALLBACK, long.class, Object.class, Object.class);
}
//获取Choreographer中用来接收Vsync信号的DisplayEventReceiver
vsyncReceiver = ReflectUtils.reflectObject(choreographer, "mDisplayEventReceiver", null);
}
UIThreadMonitor初始化时会监听主线程Looper处理每条消息前后,消息处理前回调dispatchBegin
, 消息处理后回调dispatchEnd
(第二篇LooperAnrTracer卡顿ANR监控有分析过),通过反射拿到主线程Choreographer中的一些重要属性(第五篇Choreographer源码分析有分析过)。
接着看下UIThreadMonitor的onStart启动方法:
Java
//3种回调的下标
public static final int CALLBACK_INPUT = 0;
public static final int CALLBACK_ANIMATION = 1;
public static final int CALLBACK_TRAVERSAL = 2;
private static final int CALLBACK_LAST = CALLBACK_TRAVERSAL;
//数组存放开始结束状态、是否存在、花费时间
private int[] queueStatus = new int[CALLBACK_LAST + 1];
private boolean[] callbackExist = new boolean[CALLBACK_LAST + 1]; // ABA
private long[] queueCost = new long[CALLBACK_LAST + 1];
@Override
public synchronized void onStart() {
//UIThreadMonitor需要先init才能调onStart
if (!isInit) {
MatrixLog.e(TAG, "[onStart] is never init.");
return;
}
if (!isAlive) {
this.isAlive = true;
synchronized (this) {
MatrixLog.i(TAG, "[onStart] callbackExist:%s %s", Arrays.toString(callbackExist), Utils.getStack());
//记录3种回调是否存在
callbackExist = new boolean[CALLBACK_LAST + 1];
}
//记录3种回调的状态(开始和结束)
queueStatus = new int[CALLBACK_LAST + 1];
//记录3种回调花费的时间
queueCost = new long[CALLBACK_LAST + 1];
//添加input回调
addFrameCallback(CALLBACK_INPUT, this, true);
}
}
queueStatus记录3种回调(input、animation、traversal)的状态(DO_QUEUE_BEGIN、DO_QUEUE_END),queueCost记录3种回调花费的时间。看下addFrameCallback是怎样计算花费的时间的:
Java
private synchronized void addFrameCallback(int type, Runnable callback, boolean isAddHeader) {
//回调已存在
if (callbackExist[type]) {
MatrixLog.w(TAG, "[addFrameCallback] this type %s callback has exist! isAddHeader:%s", type, isAddHeader);
return;
}
if (!isAlive && type == CALLBACK_INPUT) {
MatrixLog.w(TAG, "[addFrameCallback] UIThreadMonitor is not alive!");
return;
}
try {
synchronized (callbackQueueLock) {
Method method = null;
//根据回调类型获取对应链表的addCallbackLocked方法,init中通过反射获取的
switch (type) {
case CALLBACK_INPUT:
method = addInputQueue;
break;
case CALLBACK_ANIMATION:
method = addAnimationQueue;
break;
case CALLBACK_TRAVERSAL:
method = addTraversalQueue;
break;
}
if (null != method) {
//调用对应链表的addCallbackLocked(long dueTime, Object action, Object token),这里传入的token为null
method.invoke(callbackQueues[type], !isAddHeader ? SystemClock.uptimeMillis() : -1, callback, null);
//保存回调类型到callbackExist
callbackExist[type] = true;
}
}
} catch (Exception e) {
MatrixLog.e(TAG, e.toString());
}
}
回调添加完毕,ViewRootImp首次绘制或View发生变化时会请求接收Vsync信号,主线程Choreographer接收到Vsync信号会回调doFrame方法,由于传入的token为null,会调用run方法。
Java
@Override
public void run() {
final long start = System.nanoTime();
try {
//标记是垂直同步帧
doFrameBegin(token);
//记录状态和时间
doQueueBegin(CALLBACK_INPUT);
//添加animation回调
addFrameCallback(CALLBACK_ANIMATION, new Runnable() {
@Override
public void run() {
//input结束,animation开始
doQueueEnd(CALLBACK_INPUT);
doQueueBegin(CALLBACK_ANIMATION);
}
}, true);
//添加traversal回调
addFrameCallback(CALLBACK_TRAVERSAL, new Runnable() {
@Override
public void run() {
//animation结束,traversal开始
doQueueEnd(CALLBACK_ANIMATION);
doQueueBegin(CALLBACK_TRAVERSAL);
}
}, true);
} finally {
if (config.isDevEnv()) {
MatrixLog.d(TAG, "[UIThreadMonitor#run] inner cost:%sns", System.nanoTime() - start);
}
}
}
//垂直同步标记
private void doFrameBegin(long token) {
this.isVsyncFrame = true;
}
//记录状态和时间
private void doQueueBegin(int type) {
//状态改为DO_QUEUE_BEGIN
queueStatus[type] = DO_QUEUE_BEGIN;
//记录开始时间
queueCost[type] = System.nanoTime();
}
private void doQueueEnd(int type) {
//状态改为DO_QUEUE_END
queueStatus[type] = DO_QUEUE_END;
//记录时间差,即花费的时间
queueCost[type] = System.nanoTime() - queueCost[type];
synchronized (this) {
//callbackExist中改为false
callbackExist[type] = false;
}
}
这样就统计到了3种类型回调的耗时。由于Choreographer的doFrame是通过Handler发送的,所以能够监听Looper在消息结束回调dispatchEnd
Java
private void dispatchEnd() {
long traceBegin = 0;
if (config.isDevEnv()) {
//开发环境用时间差记录方法耗时
traceBegin = System.nanoTime();
}
//fps检测配置开启
if (config.isFPSEnable()) {
//消息开始时间
long startNs = token;
//先以消息开始时间作为收到信号的时间点
long intendedFrameTimeNs = startNs;
//Vsync信号事件
if (isVsyncFrame) {
//回调结束
doFrameEnd(token);
//获取收到信号的时间点,单位纳秒。反射Choreographer内部类FrameDisplayEventReceiver中的mTimestampNanos
intendedFrameTimeNs = getIntendedFrameTimeNs(startNs);
}
//记录结束时间
long endNs = System.nanoTime();
//通知监听者
synchronized (observers) {
for (LooperObserver observer : observers) {
if (observer.isDispatchBegin()) {
observer.doFrame(AppActiveMatrixDelegate.INSTANCE.getVisibleScene(), startNs, endNs, isVsyncFrame, intendedFrameTimeNs, queueCost[CALLBACK_INPUT], queueCost[CALLBACK_ANIMATION], queueCost[CALLBACK_TRAVERSAL]);
}
}
}
}
//重置
this.isVsyncFrame = false;
if (config.isDevEnv()) {
MatrixLog.d(TAG, "[dispatchEnd#run] inner cost:%sns", System.nanoTime() - traceBegin);
}
}
observer.doFrame则将数据通知到监听者,即实现了用UIThreadMonitor.getMonitor().addObserver(looperObserver)
来监控帧率。
如果是Vsync信号事件,则调用doFrameEnd结束traversal回调
Java
private void doFrameEnd(long token) {
//traversal结束,记录耗时
doQueueEnd(CALLBACK_TRAVERSAL);
//重置状态数组
queueStatus = new int[CALLBACK_LAST + 1];
//循环下一轮监听
addFrameCallback(CALLBACK_INPUT, this, true);
}
至此,UIThreadMonitor之帧率监控实现分析完毕
小结
基于LooperPrinter实现对UI线程每个消息执行的监控,通过反射给主线程Choreographer的input回调队列头部插入一个任务,用来监听下一帧的起点。队头的input任务被调用时,说明当前消息是处理绘制任务的,消息执行结束也就是当前帧的终点。同时在队头的input任务被调用时,给 animation、traversal的回调队列头部也插入任务,得到3个阶段的耗时。doFrame是通过Handler发送的,在处理绘制任务的消息结束后,重新给input回调队列头部插入任务,继续监听下一帧的耗时情况。