簡(jiǎn)介
Matrix 是一款微信研發(fā)并日常使用的 APM (Application Performance Manage) 贪婉,當(dāng)前主要運(yùn)行在 Android 平臺(tái)上抡诞。Matrix 的目標(biāo)是建立統(tǒng)一的應(yīng)用性能接入框架锋华,通過(guò)對(duì)各種性能監(jiān)控方案快速集成旧巾,對(duì)性能監(jiān)控項(xiàng)的異常數(shù)據(jù)進(jìn)行采集和分析坏匪,輸出相應(yīng)問(wèn)題的分析圃验、定位與優(yōu)化建議,從而幫助開(kāi)發(fā)者開(kāi)發(fā)出更高質(zhì)量的應(yīng)用韩玩。 開(kāi)源地址:https://github.com/tencent/matrix
功能
Resource Canary:
1 Activity 泄漏
2 Bitmap 冗余
Trace Canary
1 界面流暢性
2 啟動(dòng)耗時(shí)
3 頁(yè)面切換耗時(shí)
4 慢函數(shù)
5 卡頓
SQLite Lint:
1 按官方最佳實(shí)踐自動(dòng)化檢測(cè) SQLite 語(yǔ)句的使用質(zhì)量
IO Canary:
1 檢測(cè)文件 IO 問(wèn)題
2 文件 IO 監(jiān)控
3 Closeable Leak 監(jiān)控
接入示例
github上的matrix項(xiàng)目下有sample.
TraceConfig traceConfig = new TraceConfig.Builder()
.dynamicConfig(dynamicConfig)
.enableFPS(fpsEnable)
.enableEvilMethodTrace(traceEnable)
.enableAnrTrace(traceEnable)
.enableStartup(traceEnable)
.splashActivities("sample.tencent.matrix.SplashActivity;")
.isDebug(true)
.isDevEnv(true)
.build();
TracePlugin tracePlugin = (new TracePlugin(traceConfig));
builder.plugin(tracePlugin);
if (matrixEnable) {
//resource
builder.plugin(new ResourcePlugin(new ResourceConfig.Builder()
.dynamicConfig(dynamicConfig)
.setDumpHprof(false)
.setDetectDebuger(true) //only set true when in sample, not in your app
.build()));
ResourcePlugin.activityLeakFixer(this);
//io
IOCanaryPlugin ioCanaryPlugin = new IOCanaryPlugin(new IOConfig.Builder()
.dynamicConfig(dynamicConfig)
.build());
builder.plugin(ioCanaryPlugin);
// prevent api 19 UnsatisfiedLinkError
//sqlite
SQLiteLintConfig config = initSQLiteLintConfig();
SQLiteLintPlugin sqLiteLintPlugin = new SQLiteLintPlugin(config);
builder.plugin(sqLiteLintPlugin);
// thread module is not available now,
// ThreadWatcher threadWatcher = new ThreadWatcher(new ThreadConfig.Builder().dynamicConfig(dynamicConfig).build());
// builder.plugin(threadWatcher);
}
Matrix.init(builder.build());
//start only startup tracer, close other tracer.
tracePlugin.start();
Matrix.with().getPluginByClass(SQLiteLintPlugin.class).start();
不過(guò)需要注意的是0.6.1 版本的線程相關(guān)模塊暫未開(kāi)源垒玲,所以運(yùn)行時(shí)需要將相關(guān)diamante注釋。注釋部分包括app的build gradle中的dependencies找颓。
dependencies {
......
implementation group: "com.tencent.matrix", name: "matrix-resource-canary-common", version: MATRIX_VERSION, changing: true
implementation group: "com.tencent.matrix", name: "matrix-io-canary", version: MATRIX_VERSION, changing: true
// implementation group: "com.tencent.matrix", name: "matrix-thread-canary", version: MATRIX_VERSION, changing: true //未開(kāi)源合愈,需要注釋
implementation group: "com.tencent.matrix", name: "matrix-sqlite-lint-android-sdk", version: MATRIX_VERSION, changing: true
......
}
另外一部分是MatrixApplication的onCreate函數(shù)中的部分代碼:
@Override
public void onCreate() {
Matrix.init(builder.build());
//start only startup tracer, close other tracer.
tracePlugin.start();
Matrix.with().getPluginByClass(SQLiteLintPlugin.class).start();
// Matrix.with().getPluginByClass(ThreadWatcher.class).start(); // 注釋
MatrixLog.i("Matrix.HackCallback", "end:%s", System.currentTimeMillis());
此外如果使用的電腦是windows。則需要將app的build.gradle中的apksignerPath改成批處理击狮。改動(dòng)如下
matrix {
trace {
enable = true
baseMethodMapFile = "${project.projectDir}/matrixTrace/methodMapping.txt"
blackListFile = "${project.projectDir}/matrixTrace/blackMethodList.txt"
}
removeUnusedResources {
enable true
variant = "debug"
needSign true
shrinkArsc true
// apksignerPath = "${android.getSdkDirectory().getAbsolutePath()}/build-tools/${android.getBuildToolsVersion()}/apksigner" // window需要改成批處理文件
apksignerPath = "${android.getSdkDirectory().getAbsolutePath()}/build-tools/${android.getBuildToolsVersion()}/apksigner.bat"
unusedResources = project.ext.unusedResourcesSet
ignoreResources = ["R.id.*", "R.bool.*"]
}
}
至此sample項(xiàng)目就可以運(yùn)行起來(lái)了佛析。
初始化
下面看下matrix的init 函數(shù),代碼如下:
// 創(chuàng)建builder
Matrix.Builder builder = new Matrix.Builder(this);
// 綁定監(jiān)聽(tīng)器
builder.patchListener(new TestPluginListener(this));
// 創(chuàng)建plugin
TracePlugin tracePlugin = (new TracePlugin(traceConfig));
// 綁定plugin
builder.plugin(tracePlugin);
// 完成初始化
Matrix.init(builder.build());
最終調(diào)用build方法觸發(fā)了matrix的構(gòu)造函數(shù)彪蓬。
public Matrix build() {
if (pluginListener == null) {
// 創(chuàng)建一個(gè)默認(rèn)的監(jiān)聽(tīng)器
pluginListener = new DefaultPluginListener(application);
}
return new Matrix(application, pluginListener, plugins);
}
private Matrix(Application app, PluginListener listener, HashSet<Plugin> plugins) {
this.application = app;
this.pluginListener = listener;
this.plugins = plugins;
AppActiveMatrixDelegate.INSTANCE.init(application);// 1 注冊(cè)監(jiān)聽(tīng)
for (Plugin plugin : plugins) {
plugin.init(application, pluginListener);//2 初始化plugin
pluginListener.onInit(plugin);
}
}
首先在AppActiveMatrixDelegate 的init方法中注冊(cè)了ActivityLifecycleCallbacks和ComponentCallbacks2兩個(gè)監(jiān)聽(tīng)寸莫,這樣就能感知到activity的生命周期和內(nèi)存緊缺狀態(tài)。然后遍歷matrix的所有插件档冬,并對(duì)插件調(diào)用init方法進(jìn)行初始化膘茎,最終通知pluginListener的init方法。其中PluginListener的代碼如下:
public interface PluginListener {
void onInit(Plugin plugin);
void onStart(Plugin plugin);
void onStop(Plugin plugin);
void onDestroy(Plugin plugin);
void onReportIssue(Issue issue);
}
PluginListener 能獲取到plugin的狀態(tài)酷誓,也能收到issue披坏。在sample里,是在收到issue的時(shí)候彈出一個(gè)IssuesList展示issue的具體信息盐数。默認(rèn)的DefaultPluginListener沒(méi)有對(duì)issue進(jìn)行處理棒拂,只是打印日志。實(shí)際上,我們接入matrix帚屉,需要定義自己的PluginListener 谜诫,對(duì)issue進(jìn)行進(jìn)一步的處理,比如序列化到本地或者壓縮或者上傳服務(wù)端等攻旦。自己實(shí)現(xiàn)的onReportIssue方法將決定我們對(duì)issue的處理喻旷。
類(lèi)圖
從sample的接入中看到有TracePlugin,IOCanaryPlugin牢屋,ResourcePlugin和SQLiteLintPlugin四個(gè)pugin. 這四個(gè)plugin都實(shí)現(xiàn)了plugin接口掰邢。類(lèi)圖如下:
其中IPlugin接口如下:
public interface IPlugin {
Application getApplication();
void init(Application application, PluginListener pluginListener);
void start();
void stop();
void destroy();
String getTag();
void onForeground(boolean isForeground);
}
主要定義了插件的生命周期,Plugin提供了默認(rèn)的實(shí)現(xiàn)伟阔。
TracePlugin
它是trace管理器,期內(nèi)部定義了四個(gè)trace掰伸。
- AnrTracer ANR監(jiān)測(cè)
- EvilMethodTracer 耗時(shí)函數(shù)監(jiān)測(cè)
- FrameTracer 幀率監(jiān)測(cè)
- StartupTracer 啟動(dòng)耗時(shí)
類(lèi)圖如下:
這幾個(gè)trace都繼承自Trace.這是抽象類(lèi)皱炉,但不含抽象方法。已經(jīng)對(duì)繼承LooperObserver和ITracer來(lái)的方法做了默認(rèn)實(shí)現(xiàn)狮鸭。我們先看下Trace繼承的父類(lèi)和實(shí)現(xiàn)的接口合搅。
1.LooperObserver
它是個(gè)抽象類(lèi),內(nèi)部定義了三個(gè)重要的方法dispatchBegin歧蕉、doFrame灾部,dispatchEnd。但都是空實(shí)現(xiàn)惯退,這個(gè)三個(gè)方法和監(jiān)聽(tīng)主線程Handler的消息處理有關(guān)赌髓。當(dāng)主線程開(kāi)始處理一條消息之前那會(huì)回調(diào)dispatchBegin,消息處理結(jié)束會(huì)調(diào)用doFrame催跪,然后再調(diào)用dispatchEnd锁蠕。之所以這么做是因?yàn)閷?duì)于卡頓的檢測(cè)通常有兩種方式:
(1) 監(jiān)聽(tīng)主線程Handler的消息處理,通過(guò)給looper設(shè)置一個(gè)logger對(duì)象懊蒸。系統(tǒng)looper分發(fā)處理前后會(huì)通過(guò)logger對(duì)象打印日志荣倾。這樣looger就可以很方便的拿到消息執(zhí)行的前后時(shí)間點(diǎn),根據(jù)二者的事件差可以做很多卡頓的分析骑丸,比如blockCanary就是采用這種方式檢測(cè)的卡頓舌仍。
(2) 通過(guò)Choreographer開(kāi)放API ,上層可設(shè)置FrameCallback監(jiān)聽(tīng)通危,從而獲得每一幀繪制完成的onFrame回調(diào)铸豁。常用的幀率檢測(cè)工具就是通過(guò)分析兩幀之間的事件差完成FPS的計(jì)算。
2 ITracer
它是一個(gè)接口黄鳍,繼承了IAppForeground接口推姻,因此其實(shí)現(xiàn)類(lèi)必須實(shí)現(xiàn)四個(gè)四個(gè)抽象方法:onForeground,isAlive框沟,onStartTrace藏古,onCloseTrace增炭。第一個(gè)方法是監(jiān)聽(tīng)activity的前后臺(tái)狀態(tài),因此trace能感知activity前后臺(tái)狀態(tài)變化拧晕,可以用來(lái)做activity的啟動(dòng)分析隙姿。另外三個(gè)方法是用來(lái)描述trace的生命周期,由TracePlugin來(lái)統(tǒng)一管理厂捞。而Trace中這四個(gè)防范都是空實(shí)現(xiàn)输玷,具體實(shí)現(xiàn)都有trace的子類(lèi)完成。
3 FrameTracer
FrameTracer 重寫(xiě)doFrame靡馁,并將時(shí)間戳欲鹏,掉幀情況,頁(yè)面名稱(chēng)等信息發(fā)送給IDoFrameListener臭墨。
private void notifyListener(final String visibleScene, final long frameCostMs) {
long start = System.currentTimeMillis();
try {
synchronized (listeners) {
for (final IDoFrameListener listener : listeners) {
final int dropFrame = (int) (frameCostMs / frameIntervalMs);
listener.doFrameSync(visibleScene, frameCostMs, dropFrame);
if (null != listener.getHandler()) {
listener.getHandler().post(new Runnable() {
@Override
public void run() {
listener.doFrameAsync(visibleScene, frameCostMs, dropFrame);
}
});
}
}
}
} finally {
}
}
4 EvilMethodTracer
FrameTracer 用來(lái)監(jiān)聽(tīng)函數(shù)的執(zhí)行時(shí)間,待補(bǔ)充完善
5 AnrTracer
AnrTracer主要用來(lái)判斷anr的發(fā)生赔嚎,原理是讓handler中延遲發(fā)送一個(gè)runnable,然后過(guò)一段時(shí)間嘗試取消胧弛,如果未能取消尤误,說(shuō)明執(zhí)行時(shí)間超過(guò)了等待時(shí)間,可以認(rèn)為發(fā)生了anr.如果取消成功结缚,說(shuō)明執(zhí)行的時(shí)間小于等待時(shí)間损晤。
@Override
public void dispatchBegin(long beginMs, long cpuBeginMs, long token) {
super.dispatchBegin(beginMs, cpuBeginMs, token);
anrTask = new AnrHandleTask(AppMethodBeat.getInstance().maskIndex("AnrTracer#dispatchBegin"), token);
// 延遲發(fā)送,延遲的時(shí)間為Constants.DEFAULT_ANR红竭。這個(gè)即為判斷anr的閾值
anrHandler.postDelayed(anrTask, Constants.DEFAULT_ANR - (SystemClock.uptimeMillis() - token));
}
@Override
public void dispatchEnd(long beginMs, long cpuBeginMs, long endMs, long cpuEndMs, long token, boolean isBelongFrame) {
super.dispatchEnd(beginMs, cpuBeginMs, endMs, cpuEndMs, token, isBelongFrame);
if (null != anrTask) {
anrTask.getBeginRecord().release();
anrHandler.removeCallbacks(anrTask);// 取消判斷anr的task
}
}
6 StartupTracer
StartupTracer用來(lái)判斷啟動(dòng)的時(shí)間尤勋,包括冷啟動(dòng)和熱啟動(dòng)。StartupTracer實(shí)現(xiàn)了ActivityLifecycleCallbacks的相關(guān)方法茵宪,主要通過(guò)生命周期回調(diào)的方式實(shí)現(xiàn)啟動(dòng)時(shí)間的計(jì)算斥黑。不過(guò)啟動(dòng)事件是通過(guò)hook系統(tǒng)handler的方式實(shí)現(xiàn)的。
public static void hackSysHandlerCallback() {
try {
sApplicationCreateBeginTime = SystemClock.uptimeMillis();
sApplicationCreateBeginMethodIndex = AppMethodBeat.getInstance().maskIndex("ApplicationCreateBeginMethodIndex");
Class<?> forName = Class.forName("android.app.ActivityThread");
Field field = forName.getDeclaredField("sCurrentActivityThread");
field.setAccessible(true);
Object activityThreadValue = field.get(forName);
Field mH = forName.getDeclaredField("mH");
mH.setAccessible(true);
Object handler = mH.get(activityThreadValue);
Class<?> handlerClass = handler.getClass().getSuperclass();
Field callbackField = handlerClass.getDeclaredField("mCallback");
callbackField.setAccessible(true);
Handler.Callback originalCallback = (Handler.Callback) callbackField.get(handler);
HackCallback callback = new HackCallback(originalCallback);
callbackField.set(handler, callback);
MatrixLog.i(TAG, "hook system handler completed. start:%s SDK_INT:%s", sApplicationCreateBeginTime, Build.VERSION.SDK_INT);
} catch (Exception e) {
MatrixLog.e(TAG, "hook system handler err! %s", e.getCause().toString());
}
}
主要是將mH對(duì)象內(nèi)部原有的Handler.Callback替換成自定義的HackCallback眉厨。
@Override
public boolean handleMessage(Message msg) {
if (!AppMethodBeat.isRealTrace()) {
return null != mOriginalCallback && mOriginalCallback.handleMessage(msg);
}
boolean isLaunchActivity = isLaunchActivity(msg);
if (hasPrint > 0) {
MatrixLog.i(TAG, "[handleMessage] msg.what:%s begin:%s isLaunchActivity:%s", msg.what, SystemClock.uptimeMillis(), isLaunchActivity);
hasPrint--;
}
if (isLaunchActivity) {
ActivityThreadHacker.sLastLaunchActivityTime = SystemClock.uptimeMillis();
ActivityThreadHacker.sLastLaunchActivityMethodIndex = AppMethodBeat.getInstance().maskIndex("LastLaunchActivityMethodIndex");
}
if (!isCreated) {
if (isLaunchActivity || msg.what == CREATE_SERVICE || msg.what == RECEIVER) { // todo for provider
ActivityThreadHacker.sApplicationCreateEndTime = SystemClock.uptimeMillis();
ActivityThreadHacker.sApplicationCreateScene = msg.what;
isCreated = true;
}
}
return null != mOriginalCallback && mOriginalCallback.handleMessage(msg);
}
參考文獻(xiàn)
感謝微信的開(kāi)源和先行者的無(wú)私分享
matix官方介紹
Matrix系列文章(一) 卡頓分析工具之Trace Canary
(4.2.49)微信APM:Matrix源碼淺析
深入了解APM講義V3