Android 腾讯 Matrix 原理分析(三):TracePlugin 卡顿分析之帧率监听

前言

TracePlugin 卡顿分析插件中包含很多 Tracer,而 FrameTracer 负责监听帧率。拿到产生的帧率数据之后,根据用户设置的丢帧阈值进行报告。

那么 TracePlugin 是怎么拿到每一帧数据的?本篇文章将围绕这个问题依据 Matrix 源码进行解答。

一、准备工作

我在上篇文章中写道,UIThreadMonitor 是 Tracer 工作功能实现的基础,FrameTracer 也不例外。

UIThreadMonitor 可以监听主线程 Looper 事件、接收硬件每 16ms 发送来的垂直同步 VSync 信号,而 FrameTracer 通过设置监听到 UIThreadMonitor 接收到每帧刷新的回调方法 onFrame()

从 FrameTracer 的介绍我们知道,该类具有统计每帧数据的能力,而这些数据则是由 UIThreadMonitor 提供的。UIThreadMonitor 又是怎么做到的呢?接下来一起看看吧。

1.1 UIThreadMonitor 初始化

TracePlugin 在 Matrix init 之后由开发者手动启动,TracePlugin 启动后会初始化 UIThreadMonitor,执行它的 init() 方法:

UIThreadMonitor # init()

public class UIThreadMonitor implements BeatLifecycle, Runnable {
    
    private static final String ADD_CALLBACK = "addCallbackLocked";
    private Object callbackQueueLock;
    private Object[] callbackQueues;
    // 三种类型添加数据的方法
    private Method addTraversalQueue;
    private Method addInputQueue;
    private Method addAnimationQueue;
    private Choreographer choreographer;
    
    public void init(TraceConfig config) {
        if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
            throw new AssertionError("must be init in main thread!");
        }
        // 第一部分
        // getInstance() 是 ThreadLocal 实现,在主线程创建的,所以获取的是主线程的 Choreographer
        choreographer = Choreographer.getInstance();
        // 获取 Choreographer 的对象锁
        callbackQueueLock = ReflectUtils.reflectObject(choreographer, "mLock", new Object());
        // 获取 Choreographer 的 mCallbackQueues 对象,也就是 CallbackQueues 数组
        callbackQueues = ReflectUtils.reflectObject(choreographer, "mCallbackQueues", null);
        if (null != callbackQueues) {
            // 获取三种类型对象的 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);
        }
        vsyncReceiver = ReflectUtils.reflectObject(choreographer, "mDisplayEventReceiver", null);
        frameIntervalNanos = ReflectUtils.reflectObject(choreographer, "mFrameIntervalNanos", Constants.DEFAULT_FRAME_DURATION);
    }
    // 第二部分
    LooperMonitor.register(new LooperMonitor.LooperDispatchListener() {
         @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.getInstance() 是 ThreadLocal 实现,而 UIThreadMonitor 是在主线程初始化的,所以获取的是主线程的 Choreographer 对象。

Choreographer 在接收到 VSync 信号之后会通过内部的 Handler 发送一个异步消息执行 doFrame() 方法。由于是主线程的 Choreographer,所以执行 doFrame() 方法也是在主线程环境下完成的。

而在 doFrame() 方法中会遍历 CallbackQueue 数组(Choreographer 内部维护的回调队列)并回调它们的 doFrame()/run() 方法,Matrix 当然可以通过 Choreographer 的外部方法向 CallbackQueue 添加监听,但是这样做就只能接收到回调而不能达到监控的目的。

所以 Matrix 利用反射向 CallbackQueue 数组每个下标链表头部添加回调,在每个类型回调之后更新状态并统计耗时。

回过头来看第一部分的代码,第一部分要注意的是反射获取的三个方法的逻辑:

  • callbackQueues 数组三个下标对应三种不同的回调类型对象:CALLBACK_INPUT(下标 0)、CALLBACK_ANIMATION(下标 1)、CALLBACK_TRAVERSAL(下标 2)。
  • 那么 callbackQueues[CALLBACK_INPUT]、callbackQueues[CALLBACK_ANIMATION]、callbackQueues[CALLBACK_TRAVERSAL] 获取的就是这三种类型的对象实例。
  • 最后获取它们的 addCallbackLocked() 方法,这个方法的作用是往当前链表添加元素。

接着看一下反射获取到的方法原型:

Choreographer.CallbackQueue # addCallbackLocked()

public void addCallbackLocked(long dueTime, Object action, Object token) {
    // 缓存获取,如果没有则创建
    CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
    CallbackRecord entry = mHead;
    // 头结点为 null 直接替代
    if (entry == null) {
        mHead = callback;
        return;
    }
    // 根据时间排序
    if (dueTime < entry.dueTime) {
        callback.next = entry;
        mHead = callback;
        return;
    }
    while (entry.next != null) {
        if (dueTime < entry.next.dueTime) {
            callback.next = entry.next;
            break;
        }
        entry = entry.next;
    }
    entry.next = callback;
}
  • CallbackRecord:所有的回调会被包装为 CallbackRecord 类,该类会保存定时时间、回调、token,后文会再分析;
  • 后面的逻辑也比较简单,就是往当前链表添加元素。注意如果传入的定时时间 dueTime 小于头结点的时间,则会替换头结点。

这样就 创建了 Choreographer 并提供了添加三种回调类型的方法。

第二部分:监听主线程 Looper 事件前后

第二部分的代码主要就是监听主线程 Looper 处理每条消息前后,也就是说处理消息前回调 dispatchStart()、消息被 Handler 处理之后回调 dispatchEnd()

有关 UIThreadMonitor 和 LooperMonitor 的实现可以参考前文:

Android 腾讯 Matrix 原理分析(二):TracePlugin 卡顿分析之主线程监听

2.2 准备数据容器

UIThreadMonitor 执行 init() 初始化之后,紧接着会调用 onStar() 方法启动:

UIThreadMonitor # onStar()

public class UIThreadMonitor implements BeatLifecycle, Runnable {
    // 三种回调三个下标
    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 long[] queueCost = new long[CALLBACK_LAST + 1];
    
    @Override
    public synchronized void 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());
                callbackExist = new boolean[CALLBACK_LAST + 1];
            }
            // 1.状态数组
            queueStatus = new int[CALLBACK_LAST + 1];
            // 2.花费时间数组
            queueCost = new long[CALLBACK_LAST + 1];
            addFrameCallback(CALLBACK_INPUT, this, true);
        }
    }
}

