APM框架Matrix源码分析(六)UIThreadMonitor之帧率监控实现

第四篇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回调队列头部插入任务,继续监听下一帧的耗时情况。

相关推荐
xvch2 小时前
Kotlin 2.1.0 入门教程(二十三)泛型、泛型约束、协变、逆变、不变
android·kotlin
ianozo3 小时前
BUU40 [安洵杯 2019]easy_serialize_php
android·开发语言·php
abs6253 小时前
uniapp使用uts插件启动原生安卓Service
android·uni-app·uniapp uts插件·uniapp 安卓服务
Evaporator Core3 小时前
MATLAB在投资组合优化中的应用:从基础理论到实践
android
Neo Evolution4 小时前
Flutter与移动开发的未来:谷歌的技术愿景与实现路径
android·人工智能·学习·ios·前端框架·webview·着色器
coooliang4 小时前
Flutter项目中设置安卓启动页
android·flutter
xianrenli384 小时前
android 使用 zstd算法压缩文件
android
九思x5 小时前
Android Studio安装配置及运行
android·ide·android studio
风浅月明16 小时前
[Android]如何判断当前APP是Debug还是Release环境?
android
freflying111916 小时前
使用jenkins构建Android+Flutter项目依赖自动升级带来兼容性问题及Jenkins构建速度慢问题解决
android·flutter·jenkins