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

前言

接上文,我们首先来看 TracePlugin 卡顿分析的实现。TracePlugin 主要靠监视主线程的各种状态来分析是否卡顿,接下来我们开始吧。

前文传送门:

Android 腾讯 Matrix 原理分析(一):Matrix 概览

一、TracePlugin 类关系

TracePlugin 继承抽象类 Plugin,Plugin 又实现了三个接口:

  • IPlugin:拥有插件的特性,其中包含插件生命周期的回调;
  • IssuePublisher.OnIssueDetectListener:可以监听 Issue 报告;
  • IAppForeground:可以接收 App 前后台切换的信息。

实现关系

二、TracePlugin 的初始化

前文已经提到过,插件创建之后添加到了 Matrix 中,在 Matrix 初始化的时候会遍历所有插件并执行它们的 init() 方法:

public class TracePlugin extends Plugin {
    private static final String TAG = "Matrix.TracePlugin";

    private final TraceConfig traceConfig;
    private EvilMethodTracer evilMethodTracer;
    private StartupTracer startupTracer;
    private FrameTracer frameTracer;
    private AnrTracer anrTracer;

    public TracePlugin(TraceConfig config) {
        this.traceConfig = config;
    }

    @Override
    public void init(Application app, PluginListener listener) {
        super.init(app, listener);
        MatrixLog.i(TAG, "trace plugin init, trace config: %s", traceConfig.toString());
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
            MatrixLog.e(TAG, "[FrameBeat] API is low Build.VERSION_CODES.JELLY_BEAN(16), TracePlugin is not supported");
            unSupportPlugin();
            return;
        }
		// 初始化四个 Tracer
        anrTracer = new AnrTracer(traceConfig);
        frameTracer = new FrameTracer(traceConfig);
        evilMethodTracer = new EvilMethodTracer(traceConfig);
        startupTracer = new StartupTracer(traceConfig);
    }
    ...
}

需要注意的是 TracePlugin 包含了四个 Tracer,暂且取名为追踪器:

  • EvilMethodTracer:慢函数追踪器;
  • StartupTracer:启动追踪器;
  • FrameTracer:帧率追踪器;
  • AnrTracer:ANR 追踪器。

TracePlugin 的功能主要依靠这四个追踪器来实现,也是我们重点学习的目标,但是在看他们之前还是要先了解大致结构和作好知识储备。

在学习 Tracer 之前需要了解的知识有点多,所以本文暂时不会分析 Tracer 的实现。但是本文分析的内容是 Tracer 功能实现的基础:UIThreadMonitor(主线程监视器),正是有了 UIThreadMonitor 才能让 Tracer 了解到 App 运行时的各种状态。

三、TracePlugin 开始工作

根据前文可知,我们需要在 Matrix init 之后手动调用插件的 start() 方法:

TracePlugin # start

@Override
public void start() {
    super.start();
    if (!isSupported()) {
        MatrixLog.w(TAG, "[start] Plugin is unSupported!");
        return;
    }
    MatrixLog.w(TAG, "start!");
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            // 1. UI线程监视器初始化
            if (!UIThreadMonitor.getMonitor().isInit()) {
                try {
                    UIThreadMonitor.getMonitor().init(traceConfig);
                } catch (java.lang.RuntimeException e) {
                    MatrixLog.e(TAG, "[start] RuntimeException:%s", e);
                    return;
                }
            }

            AppMethodBeat.getInstance().onStart();
            UIThreadMonitor.getMonitor().onStart();
            // 2. 追踪器开始工作
            anrTracer.onStartTrace();
            frameTracer.onStartTrace();
            evilMethodTracer.onStartTrace();
            startupTracer.onStartTrace();
        }
    };
    // 3. 线程检查
    if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
        runnable.run();
    } else {
        MatrixLog.w(TAG, "start TracePlugin in Thread[%s] but not in mainThread!", Thread.currentThread().getId());
        MatrixHandlerThread.getDefaultMainHandler().post(runnable);
    }
}

上述代码有三块重要内容:

  1. UIThreadMonitor:一个监听 UI 线程的监视器,监听什么内容?怎么监听?后面会给出答案。
  2. 追踪器开始工作:TracePlugin 包含的四大追踪器,是实际的工作者。
  3. 线程检查:监视器和追踪器等需要在主线程进行初始化,所以分为两种情况:
    当前在主线程下:直接调用 Runnable 对象的 run() 方法执行初始化;
    当前不在主线程:使用 MatrixHandlerThread 中的主线程 Handler post 任务,切换到主线程执行初始化。