根据上篇文章可知,我们需要关注的回调类型有三种:CALLBACK_INPUT(输入)CALLBACK_ANIMATION(动画)CALLBACK_TRAVERSAL(绘制)

对于 Matrix 来说,要做的事情有两件:

  1. 记录每种回调的状态,也就是数组 queueStatus,容量为 3(因为监听三种回调嘛)。
    顺带提一下,数组 queueStatus 每个下标可以赋值为固定的状态值:
private static final int DO_QUEUE_BEGIN = 1;
private static final int DO_QUEUE_END = 2;
  1. 记录每种回调所花费的时间,数组 queueCost,容量同样为 3。
    每个下标第一次记录回调开始的时间,第二次计算出花费的时间并记录。

这样添加监听系统信号的方法有了,储存数据的容器也有了,就可以让 FrameTracer 添加监听并接收数据了。

二、FrameTracer 添加监听

FrameTracer 是 TracePlugin(卡顿分析插件) 的一部分,所以也是在 TracePlugin 中创建和开始工作的:

TracePlugin # init & start() 简略版

public class TracePlugin extends Plugin {
    private FrameTracer frameTracer;

    @Override
    public void init(Application app, PluginListener listener) {
        super.init(app, listener);
        // 1.初始化
        frameTracer = new FrameTracer(traceConfig);
    }
    
    @Override
    public void start() {
    super.start();
    Runnable runnable = new Runnable() {
         @Override
         public void run() {
             if (!UIThreadMonitor.getMonitor().isInit()) {
                 try {
                     UIThreadMonitor.getMonitor().init(traceConfig);
                 } catch (java.lang.RuntimeException e) {
                     MatrixLog.e(TAG, "[start] RuntimeException:%s", e);
                     return;
                 }
             }
             // 开始监听主线程
             UIThreadMonitor.getMonitor().onStart();
             // 2.Tracer 开始工作
             frameTracer.onStartTrace();
         }
     };
    }
}
  1. new FrameTracer:FrameTracer 的构造器主要利用传入的用户配置 traceConfig 进行参数设置,然后添加一个 FPS 的监听。后文可能还会分析,这里先不贴这段代码。

  2. frameTracer.onStartTrace():TracePlugin 的 start() 方法是由开发者手动调用的,里面调用父类 Tracer 的onStartTrace() 标记 FrameTracer 进入活动状态。

