Android性能优化系列-腾讯matrix-TracePlugin之UIThreadMonitor源码分析

前言

UIThreadMonitor也是TracePlugin中的一个基础的能力,在分析其他tracer之前,有必要先将这些基础能力理解清楚,再进行后续的分析才能事半功倍。

UIThreadMonitors实现了两个接口:BeatLifecycle、Runnable,所以它是有生命周期的,同时也能运行在子线程中。我们从他几个关键的方法入手来查看源码。

init

方法初始化时,主要做了两件事:

  1. 向LooperMonitor注册监听,以拿到消息队列中消息执行前后的回调。LooperMonitor的实现可查阅Android性能优化系列-腾讯matrix-TracePlugin之LooperMonitor源码分析
  2. 8.0以下通过反射的方式获取到Choreographer类中CallbackQueues数组相关的信息备用。 获取到的关键信息如下:
diff 复制代码
- Choreographer对象
- Choreographer中的锁对象:mLock
- Choreographer中的CallbackQueue数组:mCallbackQueues
- Choreographer中的FrameDisplayEventReceiver对象:vsyncReceiver
- CallbackQueue数组中CALLBACK_INPUT类型的CallbackQueue对象的addCallbackLocked方法
- CallbackQueue数组中CALLBACK_ANIMATION类型的CallbackQueue对象的addCallbackLocked方法
- CallbackQueue数组中CALLBACK_TRAVERSAL类型的CallbackQueue对象的addCallbackLocked方法

注意,在UIThreadMonitor中,useFrameMetrics我们默认认为其值为false,因为UIThreadMonitor本身就是针对useFrameMetrics为false时的处理方案

