微信的APM Matrix开源有段时间了,我在看了源码后发现,其作为国民第一应用出品的APM,确实是精益求精,业内领先的。TraceCanary 是其中的一个工具,主要用来监控方法耗时,启动耗时, ANR、掉帧情况等。其分两部分,一部分是以插件的形式在编译期在每个方法执行前后插桩, 用于统计方法耗时;另一部分是运行时的sdk,真正的耗时、卡顿、ANR监控逻辑所在。
插桩插件
插桩主要是为了统计方法耗时。gradle插件的代码一般都有个plugin入口,很好找:
1 | /** |
可以看到走进了MatrixTraceTransform的inject方法。这里还有个bonus,RemoveUnusedResourcesTask,顾名思义,删除无用资源的task,会在assemble之前触发,内部具体实现后续分析APK Checker的时候再细说。接着看MatrixTraceTransform的inject方法。
1 | public static void inject(Project project, MatrixTraceExtension extension, VariantScope variantScope) { |
可以看到就是通过反射加代理替换transformClassesWithDexForXXX或者新版本gradle的transformClassesWithDexBuilderForXXX task里的transform,来实现在transformClassesWithDexForXXX/transformClassesWithDexBuilderForXXX执行之前执行matrix trace的插桩逻辑。为什么要选择这个时候呢?因为这是打dex的task,在这之前proguard已经执行完了,避免因为插桩代码导致本可以内联的方法无法内联导致dex方法数增多。接着来看transform里的逻辑:
1 | private void doTransform(TransformInvocation transformInvocation) throws ExecutionException, InterruptedException { |
三步:先收集dir/jar输入;扫描输入中的method 存在列表里;执行插桩。比较常规的插桩套路,插桩的代码在TraceMethodAdapter
, 就不贴出来了。主要做了:
- 方法执行前插入
AppMethodBeat.i(id)
- 执行后插入
AppMethodBeat.o(id)
- 对于activity的子类:
- 如果没有重写
onWindowFocusChanged
方法,则插入重写的onWindowFocusChanged
方法,方法内调super后再调AppMethodBeat.at(Activity activity, boolean isFocus)
方法打点 - 如果有重写
onWindowFocusChanged
方法,则在onWindowFocusChanged
方法后调AppMethodBeat.at(Activity activity, boolean isFocus)
方法打点
- 如果没有重写
可见AppMethodBeat
是方法耗时打点的入口。
runtime sdk
Matrix集成的官方示例如下:
1 | public class MatrixApplication extends Application { |
Trace Canary / Resource Canary / IO Canary / SQLite Lint
这四个功能都是独立的plugin,Application初始化的时候在Matrix Builder中设置对应的plugin即可集成。在Matrix实例构建的时候会调每个plugin的init方法,来看下TracePlugin
的init方法:
1 |
|
创建了四个tracer实例,然后调用了:AppMethodBeat单例的onStart()方法、UIThreadMonitor单例的onStart()方法以及每个tracer的onStartTrace方法。
- anrTracer: 主要进行ANR的监控上报
- frameTracer:掉帧数和帧率的监控上报
- evilMethodTracer: 方法耗时的监控上报
- startupTracer: 启动速度的监控上报
他们都继承自Tracer
:
1 | public abstract class Tracer extends LooperObserver implements ITracer { |
Tracer
继承自LooperObserver
,这也是Matrix自己定义的类。这里需要先介绍下Matrix监控主线程卡顿的原理,和BlockCanary一样: Looper的loop方法中会循环取MessageQueue里的Message执行,执行前后分别会打印一次特殊的log,通过设置主线程looper中的printer,拦截到特殊的log后就能知道主线程dispatch某个message的耗时了。
1 | /** |
Matrix按照这个思路在dispatch Message的开始和结束通过观察者模式通知LooperObserver
进行耗时统计,EvilMethodTracer、FrameTracer和AnrTracer这仨observer都注册到了UIThreadMonitor(observable)上,在主线程message dispatch的前后LooperMonitor会通知UIThreadMonitor,UIThreadMonitor通知observer进行打点。
EvilMethodTracer
EvilMethodTracer以TraceConfig中配置的方法耗时阈值(默认是700毫秒)来判断某个message dispatch时间是否过长,超过阈值则把进程信息,CPU使用率,调用栈(包含具体方法耗时),这帧input、animation、traversal耗时等信息上报。
1 |
|
为了减少频繁调用System.currentTimeMillis()
带来的性能开销,Matrix启动了一个后台线程,每5毫秒更新一次时间戳,AppMethodBeat打点的时候直接用计算好的时间戳。
可能你会问,这帧input、animation、traversal耗时是怎么算出来的? Choreographer并没有给我们这样的接口。这就得说说Choreographer的源码了。
1 | void doFrame(long frameTimeNanos, int frame) { |
Choreographer负责接收和处理所有界面更新消息,在收到VSYNC信号的时候统一处理,按顺序执行CALLBACK_INPUT(input事件)、CALLBACK_ANIMATION(动画)、CALLBACK_TRAVERSAL(layout、draw)、CALLBACK_COMMIT(ActivityThread post的,执行scheduleTrimMemory)这几种callback,这几种callback分别在各自的CallbackQueue里存着,只要在对应的CallbackQueue的头插入一个callback,就能统计出input、animation、traversal这几个阶段的耗时。
具体代码在UIThreadMonitor中,首先其初始化的时候会反射拿到Choreographer里的CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL这三种CallbackQueue,接着反射拿到他们的addCallbackLocked
方法以备调用。
1 | private Object callbackQueueLock; |
然后在其创建时候的start和每次message的dispatchEnd方法中会调用addFrameCallback(CALLBACK_INPUT, this, true);
给input类型的CallbackQueue头上插入一个callback(就是UIThreadMonitor自己)
1 | private synchronized void addFrameCallback(int type, Runnable callback, boolean isAddHeader) { |
执行到UIThreadMonitor
这个input callback的时候会进行打点,然后给CALLBACK_ANIMATION的CallbackQueue头插入callback,这个callback的执行就表示input执行结束,animation执行开始,从而统计出处理input阶段耗时。CALLBACK_TRAVERSAL同,不赘述。
1 |
|
到这里其实耗时方法统计的原理已经清楚了。
AnrTracer
ANR耗时在方法耗时的基础上进行上报,在开始dispatch的时候post一个5秒后执行的runnable,然后在结束的时候cancel这个runnable。runnable执行的时候就说明发生了ANR,进行数据上报。
1 |
|
FrameTracer
FrameTracer主要统计的是掉帧数和帧率,掉帧数有时候相比帧率能更好的反映视觉上的卡顿,比如30帧每秒可能不高 但是只要帧数稳定,人眼感觉到的依然是流畅的画面,而一旦某帧执行时间过长,那卡顿感就比较明显了。掉帧数通过某帧耗时除以16.666666秒来计算。一帧的耗时就是通过上面所说的msg dispatch前后时间计算的,然后doFrame当执行到INPUT类型的callback的时候就标记这是更新界面的message。
StartupTracer
主要统计应用冷启、热启、首屏、单个activity启动等阶段耗时:
1 | * firstMethod.i LAUNCH_ACTIVITY onWindowFocusChange LAUNCH_ACTIVITY onWindowFocusChange |
启动和首屏出现耗时的起点都是应用中的第一个方法开始执行的时间,通过前面的插桩已经可以拿到了。因为handler处理message的优先顺序是:
msg自身的callback(不向下传递)
> handler自身的mCallback(如果handleMessage返回true不向下传递,false继续传给handler的handleMessage)
> handler的handleMessage()
所以通过反射加代理替换掉ActivityThread的mH的mCallback,就可以进行对activity和application启动的打点。
在Android 9.0以前 activity的启动通过发送一个what是LAUNCH_ACTIVITY的msg给mH,mH收到后调用ActivityThread的handleLaunchActivity
从而开始启动activity; 9.0及以后去除了activity生命周期相关的msg.what,统一使用what
是EXECUTE_TRANSACTION
,obj是ClientTransaction
实例的msg来通知mH执行生命周期。ClientTransaction
其callbacks是ClientTransactionItem
list,launch activity的时候其callback是LaunchActivityItem
(继承自ClientTransactionItem), 可据此判断调起activity。
application创建结束的时间计算方法是:只要activity开始launch或者service开始create或者broadcastReceiver开始创建就算application创建结束。
1 | public class ActivityThreadHacker { |
单个activity从启动到展示UI的耗时是从launch activity到onWindowFocusChanged
这段时间,onWindowFocusChanged(true)
表示activity已经获取到焦点,可以和用户进行交互。
到这里Matrix的TraceCanary部分就分析完了,可以看出和BlockCanCary是大体一样的原理,但微信就是做的细致和完美,包括细化每帧内几部分的执行时间、掉帧数的计算、插桩性能、上报数据大小等方面都超出竞品。
其他的APK Checker
、Resource Canary
、SQLite Lint
、IO Canary
等工具后续一一来分析。