private static volatile Handler defaultMainHandler = new Handler(Looper.getMainLooper());

3.1 UIThreadMonitor

说明

顾名思义,一个监听 UI 线程的监视器,它实现了 Runnable 所以是一个线程。

TracePlugin 开始工作后,先要进行监视器的初始化:

UIThreadMonitor # init

public void init(TraceConfig config) {
    if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
        throw new AssertionError("must be init in main thread!");
    }
    this.config = config;
    // 核心一
    choreographer = Choreographer.getInstance();
    // 下面反射得到一堆 Choreographer 的属性
    callbackQueueLock = ReflectUtils.reflectObject(choreographer, "mLock", new Object());
    j
    callbackQueues = ReflectUtils.reflectObject(choreographer, "mCallbackQueues", null);
    if (null != callbackQueues) {
        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;
    MatrixLog.i(TAG, "[UIThreadMonitor] %s %s %s %s %s %s frameIntervalNanos:%s", callbackQueueLock == null, callbackQueues == null,
            addInputQueue == null, addTraversalQueue == null, addAnimationQueue == null, vsyncReceiver == null, frameIntervalNanos);
	...
}

两个核心

  • Choreographer 可以简单理解为帧率监听器;
  • LooperMonitor Looper 监听。

所以 UIThreadMonitor 是依靠上述两个核心来实现的帧率和主线程 Looper 的监听,接下来将逐个分析。

3.2 Choreographer

作用

用来接收硬件发送的 VSync(垂直同步) 信号,一般情况下,硬件每 16ms 发送一次。

原理简析

  1. Choreographer 内部维护了一个 CallbackQueue 数组,从名字上看它是一个队列。
    其实它跟 HashMap 的结构有点像,每一个下标下都是一个链表。它的最大长度是 4,代表了四种不同类型的回调:
  • CALLBACK_INPUT:输入回调;
  • CALLBACK_ANIMATION:动画回调;
  • CALLBACK_TRAVERSAL:View 绘制遍历回调;
  • CALLBACK_COMMIT:提交回调。
  1. 外部可以通过 postCallback() 添加回调,可以自定义类型放置到合适的下标链表处。
    虽然硬件每 16ms 发送一次 VSync 信号,但是想要接收到信号需要主动调用 nativeScheduleVsync() 方法。另外这个方法是一次性的,调用一次只会接收到一次信号。
    所以在 postCallback() 添加回调后调用一次,等到接收到信号处理完逻辑后再次添加回调、请求信号,这样循环下去。
// postCallback 最终调用 postCallbackDelayedInternal
private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
    if (DEBUG_FRAMES) {
        Log.d(TAG, "PostCallback: type=" + callbackType
                + ", action=" + action + ", token=" + token
                + ", delayMillis=" + delayMillis);
    }

    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        // 添加回调
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        if (dueTime <= now) {
        	// 请求接收信号
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}
  1. 那么信号是如何接收的呢,答案是在 DisplayEventReceiver 类:
public abstract class DisplayEventReceiver {
	...
    // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
        onVsync(timestampNanos, builtInDisplayId, frame);
    }
    ...
}

看上面的注释,这个方法是由 native 调用的,其实就是由这个方法接收发送来的 VSync 信号。timestampNanos 是指接收到信号的时间(纳秒)。

该类是抽象类,所以 Choreographer 有一个它的子类 FrameDisplayEventReceiver 负责处理接收到信号之后的逻辑:

private final class FrameDisplayEventReceiver extends DisplayEventReceiver
        implements Runnable {
    @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
		...
		// 收到信号之后记录收到信号的时间
        mTimestampNanos = timestampNanos;
        mFrame = frame;
        Message msg = Message.obtain(mHandler, this);
        msg.setAsynchronous(true);
        // 再通过 Handler 定时执行当前线程
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }

    @Override
    public void run() {
        mHavePendingVsync = false;
        // 每一帧回调
        doFrame(mTimestampNanos, mFrame);
    }
}
  1. doFrame() 方法负责计算丢帧以及处理回调,调用者收到回调之后再次添加并请求接收信号。
void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
       // 当前时间纳秒数
        startNanos = System.nanoTime();
        // frameTimeNanos 是上次接收到 VSync 信号时的纳秒数
        // 经过计算得出是两者时间差
        final long jitterNanos = startNanos - frameTimeNanos;
        // mFrameIntervalNanos 是理想状态下两帧之间间隔的纳秒数
        if (jitterNanos >= mFrameIntervalNanos) { // 如果间隔大于等于纳秒数
        	// 计算得出跳了多少帧
            final long skippedFrames = jitterNanos / mFrameIntervalNanos;
            if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                        + "The application may be doing too much work on its main thread.");
            }
		...
        }
    }

    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
		// 下面四个 doCallbacks 就是遍历回调
        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);
    }
}