ini 复制代码
public void init(TraceConfig config, boolean supportFrameMetrics) {
    //android N及以上使用Android提供了原生方法用于获取帧刷新的信息
    //addOnFrameMetricsAvailableListener,但是细心的你会发现,matrix
    //中Android O及以上supportFrameMetrics才设置为true?
    useFrameMetrics = supportFrameMetrics;
    
    LooperMonitor.register(new LooperMonitor.LooperDispatchListener(historyMsgRecorder, denseMsgTracer) {
        ...
    });
    //mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
    //1s = 1000000000ns, mFrameIntervalNanos表示刷新一次消耗的纳秒值,
    //假如刷新率为60,那么mFrameIntervalNanos = 16666666纳秒
    frameIntervalNanos = ReflectUtils.reflectObject(choreographer, "mFrameIntervalNanos", Constants.DEFAULT_FRAME_DURATION);
    if (!useFrameMetrics) {
        //8.0以下,反射得到Choreographer中的几个关键对象的方法
        choreographer = Choreographer.getInstance();
        callbackQueueLock = ReflectUtils.reflectObject(choreographer, "mLock", new Object());
        //反射得到mCallbackQueues数组
        callbackQueues = ReflectUtils.reflectObject(choreographer, "mCallbackQueues", null);
        if (null != callbackQueues) {
            //反射得到mCallbackQueues数组中不同类型对象的addCallbackLocked方法
            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中的FrameDisplayEventReceiver对象,用于接收vsync信号
        vsyncReceiver = ReflectUtils.reflectObject(choreographer, "mDisplayEventReceiver", null);
    }
}

onStart

初始化完成之后,开始onStart的调用,初始化了三个数组,然后调用addFrameCallback,将CALLBACK_INPUT类型的一个Runnable添加CALLBACK_INPUT类型的CallbackQueue中,并且是添加到头部位置。

arduino 复制代码
@Override
public synchronized void onStart() {
    synchronized (this) {
        //用来记录指定type类型的runnable是否添加过,如添加过,不会重复添加
        callbackExist = new boolean[CALLBACK_LAST + 1];
    }
    if (!useFrameMetrics) {
        //两个数组,记录状态和时间,后边会用到
        queueStatus = new int[CALLBACK_LAST + 1];
        queueCost = new long[CALLBACK_LAST + 1];
        //往CallbackQueue中添加
        addFrameCallback(CALLBACK_INPUT, this, true);
    }
}

addFrameCallback

此方法的作用是调用初始化时反射获取到的addCallbackLocked方法:

  1. addInputQueue
  2. addAnimationQueue
  3. addTraversalQueue

这三个Method都是addCallbackLocked的方法,只不过是不同对象的addCallbackLocked。 这里最先通过反射执行的是CALLBACK_INPUT类型的CallbackQueue的addCallbackLocked方法,会将当前Runnable(UIThreadMonitors本身就实现了Runnable接口)添加到系统Choreographer中的对应type类型的CallbackQueue中。

java 复制代码
private synchronized void addFrameCallback(int type, Runnable callback, boolean isAddHeader) {
    synchronized (callbackQueueLock) {
        Method method = null;
        switch (type) {
            //CALLBACK_INPUT类型
            case CALLBACK_INPUT:
                method = addInputQueue;
                break;
            //CALLBACK_ANIMATION类型
            case CALLBACK_ANIMATION:
                method = addAnimationQueue;
                break;
            //CALLBACK_TRAVERSAL类型
            case CALLBACK_TRAVERSAL:
                method = addTraversalQueue;
                break;
        }
        if (null != method) {
            //反射执行addCallbackLocked方法,将runnable添加进去
            method.invoke(callbackQueues[type], !isAddHeader ? SystemClock.uptimeMillis() : -1, callback, null);
        }
    }
}

addCallbackLocked

CallbackQueue是一个队列,队列内部存储的是CallbackRecord,CallbackRecord这里可以简单的认为是包装了Runnable的一个类,我们看下addCallbackLocked方法。

ini 复制代码
public void addCallbackLocked(long dueTime, Object action, Object token) {
    //将Runnable等信息封装成CallbackRecord对象
    CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
    CallbackRecord entry = mHead;
    //链表为空,设置为表头
    if (entry == null) {
        mHead = callback;
        return;
    }
    //将CallbackRecord插入到链表头位置,因为它时间比当前链表头时间小
    if (dueTime < entry.dueTime) {
        callback.next = entry;
        mHead = callback;
        return;
    }
    //将CallbackRecord插入到链表中,按照dueTime时间大小排序
    while (entry.next != null) {
        if (dueTime < entry.next.dueTime) {
            callback.next = entry.next;
            break;
        }
        entry = entry.next;
    }
    entry.next = callback;
}

那么这里在UIThreadMonitor onStart的时候往CALLBACK_INPUT类型的CallbackQueue队列中插入一个Runnable目的是什么?matrix想做什么?这里需要理解一些Choreographer的机制,简单提一下,详细内容可自行搜索。

Choreographer运行机制

Android的消息的运行依赖于VSYNC信号,每当新的VSYNC信号到达时,系统会一次性批量处理一些消息任务。而VSYNC信号的申请和接收是由Choreographer来管理的,我们直接进入VSYNC信号到达的位置看下代码,找到doFrame方法,这里只保留了与本次相关的逻辑。

scss 复制代码
void doFrame(long frameTimeNanos, int frame) {
    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}

可以看出doCallbacks传递的第一个参数似乎与上边提到的几种类型的type相呼应了,没错,它们表示的其实是同一个意思,这里提到了五种type,而matrix中关注的只有其中三种。我们看下doCallbacks的实现。

java 复制代码
void doCallbacks(int callbackType, long frameTimeNanos) {
    CallbackRecord callbacks;
    synchronized (mLock) {
        final long now = System.nanoTime();
        //拿到对应type的CallbackRecord
        callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                now / TimeUtils.NANOS_PER_MS);
        if (callbacks == null) {
            return;
        }
    }
    try {
        //遍历这个链表,所有被遍历到的CallbackRecord执行其run方法
        for (CallbackRecord c = callbacks; c != null; c = c.next) {
            c.run(frameTimeNanos);
        }
    } finally {
    }
}

可以看出,doCallbacks是按照当前时间从CallbackQueues中拿到一个CallbackRecord作为入口,遍历这个链表,所有被遍历到的CallbackRecord执行其run方法,这也就是上边我们提到的"VSYNC信号到达时,系统会一次性批量处理一些消息任务"。所以按照顺序,是先执行CALLBACK_INPUT类型的任务,再执行CALLBACK_ANIMATION类型的任务,下一步执行CALLBACK_TRAVERSAL类型的任务,不同类型的任务按照优先级依次排队执行。

run

继续回到上边对addFrameCallback的分析中,在onStart的时候将一个Runnable插入到CALLBACK_INPUT类型的CallbackQueues队列的最靠前的位置,那么当下一次VSYNC信号到来的时候,这个Runnable会在所有CALLBACK_INPUT类型的任务中第一个被执行,于是就走到了这里的run方法。

