Matrix 是一款微信研发并日常使用的 APM(Application Performance Manage),当前主要运行在 Android 平台上
只看核心代码即可,关于相关变量我们后文解释:
Matrix.Builder builder = new Matrix.Builder(application); // build matrix
builder.patchListener(new TestPluginListener(this)); // add general pluginListener
DynamicConfigImplDemo dynamicConfig = new DynamicConfigImplDemo(); // dynamic config
// init plugin
IOCanaryPlugin ioCanaryPlugin = new IOCanaryPlugin(new IOConfig.Builder()
.dynamicConfig(dynamicConfig)
.build());
//add to matrix
builder.plugin(ioCanaryPlugin);
//init matrix
Matrix.init(builder.build());
// start plugin
ioCanaryPlugin.start();
说实话,如果大家有看过另外一个APM开源项目Kyson/AndroidGodEye,会很容易把握到整体的设计思路,这里有一篇针对AndroidGodEye的分析:(4.2.46)AndroidGodEye源码整体结构分析
建议在把握整体框架结构的基础上,进行进一步的细节了解。
按照 matrix 现在的描述,它提供的运行时监控能力:
Matrix.with().getPluginByClass(xxx).onDetectIssue(Issue)
这样调用。注意这里是第一种通知方式PluginListener#onReportIssue
public interface IPlugin {
/**
* 用于标识当前的监控,相当于名称索引(也可用classname直接索引)
*/
String getTag();
/**
* 在Matrix对象构建时被调用
*/
void init(Application application, PluginListener pluginListener);
/**
* 对activity前后台转换的感知能力
*/
void onForeground(boolean isForeground);
void start();
void stop();
void destroy();
}
public interface PluginListener {
void onInit(Plugin plugin);
void onStart(Plugin plugin);
void onStop(Plugin plugin);
void onDestroy(Plugin plugin);
void onReportIssue(Issue issue);
}
publishIssue(Issue)
—>IssuePublisher.OnIssueDetectListener接口的onDetectIssue方法—>**最终触发PluginListener#onReportIssue
public class Matrix {
private static final String TAG = "Matrix.Matrix";
/********************************** 单例实现 **********************/
private static volatile Matrix sInstance;
public static Matrix init(Matrix matrix) {
if (matrix == null) {
throw new RuntimeException("Matrix init, Matrix should not be null.");
}
synchronized (Matrix.class) {
if (sInstance == null) {
sInstance = matrix;
} else {
MatrixLog.e(TAG, "Matrix instance is already set. this invoking will be ignored");
}
}
return sInstance;
}
public static boolean isInstalled() {
return sInstance != null;
}
public static Matrix with() {
if (sInstance == null) {
throw new RuntimeException("you must init Matrix sdk first");
}
return sInstance;
}
/**************************** 构造函数 **********************/
private final Application application;
private final HashSet plugins;
private final PluginListener pluginListener;
private Matrix(Application app, PluginListener listener, HashSet plugins) {
this.application = app;
this.pluginListener = listener;
this.plugins = plugins;
for (Plugin plugin : plugins) {
plugin.init(application, pluginListener);
pluginListener.onInit(plugin);
}
}
/**************************** 控制能力 **********************/
public void startAllPlugins() {
for (Plugin plugin : plugins) {
plugin.start();
}
}
public void stopAllPlugins() {
for (Plugin plugin : plugins) {
plugin.stop();
}
}
public void destroyAllPlugins() {
for (Plugin plugin : plugins) {
plugin.destroy();
}
}
/**************************** get | set **********************/
public Plugin getPluginByTag(String tag) {
for (Plugin plugin : plugins) {
if (plugin.getTag().equals(tag)) {
return plugin;
}
}
return null;
}
public T getPluginByClass(Class pluginClass) {
String className = pluginClass.getName();
for (Plugin plugin : plugins) {
if (plugin.getClass().getName().equals(className)) {
return (T) plugin;
}
}
return null;
}
/**************************** 其他 **********************/
public static void setLogIml(MatrixLog.MatrixLogImp imp) {
MatrixLog.setMatrixLogImp(imp);
}
}
我们还是先看下官方给出来的说明文档
阅读上文后,我们可以得知:Matrix 的内存泄露检测基本是源自于 LeakCanary的,(如果对这个开源项目不太了解的建议去看下源码)
我们根据 流程概述:(4.2.39)内存泄漏检测LeakCanary源码分析, 简单做个回顾:
涉及到的对象如下:
我们主要看下它,改进的点:
com.tencent.matrix.resource.watcher.ActivityRefWatcher#RetryableTask
com.tencent.matrix.resource.watcher.ActivityRefWatcher#RetryableTask
mHeapDumpHandler.process(heapDump)
-----CanaryWorkerService.shrinkHprofAndReport(context, result);
com.tencent.matrix.resource.CanaryWorkerService
ActivityLeakAnalyzer
和 DuplicatedBitmapAnalyzer
实现
性能开销:
监测部分,在周期性轮询阶段由于是在后台线程执行的,目前设定的轮询间隔为1min。以通讯录界面和朋友圈界面的帧率作为参考,在接入ResourceCanary后2min内的平均帧率降低了10帧左右(未接入时同样时段内的平均帧率为120帧/秒),开销并不明显。
Dump Hprof阶段的开销则较大。Dump时整个App会卡死约5~15s,但考虑到ResourceCanary模块不会在线上环境启用,因此尚可接受。个人猜想Dump Hprof操作的耗时通过某些hack应该还有优化的空间;对Hprof的预处理阶段耗时约3~20s(取决于机器性能和Hprof的大小),内存开销约为1.5倍Hprof的大小,但由于是在另一个进程中完成的,而且只有在触发了Dump Hprof之后才会执行,因此对App正常运行的干扰也较小。不过改进算法使耗时和内存占用都尽可能少,还是要继续探究的。
IO Canary: 检测文件 IO 问题,包括:
整体思路是:
IOCanaryPlugin 持有探针类IOCanaryCore,以执行相关启动、停止动作
IOCanaryCore
Config
,内部存储相关配置信息CloseGuardHooker
, 用于 文件泄露检测IOCanaryJniBridge
, 用于IO 监控IOCanaryPlugin
,用于发生事件时进行通知OnJniIssuePublishListener
,用于在jni层发现问题时,能够触发该listener,并最终回调mIoCanaryPlugin.onDetectIssue
CloseGuardHooker 文件泄露检测
StrictMode,严苛模式,是Android提供的一种运行时检测机制,用于检测代码运行时的一些不规范的操作,最常见的场景是用于发现主线程的IO操作
StrictMode的实现涉及到以下源码:
- libcore/dalvik/src/main/java/dalvik/system/BlockGuard.java
- libcore/dalvik/src/main/java/dalvik/system/CloseGuard.java
- frameworks/base/core/java/android/os/StrictMode.java
Guard有“守卫”的意思,Block是阻塞的意思,在进行一些耗时操作时,譬如磁盘读写、网络操作,有一个守卫在监测着,它就是BlockGuard,如果这些耗时的操作导致主线程阻塞,BlockGuard就会发出通知; Close对应到可打开的文件,在文件被打开后,也有一个守卫在监测着,它就是CloseGuard,如果没有关闭文件,则CloseGuard就会发出通知
IOCanaryJniBridge
static void onIssuePublish
用于给jni层实现回调通知cpp
virtual void Detect(const IOCanaryEnv& env, const IOInfo& file_io_info, std::vector& issues) = 0;
issues
中issues
中issues
中static void onIssuePublish
OnPublishIssueCallback
,并触发doHook()
在C层hook相关IO的读写操作,并触发 core#io_canary的相关IO事件监听
OnIssuePublish()
回调java层触发通知doUnHook
恢复Io的相关读写操作Trace Canary: 监控界面流畅性、启动耗时、页面切换耗时、慢函数及卡顿等问题
我们看com.tencent.matrix.trace.TracePlugin
,可以观察到四种探针:
我们还是先分析整体结构:
start()
FrameBeat.getInstance().onCreate()
stop()
FrameBeat.getInstance().onDestroy()
LinkedList mFrameListeners
列表Choreographer.FrameCallback#doFrame
(这是一个帧频通知器,往往用于界面卡顿监听),通知mFrameListeners本次帧频时间和上次帧频时间onCreate()
向ApplicationLifeObserver注册自身作为观察者; 前台模式时,将自身作为帧频通知器注册入android系统中onDestroy()
向ApplicationLifeObserver注销自身作为观察者;通知mFrameListeners注销事件;清除mFrameListeners;ApplicationLifeObserver.IObserver
,从而具备监听Activity生命周期的相关能力
Hacker.hackSysHandlerCallback()
开始hook相关生命周期函数,其中HackCallback
实现了对 Activity和Application的创建感知LinkedList sListeners = new LinkedList<>()
列表onCreate()
向ApplicationLifeObserver注册自身作为观察者; 取消主线程的releaseBufferMsg;释放一次延时循环时间线程的updateMsg(即开启周期循环任务)onDestroy()
向ApplicationLifeObserver注销自身作为观察者; 取消相关Msg;清空监听器i()
与o()
产生数据
i()
,在每个方法结尾调用方法o()
,记录方法名称和o-i的时间差。这两个方法的最后会执行mergeData方法matrix-gradle-plugin
库里MethodTracer
这个类,在于使用ASM进行字节码操作,常用于切面编程Hacker
,内部使用自定义的HackCallback,hook了"android.app.ActivityThread"
, 这个类大家应该很眼熟,Activity相关生命周期等本地进程操作都是由这个类触发HackCallback
通过动态代理handleMessage(Message msg)
方法,实现对
Hacker#isEnterAnimationComplete
静态存储HackCallback#isCreated
静态存储onTimeExpire()
doFrame(long lastFrameNanos, long frameNanos)
在 Choreographer.FrameCallback
中被触发,通知两次帧频的时间cancelFrame()
doFrameSync
同步通知getHandler
+ doFrameAsync
异步通知onActivityEntered
onApplicationCreated
pushFullBuffer
ApplicationLifeObserver.IObserver
,从而具备感知Activity生命周期的能力IFrameBeatListener
,从而具备感知帧频通知的能力IMethodBeatListener
, 从而具备感知相关系统函数的能力onCreate()
,将上文提及的 IObserver\IFrameBeatListener\IMethodBeatListener
注册到对应的执行器中onDestroy()
,将上文提及的listeners全部注销掉sendReport
内部调用TracePlugin的onDetectIssue()LinkedList mDoFrameListenerList = new LinkedList<>()
事件监听器doFrame(xxx,xxx)
, 在认为两次帧频超过阈值16.67ms时,通知mDoFrameListenerListViewTreeObserver.OnDrawListener
,并在Activity页面切换onChange生命周期期间注册自身,实现 isDrawing期间不检测由此,我们可以得出以下事件链:
TracePlugin#init
初始化时构建单例ApplicationLifeObserver监听相关Activity生命周期TracePlugin#init
初始化时构建FrameTracer、StartUpTracer、FPSTracer、EvilMethodTracer探针实例
BaseTracer
,由此会构建其内部静态对象实例HashMap, BaseTracer> sTracerMap
和 MethodBeat sMethodBeat = new MethodBeat()
MethodBeat sMethodBeat = new MethodBeat()
会执行MethodBeat
其内部的静态块
Hacker.hackSysHandlerCallback()
其内部使用自定义的HackCallback,hook了"android.app.ActivityThread"
, 这个类大家应该很眼熟,Activity相关生命周期等本地进程操作都是由这个类触发HackCallback
实现了对 Activity和Application的创建感知TracePlugin#start()
在主进程中调用 FrameBeat.getInstance().onCreate()
onCreate
向ApplicationLifeObserver注册自身FrameBeat作为观察者; 前台模式时,将自身作为帧频通知器注册入android系统中LinkedList mFrameListeners
监听列表TracePlugin#start()
中调用mFrameTracer.onCreate();
onCreate
其实现在BaseTracer中,将自身作为 IObserver\IFrameBeatListener\IMethodBeatListener
注册到对应的执行器中,从而具备感知Activity生命周期的能力、感知帧频通知的能力、感知相关系统函数的能力doFrame(xxx,xxx)
, 在认为两次帧频超过阈值16.67ms时,通知mDoFrameListenerListViewTreeObserver.OnDrawListener
,并在Activity页面切换onChange生命周期期间注册自身,实现 isDrawing期间不检测TracePlugin#start()
中调用mEvilMethodTracer.onCreate();
onCreate
其实现在BaseTracer中,将自身作为 IObserver\IFrameBeatListener\IMethodBeatListener
注册到对应的执行器中,从而具备感知Activity生命周期的能力、感知帧频通知的能力、感知相关系统函数的能力onCreate
构建相关线程对象、周期循环任务对象等doFrame(xxx,xxx)
, 取消上次延时任务,并开启一次延时任务,这也就是说 如果一个函数在一次帧频间隔16.67ms期间完成,那么就不会触发延时任务;当触发延时任务时,其实也意味着主线程出现了耗时操作,并注明Type.ANR, anr超时场景LazyScheduler.ILazyTask
实现延时任务,即打印当前堆栈信息或其他分析(调用evilMethodTracer.handleBuffer
并注明Type.ANR)doFrame(xxx,xxx)
中会比对两次doFrame的时间,如果超过阈值则认为出现了NORMAL耗时evilMethodTracer.handleBuffer
并注明Type.STARTUPonActivityCreated
,记录界面进入时间onActivityEntered
,比对进入时间,并用evilMethodTracer.handleBuffer
并注明Type.EnterTracePlugin#start()
中调用StartUpTracer.onCreate();
onCreate
其实现在BaseTracer中,将自身作为 IObserver\IFrameBeatListener\IMethodBeatListener
注册到对应的执行器中,从而具备感知Activity生命周期的能力、感知帧频通知的能力、感知相关系统函数的能力onActivityCreated
,监听第一个FirstActivityNameonActivityEntered
,进行启动慢函数检测和启动监控上报TracePlugin#start()
中调用FPSTracer.onCreate();
统计Acitivty的帧频情况SQLiteLint在APP运行时进行检测,而且大部分检测算法与数据量无关即不依赖线上的数据状态。只要你触发了某条sql语句的执行,SQLiteLint就会帮助你review这条语句是否写得有问题。而这在开发、测试或者灰度阶段就可以进行。
简单而言,sqliteLint主要是在运行时通过切入数据库框架的sql语句解析层,通过织入相关的Checker,根据sql语句结构和数据库结构来分析sql语句的性能,给出不同等级的建议
源码涉及的较多,本文暂不分析,留待下一章节
Matrix-ApkChecker以一个jar包的形式提供使用,通过命令行执行 java -jar ApkChecker.jar 即可运行。
可以参看官方的文档:Matrix ApkChecker