Tracer # onStartTrace()

@Override
final synchronized public void onStartTrace() {
    if (!isAlive) {
        this.isAlive = true;
        onAlive();
    }
}

紧接着调用 onAlive() 使子类 FrameTracer 开始工作:

FrameTracer # onAlive()

@Override
public void onAlive() {
    super.onAlive();
    UIThreadMonitor.getMonitor().addObserver(this);
}

可以看到 FrameTracer 进入活动状态后只是把自己添加到 UIThreadMonitor 的监听者列表中,添加的类型是 LooperObserver。

UIThreadMonitor # addObserver

public void addObserver(LooperObserver observer) {
    if (!isAlive) {
        onStart();
    }
    synchronized (observers) {
        observers.add(observer);
    }
}

因为父类 Tracer 实现了 LooperObserver 接口,所以可以被添加到监听列表中,而添加监听的目的就是为了接收每帧回调。

在第一节的准备中,UIThreadMonitor 已经拥有了监听系统 VSync 信号的能力,只需要在接收到信号的时候回调这些监听就可以让 FrameTracer 接收到每帧的回调。

接下来看 UIThreadMonitor 是如何接收系统垂直同步信号并返回给监听者的。

三、UIThreadMonitor 监听帧率

3.1 监听系统 VSync 信号

UIThreadMonitor 的 onStart() 方法中有一句重要代码:

UIThreadMonitor # onStart()

@Override
public synchronized void 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());
            callbackExist = new boolean[CALLBACK_LAST + 1];
        }
        queueStatus = new int[CALLBACK_LAST + 1];
        queueCost = new long[CALLBACK_LAST + 1];
        // 添加系统信号回调
        addFrameCallback(CALLBACK_INPUT, this, true);
    }
}

就是最后这句 addFrameCallback(CALLBACK_INPUT, this, true);注意此时第一个参数为 CALLBACK_INPUT 表示添加输入类型的回调、第二个参数 this 也就是把 UIThreadMonitor 这个线程对象传递、第三个 true 表示添加到队首。

接下来看是怎么添加的:

UIThreadMonitor # addFrameCallback()

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;
            // 1. 根据添加类型得到要调用的对象方法
            switch (type) {
                case CALLBACK_INPUT:
                    method = addInputQueue;
                    break;
                case CALLBACK_ANIMATION:
                    method = addAnimationQueue;
                    break;
                case CALLBACK_TRAVERSAL:
                    method = addTraversalQueue;
                    break;
            }
            if (null != method) {
                // 2. 调用相应对象添加元素的方法
                method.invoke(callbackQueues[type], !isAddHeader ? SystemClock.uptimeMillis() : -1, callback, null);
                callbackExist[type] = true;
            }
        }
    } catch (Exception e) {
        MatrixLog.e(TAG, e.toString());
    }
}
  1. 首先根据传入的回调类型确定调用的方法,方法对象在 init() 的时候已经创建好了,这里直接调用就可以了。
    比如这里传入的是 CALLBACK_INPUT 类型,使用的是 addInputQueue 方法对象。
Method addInputQueue = ReflectUtils.reflectMethod(callbackQueues[CALLBACK_INPUT], ADD_CALLBACK, long.class, Object.class, Object.class);

这个 Method 对象由三部分组成:

  • callbackQueues[CALLBACK_INPUT]:对象实例,获取 callbackQueues 第一个元素,类型为 CallbackQueue;
  • ADD_CALLBACK:方法名,表示调用的是上面对象的 addCallbackLocked 方法;
    private static final String ADD_CALLBACK = "addCallbackLocked";
  • long.class, Object.class, Object.class:方法参数,对象可能包含多个方法重载,所以传入入参类型确定具体调用哪个方法。

CallbackQueue # addCallbackLocked

public void addCallbackLocked(long dueTime, Object action, Object token) {...}

  1. 确定好方法之后,就可以调用 invoke() 执行了,后面参数也可以分为三部分:
  • callbackQueues[type]:对象实例,传来的是 CALLBACK_INPUT 也就是调用 callbackQueues 第一个对象的 addCallbackLocked() 方法;
  • !isAddHeader ? SystemClock.uptimeMillis() : -1:如果添加到链表头传 -1,反之传入开机到当前的时间总数;
  • callback:接收系统信号的回调,这里传入的是 this 表示由 UIThreadMonitor 接收。UIThreadMonitor 是一个线程,所以会回调它的 run() 方法。