scss 复制代码
public void run() {
    //此方法执行时,说明vsync信号到达,主线程已经开始处理input类型的第一条消息
    final long start = System.nanoTime();
    try {
        //将isVsyncFrame设置为true,token是时间戳,记录消息队列当前消息执行前的那个时间节点
        doFrameBegin(token);
        //这里用到了onStart中初始化的两个数组,用来记录CALLBACK_INPUT类型消息开始的时间
        doQueueBegin(CALLBACK_INPUT);
        //调用addFrameCallback将CALLBACK_ANIMATION类型的runnable加入CALLBACK_ANIMATION
        //类型的队列中,这样,当CALLBACK_INPUT类型的消息全部执行完时,就会执行到下面这个runnable,并将这个节点作为CALLBACK_INPUT类型消息结束的时机,记录这一vsync信号期间,
        //所有CALLBACK_INPUT类型消息执行消耗的时长。
        addFrameCallback(CALLBACK_ANIMATION, new Runnable() {

            @Override
            public void run() {
                //input类型消息执行完成
                doQueueEnd(CALLBACK_INPUT);
                //记录CALLBACK_ANIMATION开始执行
                doQueueBegin(CALLBACK_ANIMATION);
            }
        }, true);
        //调用addFrameCallback将CALLBACK_TRAVERSAL类型的runnable加入CALLBACK_TRAVERSAL
        //类型的队列中,这样,当CALLBACK_ANIMATION类型的消息全部执行完时,就会执行到下面这个runnable,并将这个节点作为CALLBACK_ANIMATION类型消息结束的时机,记录这一vsync信号期间,
        //所有CALLBACK_ANIMATION类型消息执行消耗的时长。
        addFrameCallback(CALLBACK_TRAVERSAL, new Runnable() {

            @Override
            public void run() {
                //CALLBACK_ANIMATION类型消息执行完成
                doQueueEnd(CALLBACK_ANIMATION);
                //记录CALLBACK_TRAVERSAL开始执行
                doQueueBegin(CALLBACK_TRAVERSAL);
            }
        }, true);

    } finally {
    }
}

按照注释中的分析,一个VSYNC信号期间,input类型消息、animation类型消息和traversal类型消息执行分别消耗了多少时间就统计出来了。但是你发现CALLBACK_TRAVERSAL只有开始,没有结束,怎么统计时间的?因为CALLBACK_TRAVERSAL类型消息执行完时,就表示当前VSYNC执行完成了,此时没有办法通过相同的方式记录结束的节点,于是在另一个节点做的记录-doFrameEnd方法中,doFrameEnd是在dispatchEnd被调用,dispatchEnd又是在LooperMonitor回调中执行的,从Android性能优化系列-腾讯matrix-TracePlugin之LooperMonitor源码分析中分析可知,LooperMonitor会将消息队列中每一消息执行前后的回调给到监听者,dispatchEnd就是消息执行完的时机,所以用来记录CALLBACK_TRAVERSAL执行完的时机也非常合适。

arduino 复制代码
private void doFrameEnd(long token) {
    doQueueEnd(CALLBACK_TRAVERSAL);
    //上一VSYNC周期结束了,再添加一个CALLBACK_INPUT,如此周而复始,整个消息运行
    //的时间就清晰的记录了下来
    addFrameCallback(CALLBACK_INPUT, this, true);
}
typescript 复制代码
private void doQueueBegin(int type) {
    //指定type开始
    queueStatus[type] = DO_QUEUE_BEGIN;
    //记录开始的时间
    queueCost[type] = System.nanoTime();
}
typescript 复制代码
private void doQueueEnd(int type) {
    //指定type结束
    queueStatus[type] = DO_QUEUE_END;
    //记录type类型消息消耗的时长
    queueCost[type] = System.nanoTime() - queueCost[type];
    synchronized (this) {
        callbackExist[type] = false;
    }
}

onStop

onStop切换状态。

java 复制代码
@Override
public synchronized void onStop() {
    if (!isInit) {
        return;
    }
    if (isAlive) {
        this.isAlive = false;
    }
}

LooperMonitor的两个回调方法也是很关键的逻辑,这里我们顺着这两个回调再看一下相关源码。

dispatchBegin

这里会回调dispatchBegin方法给外界,传递相关参数。

