文章目录
前言
接上文,我们首先来看 TracePlugin 卡顿分析的实现。TracePlugin 主要靠监视主线程的各种状态来分析是否卡顿,接下来我们开始吧。
前文传送门:
一、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);
}
}
上述代码有三块重要内容:
- UIThreadMonitor:一个监听 UI 线程的监视器,监听什么内容?怎么监听?后面会给出答案。
- 追踪器开始工作:TracePlugin 包含的四大追踪器,是实际的工作者。
- 线程检查:监视器和追踪器等需要在主线程进行初始化,所以分为两种情况:
当前在主线程下:直接调用 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 发送一次。
原理简析
- Choreographer 内部维护了一个 CallbackQueue 数组,从名字上看它是一个队列。
其实它跟 HashMap 的结构有点像,每一个下标下都是一个链表。它的最大长度是 4,代表了四种不同类型的回调:
- CALLBACK_INPUT:输入回调;
- CALLBACK_ANIMATION:动画回调;
- CALLBACK_TRAVERSAL:View 绘制遍历回调;
- CALLBACK_COMMIT:提交回调。
- 外部可以通过
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);
}
}
}
- 那么信号是如何接收的呢,答案是在 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);
}
}
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 的。
- 首先会尝试反射获取 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()
方法(妙啊)。
那么拦截到事件了,怎么区分前后呢?就是通过打印日志的内容。
- 回到 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 的实现,本篇先到这里。