额外信息

  • 添加回调的过程中会调用 nativeScheduleVsync() 请求信号,回调是 FrameCallback 类型会调用 doFrame(),是 Runnable 会执行 run()
public void run(long frameTimeNanos) {
    if (token == FRAME_CALLBACK_TOKEN) {
        ((FrameCallback)action).doFrame(frameTimeNanos);
    } else {
        ((Runnable)action).run();
    }
}
  • 每次回调之后会置空队列中的数据,下次添加回调会重新设置;
  • 想要监听每帧信号,可以设置回调监听 doFrame() 方法。
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
    }
});

ViewRootImp 和 Choreographer

Choreographer 使用比较频繁的可能就是 ViewRootImp 了,我们知道 ViewRootImp 主要绘制方法 performTraversals() 就是封装在一个线程里的,而 Choreographer 的回调也可以是线程。

这样一想整个绘制流程也就比较清晰了:
在这里插入图片描述
值得一提的是,ViewRootImp 绘制添加的回调类型是 CALLBACK_TRAVERSAL 表示执行绘制。此外 ViewRootImp 也封装了 CALLBACK_INPUT 线程处理输入事件、CALLBACK_ANIMATION 线程处理动画事件。

另外 Choreographer 回调事件的顺序是 CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL。这么做的原因也好理解,输入事件和动画会改变 View 视图,所以绘制放到最后一次性把 View 展示出来。

3.3 LooperMonitor

作用

监听主线程 Looper,监听什么呢?如何监听?

Printer

Looper 中有一个打印日志的类 Printer,会在处理 Message 的前后输出 log。所以可以通过重新设置 Printer 监听到每一个消息处理前后的信息。

Looper # loop()

public static void loop() {
	 ...
     for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // 开始处理 Message 时打印信息
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        ...
        try {
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...
        // 处理完毕之后打印结束日志
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
		...
    }
}
  • 事件处理前会打印 ">>>>> Dispatching to ...",处理后会打印 "<<<<< Finished to...
  • 如果能够拿到这个 Printer 对象,获取到打印日志的信息,就能实现类似监听事件开始前后的功能了。

如何实现

LooperMonitor 初始化的时候传入了主线程的 Looper。

private static final LooperMonitor mainMonitor = new LooperMonitor();

private LooperMonitor() {
	 // 传入主线程 Looper
     this(Looper.getMainLooper());
 }
 
public LooperMonitor(Looper looper) {
    Objects.requireNonNull(looper);
    this.looper = looper;
    // 设置 Printer 对象
    resetPrinter();
    // 添加空闲 Handler
    addIdleHandler(looper);
}

紧接着通过 resetPrinter() 尝试重新设置 Looper 中的 Printer 对象。

resetPrinter();

private synchronized void resetPrinter() {
    Printer originPrinter = null;
    try {
        if (!isReflectLoggingError) {
        	// 1. 获取 Looper 的 mLogging 对象
            originPrinter = ReflectUtils.get(looper.getClass(), "mLogging", looper);
            // 避免重复设置
            if (originPrinter == printer && null != printer) {
                return;
            }
        }
    } catch (Exception e) {
    	// 如果反射设置失败,标记一下下次不再尝试
        isReflectLoggingError = true;
        Log.e(TAG, "[resetPrinter] %s", e);
    }

    if (null != printer) {
        MatrixLog.w(TAG, "maybe thread:%s printer[%s] was replace other[%s]!",
                looper.getThread().getName(), printer, originPrinter);
    }
    // 2. 重新设置 Looper 的 Printer 对象
    looper.setMessageLogging(printer = new LooperPrinter(originPrinter));
    if (null != originPrinter) {
        MatrixLog.i(TAG, "reset printer, originPrinter[%s] in %s", originPrinter, looper.getThread().getName());
    }
}

需要注意的是,上文传来的 Looper 对象是主线程的,所以以下操作都是针对主线程 Looper 的。

  1. 首先会尝试反射获取 Looper 的 mLogging 对象,这个 mLogging 就是一个 Printer 类型的对象。
  • 可以看到 LooperPrinter 包裹了一个 Looper 中的 Printer 对象,同时自己也是一个 Printer。
  • 通过 setMessageLogging() 替换 Looper 中的 mLogging 对象为 LooperPrinter,等到 Looper 需要打印日志的时候,就会调用 LooperPrinter 的 println() 方法。