scss 复制代码
private void dispatchBegin() {
    //记录消息开始分发的时间。System.nanoTime():从设备开机到现在的时间,单位毫秒,
    //不含系统深度睡眠时间
    token = dispatchTimeMs[0] = System.nanoTime();
    //记录当前线程总共运行的时间,单位毫秒
    dispatchTimeMs[2] = SystemClock.currentThreadTimeMillis();
    if (config.isAppMethodBeatEnable()) {
        //主动调一下i方法,内部会更新时间偏移量,参考:https://juejin.cn/post/7278883051380408356
        AppMethodBeat.i(AppMethodBeat.METHOD_ID_DISPATCH);
    }
    //分发给所有监听者
    synchronized (observers) {
        for (LooperObserver observer : observers) {
            if (!observer.isDispatchBegin()) {
                observer.dispatchBegin(dispatchTimeMs[0], dispatchTimeMs[2], token);
            }
        }
    }
}

dispatchEnd

此方法中会回调doFrame和dispatchEnd方法给外界,并传递相关参数。

scss 复制代码
private void dispatchEnd() {
    if (config.isFPSEnable() && !useFrameMetrics) {
        long startNs = token;
        long intendedFrameTimeNs = startNs;
        if (isVsyncFrame) {
            //记录traversal执行完成,并开启下一个周期
            doFrameEnd(token);
            intendedFrameTimeNs = getIntendedFrameTimeNs(startNs);
        }

        long endNs = System.nanoTime();

        synchronized (observers) {
            for (LooperObserver observer : observers) {
                if (observer.isDispatchBegin()) {
                   //一帧结束。回调doFrame
                   //注意餐素比较关键
                   //1.当前可见的页面
                   //2.消息执行前的时间点
                   //3.消息执行完成后的时间点
                   //4.是否是根据vsync计算
                   //5.onVsync方法回调时的时间点
                   //6.input消耗的时长
                   //7.animation消耗的时长
                   //8.traversal消耗的时长
                   observer.doFrame(AppActiveMatrixDelegate.INSTANCE.getVisibleScene(), startNs, endNs, isVsyncFrame, intendedFrameTimeNs, queueCost[CALLBACK_INPUT], queueCost[CALLBACK_ANIMATION], queueCost[CALLBACK_TRAVERSAL]);
                }
            }
        }
    }
    //慢方法记录开启的话,记录线程执行时间和当前执行时间
    if (config.isEvilMethodTraceEnable() || config.isDevEnv()) {
        dispatchTimeMs[3] = SystemClock.currentThreadTimeMillis();
        dispatchTimeMs[1] = System.nanoTime();
    }
    //主动调用o方法
    AppMethodBeat.o(AppMethodBeat.METHOD_ID_DISPATCH);
    //回调消息分发结束
    synchronized (observers) {
        for (LooperObserver observer : observers) {
            if (observer.isDispatchBegin()) {
                //参数
                //1.dispathStart开始的时间戳
                //2.线程执行(cpu执行)开始的时间
                //3.dispatchEnd结束时的时间戳
                //4.线程执行(cpu执行)结束的时间
                //5.token等于dispatchTimeMs[0]
                //4和2两个参数可以计算出cpu运行的时长,3和1可以计算出总时长
                observer.dispatchEnd(dispatchTimeMs[0], dispatchTimeMs[2], dispatchTimeMs[1], dispatchTimeMs[3], token, isVsyncFrame);
            }
        }
    }
    this.isVsyncFrame = false;
}

总结

由上边的分析可知,UIThreadMonitors总体上就是对LooperMonitor的封装与增强,它借助了LooperMonitor两个回调,dispatchBegin和dispatchEnd,并在这两个回调的基础上,丰富了各种参数,如消息执行总时间、消息执行期间cpu运行的时间,一个消息中input时间消耗的时间、animation消耗的时间、traversal消耗的时间等等,方便后续进行更细致的分析。

相关推荐
SRC_BLUE_1728 分钟前
SQLI LABS | Less-39 GET-Stacked Query Injection-Intiger Based
android·网络安全·adb·less
无尽的大道4 小时前
Android打包流程图
android
镭封5 小时前
android studio 配置过程
android·ide·android studio
夜雨星辰4875 小时前
Android Studio 学习——整体框架和概念
android·学习·android studio
邹阿涛涛涛涛涛涛5 小时前
月之暗面招 Android 开发,大家快来投简历呀
android·人工智能·aigc
IAM四十二6 小时前
Jetpack Compose State 你用对了吗?
android·android jetpack·composer
奶茶喵喵叫6 小时前
Android开发中的隐藏控件技巧
android
Winston Wood8 小时前
Android中Activity启动的模式
android
众乐认证8 小时前
Android Auto 不再用于旧手机
android·google·智能手机·android auto
三杯温开水8 小时前
新的服务器Centos7.6 安卓基础的环境配置(新服务器可直接粘贴使用配置)
android·运维·服务器