Matrix 簡(jiǎn)介(一)

簡(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)圖如下:


11.png

其中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)圖如下:


12.png

這幾個(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)完成。


13.png

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锌奴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子憾股,更是在濱河造成了極大的恐慌鹿蜀,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件服球,死亡現(xiàn)場(chǎng)離奇詭異茴恰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)斩熊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)往枣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事分冈』恚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵雕沉,是天一觀的道長(zhǎng)集乔。 經(jīng)常有香客問(wèn)我,道長(zhǎng)坡椒,這世上最難降的妖魔是什么扰路? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮倔叼,結(jié)果婚禮上汗唱,老公的妹妹穿的比我還像新娘。我一直安慰自己丈攒,他們只是感情好渡嚣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著肥印,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绝葡。 梳的紋絲不亂的頭發(fā)上深碱,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音藏畅,去河邊找鬼敷硅。 笑死,一個(gè)胖子當(dāng)著我的面吹牛愉阎,可吹牛的內(nèi)容都是我干的绞蹦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼榜旦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼幽七!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起溅呢,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤澡屡,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后咐旧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體驶鹉,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年铣墨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了室埋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖姚淆,靈堂內(nèi)的尸體忽然破棺而出孕蝉,到底是詐尸還是另有隱情,我是刑警寧澤肉盹,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布昔驱,位于F島的核電站,受9級(jí)特大地震影響上忍,放射性物質(zhì)發(fā)生泄漏骤肛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一窍蓝、第九天 我趴在偏房一處隱蔽的房頂上張望腋颠。 院中可真熱鬧,春花似錦吓笙、人聲如沸淑玫。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)絮蒿。三九已至,卻和暖如春叁鉴,著一層夾襖步出監(jiān)牢的瞬間土涝,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工幌墓, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留但壮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓常侣,卻偏偏與公主長(zhǎng)得像蜡饵,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子胳施,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容