class LooperPrinter implements Printer {
    public Printer origin;
    boolean isHasChecked = false;
    boolean isValid = false;

    LooperPrinter(Printer printer) {
        this.origin = printer;
    }

    @Override
    public void println(String x) {
        if (null != origin) {
            origin.println(x);
            if (origin == this) {
                throw new RuntimeException(TAG + " origin == this");
            }
        }

        if (!isHasChecked) { // 日志信息检查
            isValid = x.charAt(0) == '>' || x.charAt(0) == '<';
            isHasChecked = true;
            if (!isValid) {
                MatrixLog.e(TAG, "[println] Printer is inValid! x:%s", x);
            }
        }

        if (isValid) {
            dispatch(x.charAt(0) == '>', x);
        }

    }
}

该方法先是调用原 Printer 对象的 println() 方法打印日志,再进行获取日志前后事件的处理。
这样做既不影响原 Printer 打印日志的逻辑,又可以拦截到 println() 方法(妙啊)。

那么拦截到事件了,怎么区分前后呢?就是通过打印日志的内容。

  1. 回到 Looper 中的 loop() 方法,事件开始处理打印的是 ">>>>> Dispatching to...",结束打印的是 "<<<<< Finished to...",所以可以根据第一个符号来区分是开始还是结束。
dispatch(x.charAt(0) == '>', x);

向右的 > 表示事件开始处理,回调事件开始的方法,反之回调事件结束。

private void dispatch(boolean isBegin, String log) {

    for (LooperDispatchListener listener : listeners) {
        if (listener.isValid()) {
            if (isBegin) {
                if (!listener.isHasDispatchStart) {
                    listener.onDispatchStart(log);
                }
            } else {
                if (listener.isHasDispatchStart) {
                    listener.onDispatchEnd(log);
                }
            }
        // 特殊情况,listener 不可用但是接收到了开始状态
        // 说明 listener 在设置生效前先设置了监听
        // 此时又收到了事件结束的信息,回调结束即可
        } else if (!isBegin && listener.isHasDispatchStart) {
            listener.dispatchEnd();
        }
    }
}

总结

说了那么多其实就一句话:
通过重新设置主线程 Looper 中的 Printer 对象,达到监听主线程每条消息处理前后的信息。

为啥通过 Printer 就能知道呢?
因为Looper 每次处理消息前后会使用 Printer 打印 log

怎么区分前后呢?
处理前打印的是 “>>>” 开头的,处理后是 “>>>” 开头的。所以拿到打印的内容后,">" 开头的是处理前,反之是处理后。

添加空闲 Handler

为避免 LooperPrinter 失效,Matrix 添加了一个空闲时 Handler 每分钟重新设置一次。

addIdleHandler

private synchronized void addIdleHandler(Looper looper) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        looper.getQueue().addIdleHandler(this);
    } else {
        try {
            MessageQueue queue = ReflectUtils.get(looper.getClass(), "mQueue", looper);
            queue.addIdleHandler(this);
        } catch (Exception e) {
            Log.e(TAG, "[removeIdleHandler] %s", e);
        }
    }
}

依旧是通过反射添加 IdleHandler,在主线程空闲时(也就是 Looper 没有 Message 可以拿出来使用时)会调用这些 IdleHandler 的 queueIdle() 方法。

queueIdle

@Override
public boolean queueIdle() {
	// private static final long CHECK_TIME = 60 * 1000L;
    if (SystemClock.uptimeMillis() - lastCheckPrinterTime >= CHECK_TIME) {
        resetPrinter();
        lastCheckPrinterTime = SystemClock.uptimeMillis();
    }
    return true;
}

设置这样的 Handler 不会占用主线程资源,queueIdle() 返回 true 表示会保留该 IdleHandler 供下次空闲时调用,也就是 queueIdle() 会在主线程空闲时不断回调。

这样就实现了不占用资源,又保证了 LooperPrinter 的有效性。

总结

本文主要记录 Matrix 监听主线程的方式:

  • 利用 Choreographer 接收 VSync 信号监听每帧回调;
  • 利用 Looper 中的 Printer 对象获取事件处理前后的信息。

这些功能是性能、卡顿分析插件 TracePlugin 的基础,接下来的文章会分析帧率监听 FrameTracer 的实现,本篇先到这里。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值