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

相关推荐
烬奇小云3 小时前
认识一下Unicorn
android·python·安全·系统安全
顾北川_野15 小时前
Android 进入浏览器下载应用,下载的是bin文件无法安装,应为apk文件
android
CYRUS STUDIO15 小时前
Android 下内联汇编,Android Studio 汇编开发
android·汇编·arm开发·android studio·arm
右手吉他15 小时前
Android ANR分析总结
android
PenguinLetsGo17 小时前
关于 Android15 GKI2407R40 导致梆梆加固软件崩溃
android·linux
杨武博19 小时前
音频格式转换
android·音视频
音视频牛哥21 小时前
Android音视频直播低延迟探究之:WLAN低延迟模式
android·音视频·实时音视频·大牛直播sdk·rtsp播放器·rtmp播放器·android rtmp
ChangYan.21 小时前
CondaError: Run ‘conda init‘ before ‘conda activate‘解决办法
android·conda
二流小码农21 小时前
鸿蒙开发:ForEach中为什么键值生成函数很重要
android·ios·harmonyos
夏非夏1 天前
Android 生成并加载PDF文件
android