回调添加完毕,接下来就等系统发出 VSync 信号了。

3.3 准备接收系统信号

ViewRootImp 在首次绘制或者子 View 们发生变化时会请求接收 VSync 信号,接着 Choreographer 就会收到硬件发来的信号。

Choreographer 收到系统的 VSync 信号之后,调用 doFrame() 遍历回调数组:

Choreographer # doFrame()

void doFrame(long frameTimeNanos, int frame) {
     ...
     try {
         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
         AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

         mFrameInfo.markInputHandlingStart();
         doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

         mFrameInfo.markAnimationsStart();
         doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);

         mFrameInfo.markPerformTraversalsStart();
         doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

         doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
     } finally {
         AnimationUtils.unlockAnimationClock();
         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
     ...
 }

这段代码要注意的是回调执行的顺序,可以看到先是回调 CALLBACK_INPUT 类型的数据,等到遍历回调完毕之后再遍历后续的 CALLBACK_ANIMATIONCALLBACK_TRAVERSAL

而回调的方法也很简单,就是生成 CallbackRecord 链表并遍历执行 run() 方法:

Choreographer # doCallbacks()

void doCallbacks(int callbackType, long frameTimeNanos) {
    CallbackRecord callbacks;
    synchronized (mLock) {
        final long now = System.nanoTime();
        // 1.遍历 callbackType 类型链表,包装成 CallbackRecord 链表
        callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                now / TimeUtils.NANOS_PER_MS);
        if (callbacks == null) {
            return;
        }
        mCallbacksRunning = true;
        ...
    }
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
        // 2.遍历执行 run 方法
        for (CallbackRecord c = callbacks; c != null; c = c.next) {
            c.run(frameTimeNanos);
        }
    } finally {
        synchronized (mLock) {
            mCallbacksRunning = false;
            do {
                final CallbackRecord next = callbacks.next;
                // 回收资源
                recycleCallbackLocked(callbacks);
                callbacks = next;
            } while (callbacks != null);
        }
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

CallbackRecord 的 run() 方法就是根据 token 来判断执行 doFrame() 还是 run()

Choreographer.CallbackRecord

private static final class CallbackRecord {
    public CallbackRecord next;
    public long dueTime;
    public Object action; // Runnable or FrameCallback
    public Object token;
    // 调用 run 方法
    public void run(long frameTimeNanos) {
        if (token == FRAME_CALLBACK_TOKEN) {
            ((FrameCallback)action).doFrame(frameTimeNanos);
        } else {
            ((Runnable)action).run();
        }
    }
}

在 3.3 节中可知 UIThreadMonitor 传入的 token 是 null,所以会回调它的 run()方法。

3.4 接收到信号之后

UIThreadMonitor # run()

@Override
public void run() {
    final long start = System.nanoTime();
    try {
        // 1.标记回调垂直同步
        doFrameBegin(token);
        // 2.记录时间
        doQueueBegin(CALLBACK_INPUT);
        // 3.添加动画类型回调
        addFrameCallback(CALLBACK_ANIMATION, new Runnable() {

            @Override
            public void run() {
                // 3.1CALLBACK_INPUT结束、CALLBACK_ANIMATION开始
                doQueueEnd(CALLBACK_INPUT);
                doQueueBegin(CALLBACK_ANIMATION);
            }
        }, true);
        // 4.添加绘制类型回调
        addFrameCallback(CALLBACK_TRAVERSAL, new Runnable() {

            @Override
            public void run() {
                // 4.1CALLBACK_ANIMATION结束、CALLBACK_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;
}
  1. doFrameBegin() 方法只是设置一个标记,用来表示当前接收到了垂直同步信号,该标记后面会有用处;
  2. 执行到 run() 方法说明 Choreographer 已经开始回调 CALLBACK_INPUT 类型的对象了,而 UIThreadMonitor 又被添加到了链表头部,所以记录时间作为 CALLBACK_INPUT 类型回调的起始;
private void doQueueBegin(int type) {
    // 记录状态
    queueStatus[type] = DO_QUEUE_BEGIN;
    // 记录时间
    queueCost[type] = System.nanoTime();
}
  1. 接着添加 CALLBACK_ANIMATION 类型的回调,等待 Choreographer 的遍历;
    3.1 等到开始回调 CALLBACK_ANIMATION 类型的数据,说明 CALLBACK_INPUT 已经全部回调完毕了,调用 doQueueEnd() 更新状态记录时间。
private void doQueueEnd(int type) {
    // 更新状态
    queueStatus[type] = DO_QUEUE_END;
    // 当前时间 - 开始时记录的时间 = 处理 type 类型花费的时间
    queueCost[type] = System.nanoTime() - queueCost[type];
    synchronized (this) {
        callbackExist[type] = false;
    }
}
  1. 同理,添加 CALLBACK_TRAVERSAL 类型的回调;
    4.1 等到开始执行 CALLBACK_TRAVERSAL 类型的回调,说明上一个状态的回调已经遍历执行完毕了,所以更改 CALLBACK_ANIMATION 的状态并记录所花费的时间。

小总结

这部分可能稍微有点绕,我们画个图来帮助理解:
添加回调流程
  1. UIThreadMonitor 添加 CALLBACK_INPUT 类型的回调到 Choreographer;
    addFrameCallback(CALLBACK_INPUT, this, true);
  2. Choreographer 接收到系统信号,遍历 CALLBACK_INPUT 链表并执行回调;
    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
  3. 此时 UIThreadMonitor 接收到回调,说明 CALLBACK_INPUT 类型的事件开始处理了,记录为开始状态并记录开始时间;
    doQueueBegin(CALLBACK_INPUT);
    然后添加 CALLBACK_ANIMATION 类型的回调;
    addFrameCallback(CALLBACK_ANIMATION, new Runnable(){...},true);
  4. Choreographer 开始遍历 CALLBACK_ANIMATION 链表,然后执行回调;
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
  5. CALLBACK_ANIMATION 类型接收到回调,说明上一个类型 CALLBACK_INPUT 事件处理完毕了,记录为结束状态并统计耗时;
    doQueueEnd(CALLBACK_INPUT);
    同时 CALLBACK_ANIMATION 事件开始处理了,记录为开始状态并记录开始时间;
    doQueueBegin(CALLBACK_ANIMATION);
  6. 添加 CALLBACK_TRAVERSAL 类型的回调;
    addFrameCallback(CALLBACK_TRAVERSAL, new Runnable(){...},true);
  7. Choreographer 开始遍历 CALLBACK_TRAVERSAL 链表,然后执行回调;
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
  8. CALLBACK_TRAVERSAL 类型接收到回调,说明 上一个类型CALLBACK_ANIMATION 的事件处理完毕了,记录为结束状态并统计耗时;
    doQueueEnd(CALLBACK_ANIMATION);
    同时 CALLBACK_TRAVERSAL 事件开始处理了,记录为开始状态并记录开始时间;

到这里 ViewRootImp 三种类型的回调都已经执行了,UIThreadMonitor 也成功监听并统计耗时。但是还有最后的问题,CALLBACK_TRAVERSAL 回调只有开始没有结束。

要解决这个问题,需要了解一个大前提:三种类型的回调是在 Choreographer 的 doFrame() 方法中回调的,而 doFrame() 方法是由 Handler 发送线程来执行的。

Choreographer.FrameDisplayEventReceiver

private final class FrameDisplayEventReceiver extends DisplayEventReceiver
        implements Runnable {
    ...
    @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
        ...
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }

    @Override
    public void run() {
        doFrame(mTimestampNanos, mFrame);
    }
}

Handler 发送的事件是由 Looper 取出来处理的,如果能够监听 Looper 处理完这个事件了,说明 doFrame() 方法也执行完毕、 CALLBACK_TRAVERSA 也回调完毕了。

那么这个 Looper 处理事件结束时可以监听到么?当然可以,就是上篇文章分析过的 LooperMonitor。

四、FrameTracer 接收数据

LooperMonitor 是在 UIThreadMonitor 初始化时添加监听的:

UIThreadMonitor # init()

public void init(TraceConfig config) {
    ...
    LooperMonitor.register(new LooperMonitor.LooperDispatchListener() {
        @Override
        public boolean isValid() {
            return isAlive;
        }

        @Override
        public void dispatchStart() {
            super.dispatchStart();
            UIThreadMonitor.this.dispatchBegin();
        }

        @Override
        public void dispatchEnd() {
            super.dispatchEnd();
            UIThreadMonitor.this.dispatchEnd();
        }
    });
    ...
}

这样就能够监听到 Looper 取出来的所有事件处理前后的消息,当然前提是在主线程环境下。

因为 UIThreadMonitor 是在主线程创建的,所以监听的就是主线程的 Looper。

但是这里还有一个问题,主线程 Looper 所有事件都监听,怎么才能确定是 VSync 信号事件呢?很简单,在接收到 VSync 信号的第一个回调设置一个标记,就是前面出现过的 isVsyncFrame

可以看到 UIThreadMonitor 在接收到第一个回调之后,就标记 isVsyncFrame 为 true 说明现在处理的是 doFrame() 事件。

UIThreadMonitor # run()

@Override
public void run() {
    final long start = System.nanoTime();
    try {
        doFrameBegin(token);
        ...
    }
}

private void doFrameBegin(long token) {
    this.isVsyncFrame = true;
}

然后在 doFrame() 事件结束之后,给 LooperMonitor 设置的监听会回调 dispatchEnd() 方法执行 UIThreadMonitor 的 dispatchEnd() 方法:

UIThreadMonitor # dispatchEnd()

private void dispatchEnd() {
    long traceBegin = 0;
    long startNs = token;
    long intendedFrameTimeNs = startNs;
    if (isVsyncFrame) {
        // 1.结束回调
        doFrameEnd(token);
        intendedFrameTimeNs = getIntendedFrameTimeNs(startNs);
    }
    // 2.通知监听
    long endNs = System.nanoTime();
    synchronized (observers) {
         for (LooperObserver observer : observers) {
             if (observer.isDispatchBegin()) {
                 observer.doFrame(AppMethodBeat.getVisibleScene(), startNs, endNs, isVsyncFrame, intendedFrameTimeNs, queueCost[CALLBACK_INPUT], queueCost[CALLBACK_ANIMATION], queueCost[CALLBACK_TRAVERSAL]);
             }
         }
    }
    this.isVsyncFrame = false;
}
  1. 可以看到如果是 isVsyncFrame doFrame() 事件则调用 doFrameEnd() 结束 CALLBACK_TRAVERSA 事件:

UIThreadMonitor # doFrameEnd()

private void doFrameEnd(long token) {
    // 标记结束,统计耗时
    doQueueEnd(CALLBACK_TRAVERSAL);
    queueStatus = new int[CALLBACK_LAST + 1];
    // 设置新一轮的监听
    addFrameCallback(CALLBACK_INPUT, this, true);
}
  • 到这个方法就结束了 CALLBACK_TRAVERSA 的回调,并且成功统计到了耗时;
  • doFrame() 可能是被持续调用的,因为 UI 发生变化每秒要刷新 60 次呢,所以需要添加新一轮的监听。
    再次接收到 CALLBACK_INPUT 就再修改状态、统计时间,添加另外两种类型的回调。
  1. 一轮 doFrame() 执行完毕之后,就可以回调添加到 UIThreadMonitor 的监听来统计信息了。而 FrameTracer 就是众多监听者的一员。
    UIThreadMonitor 遍历所有 LooperObserver 并执行它们的 doFrame() 方法并传递参数,FrameTracer 通过 doFrame() 方法就可以拿到数据展示信息了。

FrameTracer # doFrame()

@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);
    }
}

到这里 FrameTracer 终于接收到了数据,依据这些数据就可以进行帧率统计、帧率图绘制等工作了。

总结

简单总结下本篇文章记录的内容:

  1. FrameTracer 为了接收三种事件处理的时间和帧率,向 UIThreadMonitor 添加监听;
  2. UIThreadMonitor 内部获取主线程 Choreographer 用于接收垂直同步信号,同时反射获取向 Choreographer 数组添加回调方法的对象;
  3. UIThreadMonitor 初始化时向 Choreographer 数组添加回调,等 Choreographer 回调数组之后记录每种回调的状态和耗时;
  4. 最后 UIThreadMonitor 将回调得来的数据返回给监听者,其中就包含 FrameTracer。
  5. FrameTracer 拿到数据之后在作处理,鉴于篇幅,FrameTracer 的具体逻辑将在下篇文章进行分析。

为了得到 FrameTracer 要使用的数据,使用了 UIThreadMonitorLooperMonitorChoreographer 等,真是不容易呀。

对于 Matrix 来说,UIThreadMonitor 是很重要的部分。帧率、慢函数、ANR 监控等都用到了它,看懂 UIThreadMonitor 的实现对理解 Matrix 框架会有很大帮助。

最后感谢大家的阅读。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容