深刻了解APM講義V3

APM簡要介紹

APM 全稱 Application Performance Management & Monitoring (應用性能管理/監控)html

性能問題是致使 App 用戶流失的罪魁禍首之一,若是用戶在使用咱們 App 的時候遇到諸如頁面卡頓、響應速度慢、發熱嚴重、流量電量消耗大等問題的時候,極可能就會卸載掉咱們的 App。這也是咱們在目前工做中面臨的巨大挑戰之一,尤爲是低端機型。java

商業化的APM平臺:著名的 NewRelic,還有國內的聽雲、OneAPM 、阿里百川-碼力APM的SDK、百度的APM收費產品等等。linux

報名連接android

若是對APM感興趣,想要進一步跟着我一塊兒學下去,歡迎你們點擊APM訓練營免費參與到學習當中來,突破本身的技術瓶頸,一塊兒加油!c++

APM工做方式

  1. 首先在客戶端(Android、iOS、Web等)採集數據;
  2. 接着將採集到的數據整理上報到服務器(多種方式 json、xml,上傳策略等等);
  3. 服務器接收到數據後建模、存儲、挖掘分析,讓後將數據進行可視化展現(spark+flink),供用戶使用

image-20190914180724597

那麼移動端須要作的事情就是:git

  • 雙端統一原則(技術選型 NDK c++)
  • 數據採集 (採集指標、細化等等)
  • 數據存儲(寫文件?mmap、fileIO流)
  • 數據上報(上報策略、上報方式)

那咱們到底應該怎麼作?必定要學會看開源的東西。讓咱們先看看大廠的開源怎麼作的?咱們在本身造輪子,完成本身的APM採集框架。github

目前核心開源APM框架產品

你會發現自定義Gradle插件技術、ASM技術、打包流程Hook、Android打包流程等。那思考一下,爲何你們作的主要的流程都是同樣的,不同的是具體的實現細節,好比如何插樁採集到頁面幀率、流量、耗電量、GC log等等。面試

ArgusAPM性能監控平臺介紹&SDK開源-卜雲濤.pdf編程

咱們先簡單來看下在matrix中,如何利用Java Hook和 Native Hook完成IO 磁盤性能的監控?json

image-20190914182546349

Java Hook的hook點是系統類CloseGuard,hook的方式是使用動態代理。

github.com/Tencent/mat…

private boolean tryHook() {
        try {
            Class<?> closeGuardCls = Class.forName("dalvik.system.CloseGuard");
            Class<?> closeGuardReporterCls = Class.forName("dalvik.system.CloseGuard$Reporter");
            Method methodGetReporter = closeGuardCls.getDeclaredMethod("getReporter");
            Method methodSetReporter = closeGuardCls.getDeclaredMethod("setReporter", closeGuardReporterCls);
            Method methodSetEnabled = closeGuardCls.getDeclaredMethod("setEnabled", boolean.class);

            sOriginalReporter = methodGetReporter.invoke(null);

            methodSetEnabled.invoke(null, true);

            // open matrix close guard also
            MatrixCloseGuard.setEnabled(true);

            ClassLoader classLoader = closeGuardReporterCls.getClassLoader();
            if (classLoader == null) {
                return false;
            }

            methodSetReporter.invoke(null, Proxy.newProxyInstance(classLoader,
                new Class<?>[]{closeGuardReporterCls},
                new IOCloseLeakDetector(issueListener, sOriginalReporter)));

            return true;
        } catch (Throwable e) {
            MatrixLog.e(TAG, "tryHook exp=%s", e);
        }

        return false;
    }
複製代碼

這裏的CloseGuard有啥用?爲何騰訊的人要hook這個。這個在後續的分線中咱們在來詳細的說。若是要解決這個疑問,作好的辦法就是看源碼。(==系統埋點方式,監控系統資源的異常回收==)

關於native hook

Native Hook是採用PLT(GOT) Hook的方式hook了系統so中的IO相關的openreadwriteclose方法。在代理了這些系統方法後,Matrix作了一些邏輯上的細分,從而檢測出不一樣的IO Issue。

github.com/Tencent/mat…

JNIEXPORT jboolean JNICALL Java_com_tencent_matrix_iocanary_core_IOCanaryJniBridge_doHook(JNIEnv *env, jclass type) {
            __android_log_print(ANDROID_LOG_INFO, kTag, "doHook");

            for (int i = 0; i < TARGET_MODULE_COUNT; ++i) {
                const char* so_name = TARGET_MODULES[i];
                __android_log_print(ANDROID_LOG_INFO, kTag, "try to hook function in %s.", so_name);
								//打開so文件,並在內存中映射成ELF文件格式
                loaded_soinfo* soinfo = elfhook_open(so_name);
                if (!soinfo) {
                    __android_log_print(ANDROID_LOG_WARN, kTag, "Failure to open %s, try next.", so_name);
                    continue;
                }
								//替換open函數
                elfhook_replace(soinfo, "open", (void*)ProxyOpen, (void**)&original_open);
                elfhook_replace(soinfo, "open64", (void*)ProxyOpen64, (void**)&original_open64);

                bool is_libjavacore = (strstr(so_name, "libjavacore.so") != nullptr);
                if (is_libjavacore) {
                    if (!elfhook_replace(soinfo, "read", (void*)ProxyRead, (void**)&original_read)) {
                        __android_log_print(ANDROID_LOG_WARN, kTag, "doHook hook read failed, try __read_chk");
                        //http://refspecs.linux-foundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/libc---read-chk-1.html 相似於read()
                        if (!elfhook_replace(soinfo, "__read_chk", (void*)ProxyRead, (void**)&original_read)) {
                            __android_log_print(ANDROID_LOG_WARN, kTag, "doHook hook failed: __read_chk");
                            elfhook_close(soinfo);
                            return false;
                        }
                    }

                    if (!elfhook_replace(soinfo, "write", (void*)ProxyWrite, (void**)&original_write)) {
                        __android_log_print(ANDROID_LOG_WARN, kTag, "doHook hook write failed, try __write_chk");
                        if (!elfhook_replace(soinfo, "__write_chk", (void*)ProxyWrite, (void**)&original_write)) {
                            __android_log_print(ANDROID_LOG_WARN, kTag, "doHook hook failed: __write_chk");
                            elfhook_close(soinfo);
                            return false;
                        }
                    }
                }

                elfhook_replace(soinfo, "close", (void*)ProxyClose, (void**)&original_close);

                elfhook_close(soinfo);
            }

            return true;
        }
複製代碼

關於transform api

google.github.io/android-gra…

咱們編譯Android項目時,若是咱們想拿到編譯時產生的Class文件,並在生成Dex以前作一些處理,咱們能夠經過編寫一個Transform來接收這些輸入(編譯產生的Class文件),並向已經產生的輸入中添加一些東西。

image-20190914185405607

如何使用的?

github.com/Tencent/mat…

  • 編寫一個自定義的Transform
  • 註冊一個Plugin完成或者在gradle文件中直接註冊。
//MyCustomPlgin.groovy
public class MyCustomPlgin implements Plugin<Project> {

    @Override
    public void apply(Project project) {
        project.getExtensions().findByType(BaseExtension.class)
                .registerTransform(new MyCustomTransform());
    }
}
複製代碼
project.extensions.findByType(BaseExtension.class).registerTransform(new MyCustomTransform());  //在build.gradle中直接寫
複製代碼

MatrixTraceTransform 利用編譯期字節碼插樁技術,優化了移動端的FPS、卡頓、啓動的檢測手段。在打包過程當中,hook生成Dex的Task任務,添加方法插樁的邏輯。咱們的hook點是在Proguard以後,Class已經被混淆了,因此須要考慮類混淆的問題。

MatrixTraceTransform主要邏輯在transform方法中:

@Override
    public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        long start = System.currentTimeMillis()
        //是否增量編譯
        final boolean isIncremental = transformInvocation.isIncremental() && this.isIncremental()
        //transform的結果,重定向輸出到這個目錄
        final File rootOutput = new File(project.matrix.output, "classes/${getName()}/")
        if (!rootOutput.exists()) {
            rootOutput.mkdirs()
        }
        final TraceBuildConfig traceConfig = initConfig()
        Log.i("Matrix." + getName(), "[transform] isIncremental:%s rootOutput:%s", isIncremental, rootOutput.getAbsolutePath())
        //獲取Class混淆的mapping信息,存儲到mappingCollector中
        final MappingCollector mappingCollector = new MappingCollector()
        File mappingFile = new File(traceConfig.getMappingPath());
        if (mappingFile.exists() && mappingFile.isFile()) {
            MappingReader mappingReader = new MappingReader(mappingFile);
            mappingReader.read(mappingCollector)
        }

        Map<File, File> jarInputMap = new HashMap<>()
        Map<File, File> scrInputMap = new HashMap<>()

        transformInvocation.inputs.each { TransformInput input ->
            input.directoryInputs.each { DirectoryInput dirInput ->
                //收集、重定向目錄中的class
                collectAndIdentifyDir(scrInputMap, dirInput, rootOutput, isIncremental)
            }
            input.jarInputs.each { JarInput jarInput ->
                if (jarInput.getStatus() != Status.REMOVED) {
                    //收集、重定向jar包中的class
                    collectAndIdentifyJar(jarInputMap, scrInputMap, jarInput, rootOutput, isIncremental)
                }
            }
        }
        //收集須要插樁的方法信息,每一個插樁信息封裝成TraceMethod對象
        MethodCollector methodCollector = new MethodCollector(traceConfig, mappingCollector)
        HashMap<String, TraceMethod> collectedMethodMap = methodCollector.collect(scrInputMap.keySet().toList(), jarInputMap.keySet().toList())
       //執行插樁邏輯,在須要插樁方法的入口、出口添加MethodBeat的i/o邏輯
        MethodTracer methodTracer = new MethodTracer(traceConfig, collectedMethodMap, methodCollector.getCollectedClassExtendMap())
        methodTracer.trace(scrInputMap, jarInputMap)
        //執行原transform的邏輯;默認transformClassesWithDexBuilderForDebug這個task會將Class轉換成Dex
        origTransform.transform(transformInvocation)
        Log.i("Matrix." + getName(), "[transform] cost time: %dms", System.currentTimeMillis() - start)
    }
複製代碼

看到這裏了,咱們是否是應該總結下APM的核心技術是什麼?

大廠面試之一:APM的核心技術是什麼?作過自研的APM嗎?

APM核心原理一句話總結:依據打包原理,在 class 轉換爲 dex 的過程當中,調用 gradle transform api 遍歷 class 文件,藉助 Javassist、ASM 等框架修改字節碼,插入咱們本身的代碼實現性能數據的統計,這個過程是在編譯器間完成

你掌握了APM的核心原理,也能夠作Android的無痕埋點了,本質是同樣的,不同的是Hook的地方不同。

目前大廠面臨的問題?

  • 人手不夠,每一個員工都有招人的KPI(3-6年最吃香,<32歲,能吃苦耐勞的)
  • APP須要一套系統,包括阿里的**系統,頭條的複製機器,都須要實現快速的複製、快速的中間件、快速的監控體系化,這是將來的大廠工程化技術趨勢。那麼APM該怎麼作到快速的複製呢?
  • 目前高手和Android發燒友很差招,大廠缺人才,嚴重缺。

你本身的定位?有機會在談一談我本身的心得。

APM監控維度和指標

App基礎性能指標集中爲8類:網絡性能、崩潰、啓動加載、內存、圖片、頁面渲染、IM和VoIP(業務相關性和你的APP相關)、用戶行爲監控,基礎維度包括App、系統平臺、App版本和時間維度。

網絡性能

網絡服務成功率,平均耗時和訪問量、上下行的速率監控。訪問的連接、耗時等等。思考怎麼結合okhttp來作?

崩潰

崩潰數據的採集和分析,相似於Bugly平臺的功能

啓動加載

App的啓動咱們作了大的力氣進行了優化,多線程等等, Spark的有向無環圖(DAG)來處理業務的依賴性。對App的冷啓動時長、Android安裝後首次啓動時長和Android Bundle(atlas框架)啓動加載時長進行監控。

內存

四大監測目標:內存峯值、內存均值、內存抖動、內存泄露。

IM和VoIP等業務指標

這兩項都屬於業務型技術指標的監控,例如對各種IM消息到達率和VoIP通話的成功率、平均耗時和請求量進行監控。這裏須要根據本身的APP的業務進行鍼對性的梳理。

用戶行爲監控

用於App統計用戶行爲,實際上就是監控全部事件並把事件發送到服務上去。這在之前是埋點作的事情,如今也規整成APM須要作的事情,好比用戶的訪問路徑,相似於PC時代的PV,UV等概念。

圖片

資源文件的監測,好比Bitmap冗餘處理。haha庫處理,索引值。

頁面渲染

界面流暢性監測、FPS的監測、慢函數監測、卡頓監測、文件IO開銷監測等致使頁面渲染的各類問題。

微信Matrix框架解析

總體分析

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();
複製代碼

整理的結構以下:

image-20190914200458489

核心功能:

Resource Canary:
   Activity 泄漏
   Bitmap 冗餘
Trace Canary
   界面流暢性
   啓動耗時
   頁面切換耗時
   慢函數
   卡頓
SQLite Lint: 按官方最佳實踐自動化檢測 SQLite 語句的使用質量
IO Canary: 檢測文件 IO 問題
   文件 IO 監控
   Closeable Leak 監控
複製代碼

總體架構分析:

matrix-android-lib

  • Plugin 被定義爲某種監控能力的抽象
  • Issue 發生的某種被監控事件
  • Report 一種觀察者模式的實現,用於發現Issue時,通知觀察者
  • Matrix 單例模式的實現,暴漏給外部的接口
  • matrix-config.xml 相關監控的配置項
  • IDynamicConfig 開放給用戶自定義的相關監控的配置項,其實例會被各個Plugin持

plugin核心接口

  • IPlugin 一種監控能力的抽象
  • PluginListener 開放給用戶的,感知監控模塊初始化、啓動、中止、發現問題等生命週期的能力。 用戶可自行實現,並注入到Matrix中
  • Plugin 一、對IPlugin的默認實現,以觸發相關PluginListener生命週期 二、實現 IssuePublisher.OnIssueDetectListener接口的onDetectIssue方法
    • 該方法會在 具體監控事件發生時,被 Matrix.with().getPluginByClass(xxx).onDetectIssue(Issue)這樣調用。注意這裏是第一種通知方式
    • 該方法內部對Issue相關環境變量進行賦值,譬如引起該Issue的Plugin信息
    • 該方法最終觸發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);
}
複製代碼

Matrix對外接口

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<Plugin> plugins;
    private final PluginListener  pluginListener;
	
	private Matrix(Application app, PluginListener listener, HashSet<Plugin> 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 extends Plugin> T getPluginByClass(Class<T> 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);
    }

}
複製代碼

Utils 輔助功能

  • MatrixLog 開放給用戶的log能力
  • MatrixHandlerThread 公共線程,每每用於異步線程執行一些任務
  • DeviceUtil 獲取相關設備信息
  • MatrixUtil 判斷主線程等其餘utils

IssuePublisher被監控事件觀察者

  • Issue 被監控事件 type:類型,用於區分同一個tag不一樣類型的上報 tag: 該上報對應的tag stack:該上報對應的堆棧 process:該上報對應的進程名 time:issue 發生的時間

  • IssuePublisher 觀察者模式

    • 持有一個 發佈Listener(其實現每每是上文的 Plugin)

    • 持有一個 已發佈信息的Map,在一次運行時長內,避免針對同一事件的重複發佈

    • 通常而言,某種監控的監控探測器每每繼承該類,並在檢測到事件發生時,調用publishIssue(Issue)—>IssuePublisher.OnIssueDetectListener接口的onDetectIssue方法—>最終觸發PluginListener#onReportIssue

IO監測核心源碼分析

IO Canary:核心的做用是檢測文件 IO 問題,包括:文件 IO 監控和 Closeable Leak 監控。要想理解IO的監測和看懂開源的代碼,最重要的基礎就是掌握Native和Java層面的Hook。

Java層面的hook主要是基於反射技術,你們都比較熟悉了,那咱們來聊一聊Native層面的Hook。在JVM層面,Android使用Android PLT (Procedure Linkage Table)Hook和Inline Hookptrace三種主流的技術。

Matrix採用PLT的技術來實現SO文件API的Hook。

ELF: en.wikipedia.org/wiki/Execut…

refspecs.linuxbase.org/elf/elf.pdf

ELF文件的三種形式:

  • 可重定位的對象文件(Relocatable file),也就是咱們熟悉的那些.o文件,固然.a庫也算(由於它是.o的集合)
  • 可執行的對象文件(Executable file),linux下的可執行文件
  • 可被共享的對象文件(Shared object file),動態連接庫.so

咱們思考下C的程序是須要進行編譯和連接再到最後的運行的。那麼ELF文件從這個參與程序運行的角度也是分爲2種視圖的。

image-20190916143208858

​ ELF header 位於文件的最開始處,描述整個文件的組織結構。Program Header Table 告訴系統如何建立進程鏡像,在執行程序時必須存在,在 relocatable files 中則不須要。每一個 program header 分別描述一個 segment,包括 segment 在文件和內存中的大小及地址等等。執行視圖中的 segment 實際上是由不少個 section 組成。在一個進程鏡像中一般具備 text segment 和 data segment 等等。

關於重定位的做用和概念

重定位就是把符號引用與符號定義連接起來的過程,這也是 android linker 的主要工做之一。當程序中調用一個函數時,相關的 call 指令必須在執行期將控制流轉到正確的目標地址。因此,so 文件中必須包含一些重定位相關的信息,linker 據此完成重定位的工做。

docs.oracle.com/cd/E19683-0…

android.googlesource.com/platform/bi…

image-20190916144744091

image-20190916151707293

符號表表項的結構爲elf32_sym:

typedef struct elf32_sym {
    Elf32_Word  st_name;    /* 名稱 – index into string table */
    Elf32_Addr  st_value;   /* 偏移地址 */
    Elf32_Word  st_size;    /* 符號長度( 例如,函數的長度) */
    unsigned char   st_info;    /* 類型和綁定類型 */
    unsigned char   st_other;   /* 未定義 */
    Elf32_Half  st_shndx;   /* section header的索引號,表示位於哪一個section中 */
} Elf32_Sym;
複製代碼

重定位核心代碼:

androidxref.com/8.0.0_r4/xr… (具體的重定位類型定義和計算方法能夠參考elf說明文檔的 4.6.1.2 小節)

image-20190916152120420

Android PLT Hook的基本原理

​ Linux在執行動態連接的ELF的時候,爲了優化性能使用了一個叫延時綁定的策略。當在動態連接的ELF程序裏調用共享庫的函數時,第一次調用時先去查找PLT表中相應的項目,而PLT表中再跳躍到GOT表中但願獲得該函數的實際地址,但這時GOT表中指向的是PLT中那條跳躍指令下面的代碼,最終會執行_dl_runtime_resolve()並執行目標函數。所以,PLT Hook經過直接修改GOT表,使得在調用該共享庫的函數時跳轉到的是用戶自定義的Hook功能代碼。

image-20190916153407043

IO 監控流程:

image-20190916154924031

JNIEXPORT jboolean JNICALL Java_com_tencent_matrix_iocanary_core_IOCanaryJniBridge_doHook(JNIEnv *env, jclass type) {
            __android_log_print(ANDROID_LOG_INFO, kTag, "doHook");

            for (int i = 0; i < TARGET_MODULE_COUNT; ++i) {
                const char* so_name = TARGET_MODULES[i];
                __android_log_print(ANDROID_LOG_INFO, kTag, "try to hook function in %s.", so_name);

                loaded_soinfo* soinfo = elfhook_open(so_name);
                if (!soinfo) {
                    __android_log_print(ANDROID_LOG_WARN, kTag, "Failure to open %s, try next.", so_name);
                    continue;
                }
								//hook OS
                elfhook_replace(soinfo, "open", (void*)ProxyOpen, (void**)&original_open);
                elfhook_replace(soinfo, "open64", (void*)ProxyOpen64, (void**)&original_open64);

                bool is_libjavacore = (strstr(so_name, "libjavacore.so") != nullptr);
                if (is_libjavacore) {
                    if (!elfhook_replace(soinfo, "read", (void*)ProxyRead, (void**)&original_read)) {
                        __android_log_print(ANDROID_LOG_WARN, kTag, "doHook hook read failed, try __read_chk");
                        if (!elfhook_replace(soinfo, "__read_chk", (void*)ProxyRead, (void**)&original_read)) {
                            __android_log_print(ANDROID_LOG_WARN, kTag, "doHook hook failed: __read_chk");
                            elfhook_close(soinfo);
                            return false;
                        }
                    }
	                  //hook OS
                    if (!elfhook_replace(soinfo, "write", (void*)ProxyWrite, (void**)&original_write)) {
                        __android_log_print(ANDROID_LOG_WARN, kTag, "doHook hook write failed, try __write_chk");
                        if (!elfhook_replace(soinfo, "__write_chk", (void*)ProxyWrite, (void**)&original_write)) {
                            __android_log_print(ANDROID_LOG_WARN, kTag, "doHook hook failed: __write_chk");
                            elfhook_close(soinfo);
                            return false;
                        }
                    }
                }
	              //hook OS
                elfhook_replace(soinfo, "close", (void*)ProxyClose, (void**)&original_close);

                elfhook_close(soinfo);
            }

            return true;
        }

複製代碼

hook的替換核心代碼:(本質上就是置指針替換)

image-20190916155810095

看看代理方法:

image-20190916155946965

很明顯騰訊的人沒有考慮到自線程的問題。這裏能夠優化的。具體的其餘部分的細節請參見源碼。

內存泄漏監測核心源碼分析

github.com/Tencent/mat…

設計目的:

  • 自動且較爲準確地監測Activity泄漏,發現泄漏以後再觸發Dump Hprof而不是根據預先設定的內存佔用閾值盲目觸發
  • 自動獲取泄漏的Activity和冗餘Bitmap對象的引用鏈
  • 能靈活地擴展Hprof的分析邏輯,必要時容許提取Hprof文件人工分析

爲了解決線上監測和後臺分析,Matrix的ResourceCanary最終決定將監測步驟和分析步驟拆成兩個獨立的工具,以知足設計目標。

  • Hprof文件留在了服務端,爲人工分析提供了機會
  • 若是跳過觸發Dump Hprof,甚至能夠把監測步驟在現網環境啓用,以發現測試階段難以觸發的Activity泄漏

image-20190916162339863

ResourcePlugin

ResourcePlugin是該模塊的入口,負責註冊Android生命週期的監聽以及配置部分參數和接口回調。

ActivityRefWatcher ActivityRefWatcher負責的任務有彈出Dump內存的Dialog、Dump內存數據、讀取內存數據裁剪Hprof文件、生成包含裁剪後的Hprof以及泄漏的Activity的信息(進程號、Activity名、時間等)、通知主線程完成內存信息的備份並關閉Dialog。

咱們看下最爲核心的內存泄漏監測代碼:

//ActivityRefWatcher
private final Application.ActivityLifecycleCallbacks mRemovedActivityMonitor = new ActivityLifeCycleCallbacksAdapter() {
        private int mAppStatusCounter = 0;
        private int mUIConfigChangeCounter = 0;

        @Override
        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            mCurrentCreatedActivityCount.incrementAndGet();
        }

        @Override
        public void onActivityStarted(Activity activity) {
            if (mAppStatusCounter <= 0) {
                MatrixLog.i(TAG, "we are in foreground, start watcher task.");
                mDetectExecutor.executeInBackground(mScanDestroyedActivitiesTask);
            }
            if (mUIConfigChangeCounter < 0) {
                ++mUIConfigChangeCounter;
            } else {
                ++mAppStatusCounter;
            }
        }

        @Override
        public void onActivityStopped(Activity activity) {
            if (activity.isChangingConfigurations()) {
                --mUIConfigChangeCounter;
            } else {
                --mAppStatusCounter;
                if (mAppStatusCounter <= 0) {
                    MatrixLog.i(TAG, "we are in background, stop watcher task.");
                    mDetectExecutor.clearTasks();
                }
            }
        }

        @Override
        public void onActivityDestroyed(Activity activity) {
          //當activity銷燬的時候開始。。。
            pushDestroyedActivityInfo(activity);
            synchronized (mDestroyedActivityInfos) {
                mDestroyedActivityInfos.notifyAll();
            }
        }
    };
複製代碼
private void pushDestroyedActivityInfo(Activity activity) {
        final String activityName = activity.getClass().getName();
   	    //該Activity確認存在泄漏,且已經上報
        if (isPublished(activityName)) {
            MatrixLog.d(TAG, "activity leak with name %s had published, just ignore", activityName);
            return;
        }
        final UUID uuid = UUID.randomUUID();
        final StringBuilder keyBuilder = new StringBuilder();
        //生成Activity實例的惟一標識
        keyBuilder.append(ACTIVITY_REFKEY_PREFIX).append(activityName)
            .append('_').append(Long.toHexString(uuid.getMostSignificantBits())).append(Long.toHexString(uuid.getLeastSignificantBits()));
        final String key = keyBuilder.toString();
        //構造一個數據結構,表示一個已被destroy的Activity
        final DestroyedActivityInfo destroyedActivityInfo
            = new DestroyedActivityInfo(key, activity, activityName, mCurrentCreatedActivityCount.get());
       //放入ConcurrentLinkedQueue數據結構中,用於後續的檢查
        mDestroyedActivityInfos.add(destroyedActivityInfo);
    }
複製代碼

內存泄漏的核心代碼:

private final RetryableTask mScanDestroyedActivitiesTask = new RetryableTask() {

        @Override
        public Status execute() {
            // If destroyed activity list is empty, just wait to save power.
            while (mDestroyedActivityInfos.isEmpty()) {
                synchronized (mDestroyedActivityInfos) {
                    try {
                        mDestroyedActivityInfos.wait();
                    } catch (Throwable ignored) {
                        // Ignored.
                    }
                }
            }

            // Fake leaks will be generated when debugger is attached.
           //Debug調試模式,檢測可能失效,直接return
            if (Debug.isDebuggerConnected() && !mResourcePlugin.getConfig().getDetectDebugger()) {
                MatrixLog.w(TAG, "debugger is connected, to avoid fake result, detection was delayed.");
                return Status.RETRY;
            }
             //建立一個對象的弱引用
            final WeakReference<Object> sentinelRef = new WeakReference<>(new Object());
            triggerGc();
            //系統未執行GC,直接return
            if (sentinelRef.get() != null) {
                // System ignored our gc request, we will retry later.
                MatrixLog.d(TAG, "system ignore our gc request, wait for next detection.");
                return Status.RETRY;
            }

            final Iterator<DestroyedActivityInfo> infoIt = mDestroyedActivityInfos.iterator();

            while (infoIt.hasNext()) {
                final DestroyedActivityInfo destroyedActivityInfo = infoIt.next();
               //該實例對應的Activity已被標泄漏,跳過該實例
                if (isPublished(destroyedActivityInfo.mActivityName)) {
                    MatrixLog.v(TAG, "activity with key [%s] was already published.", destroyedActivityInfo.mActivityName);
                    infoIt.remove();
                    continue;
                }
               //若不能經過弱引用獲取到Activity實例,表示已被回收,跳過該實例
                if (destroyedActivityInfo.mActivityRef.get() == null) {
                    // The activity was recycled by a gc triggered outside.
                    MatrixLog.v(TAG, "activity with key [%s] was already recycled.", destroyedActivityInfo.mKey);
                    infoIt.remove();
                    continue;
                }
								 //該Activity實例 檢測到泄漏的次數+1
                ++destroyedActivityInfo.mDetectedCount;
    //當前顯示的Activity實例與泄漏的Activity實例相差幾個Activity跳轉
                long createdActivityCountFromDestroy = mCurrentCreatedActivityCount.get() - destroyedActivityInfo.mLastCreatedActivityCount;
               //若Activity實例 檢測到泄漏的次數未達到閾值,或者泄漏的Activity與當前顯示的Activity很靠近,可認爲是一種容錯手段(實際應用中有這種場景),跳過該實例
                if (destroyedActivityInfo.mDetectedCount < mMaxRedetectTimes
                    || (createdActivityCountFromDestroy < CREATED_ACTIVITY_COUNT_THRESHOLD && !mResourcePlugin.getConfig().getDetectDebugger())) {
                    // Although the sentinel tell us the activity should have been recycled,
                    // system may still ignore it, so try again until we reach max retry times.
                    MatrixLog.i(TAG, "activity with key [%s] should be recycled but actually still \n"
                            + "exists in %s times detection with %s created activities during destroy, wait for next detection to confirm.",
                        destroyedActivityInfo.mKey, destroyedActivityInfo.mDetectedCount, createdActivityCountFromDestroy);
                    continue;
                }

                MatrixLog.i(TAG, "activity with key [%s] was suspected to be a leaked instance.", destroyedActivityInfo.mKey);
                if (mHeapDumper != null) {
                    final File hprofFile = mHeapDumper.dumpHeap();
                    if (hprofFile != null) {
                        markPublished(destroyedActivityInfo.mActivityName);
                        final HeapDump heapDump = new HeapDump(hprofFile, destroyedActivityInfo.mKey, destroyedActivityInfo.mActivityName);
                        mHeapDumpHandler.process(heapDump);
                        infoIt.remove();
                    } else {
                        MatrixLog.i(TAG, "heap dump for further analyzing activity with key [%s] was failed, just ignore.",
                                destroyedActivityInfo.mKey);
                        infoIt.remove();
                    }
                } else {
                    // Lightweight mode, just report leaked activity name.
                    MatrixLog.i(TAG, "lightweight mode, just report leaked activity name.");
                    markPublished(destroyedActivityInfo.mActivityName);
                    if (mResourcePlugin != null) {
                        final JSONObject resultJson = new JSONObject();
                        try {
                            resultJson.put(SharePluginInfo.ISSUE_ACTIVITY_NAME, destroyedActivityInfo.mActivityName);
                        } catch (JSONException e) {
                            MatrixLog.printErrStackTrace(TAG, e, "unexpected exception.");
                        }
                        mResourcePlugin.onDetectIssue(new Issue(resultJson));
                    }
                }
            }

            return Status.RETRY;
        }
    };
複製代碼

RetryableTaskExecutor

RetryableTaskExecutor中包含了兩個Handler對象,一個mBackgroundHandlermMainHandler,分別給主線程和後臺的線程提交任務。默認重試次數是3。

AndroidHeapDumper

AndroidHeapDumper這個其實就是封裝了android.os.Debug的接口的類。主要是用系統提供的類android.os.DebugDump內存信息到本地,android.os.Debug會在本地生成一個Hprof文件,也是Matrix須要分析和裁剪的原始文件。

注意:通常Dump一次要5s~15s之間,線上建議不要使用,有必定的風險。

Dump的時候,AndroidHeapDumper會展現一個Dialog提示當前正在Dump中,Dump完畢就會將Dialog關閉。

Debug.dumpHprofData(hprofFile.getAbsolutePath());
複製代碼

Trace模塊核心源碼分析

Trace Canary: 用於監控界面流暢性、啓動耗時、頁面切換耗時、慢函數及卡頓等問題。(思考一下,技術上怎麼實現)

入口函數探針分析:

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; //fps
    private AnrTracer anrTracer; //anr

    public TracePlugin(TraceConfig config) {
        this.traceConfig = config;
    }
  ...
複製代碼

【關鍵知識點1】: MessageQueue中的IdleHandler接口有什麼用?

在Android中,咱們能夠處理Message,這個Message咱們能夠當即執行也能夠delay 必定時間執行。Handler線程在執行完全部的Message消息,它會wait,進行阻塞,直到有新的Message到達。若是這樣子,那麼這個線程也太浪費了。MessageQueue提供了另外一類消息,IdleHandler。也就是說當咱們的MessageQueue中的消息被處理完後,就會觸發一次或者屢次回調消息。

應用場景:一、好比主線程在開始加載頁面完成後,若是線程空閒就提早加載些二級頁面的內容。

​ 二、消息觸發器 例如在APM中的做用

​ 三、優化Activity的啓動時間,在Resume中是否是能夠增長idle的監聽

Looper.myQueue().addIdleHandler(() -> {
            initializeData();
		    return false;
        });
複製代碼

源碼分析:

Message next(){
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message. Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier. Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready. Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run. Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
								//根據IdleHandler中的回掉方法來判斷是否移除
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }
複製代碼

LooperMonitor類監測卡頓問題:

發生在Android主線程的每16ms重繪操做依賴於Main Looper中消息的發送和獲取。若是App一切運行正常,無卡頓無丟幀現象發生,那麼開發者的代碼在主線程Looper消息隊列中發送和接收消息的時間會很短,理想狀況是16ms,這是也是Android系統規定的時間。可是,若是一些發生在主線程的代碼寫的過重,執行任務花費時間過久,就會在主線程延遲Main Looper的消息在16ms尺度範圍內的讀和寫。

咱們如何檢測卡頓的問題?

使用主線程的Looper監測系統發生的卡頓和丟幀。編程技巧是設置一個閾值,看是否能夠打印stack信息。

網絡上說使用Android的Choreographer監測App發生的UI卡頓丟幀問題,本質上仍是利用了Android的主線程的Looper消息機制。Android 系統每隔 16.67 ms 都會發送一個 VSYNC 信號觸發 UI 的渲染,正常狀況下兩個 VSYNC 信號之間是 16.67 ms ,若是超過 16.67 ms 則能夠認爲渲染髮生了卡頓。

Choreographer.getInstance()
            .postFrameCallback(new Choreographer.FrameCallback() {
                @Override
                public void doFrame(long l) {
                     if(frameTimeNanos - mLastFrameNanos > 100) {
                        ...
                     }
                     mLastFrameNanos = frameTimeNanos;
                     Choreographer.getInstance().postFrameCallback(this);
                }
        });
複製代碼

本質:判斷相鄰的兩次 FrameCallback.doFrame(long l) 間隔是否超過閾值,若是超過閾值則發生了卡頓,則能夠在另一個子線程中 dump 當前主線程的堆棧信息進行分析。

消息處理

UIThreadMonitor

init():

public void init(TraceConfig config) {
        if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
            throw new AssertionError("must be init in main thread!");
        }
        this.isInit = true;
        this.config = config;
        choreographer = Choreographer.getInstance();
        callbackQueueLock = reflectObject(choreographer, "mLock");              
        callbackQueues = reflectObject(choreographer, "mCallbackQueues");                                                                    // 代碼 1

        addInputQueue = reflectChoreographerMethod(callbackQueues[CALLBACK_INPUT], ADD_CALLBACK, long.class, Object.class, Object.class);             // 代碼 2
        addAnimationQueue = reflectChoreographerMethod(callbackQueues[CALLBACK_ANIMATION], ADD_CALLBACK, long.class, Object.class, Object.class);     // 代碼 3
        addTraversalQueue = reflectChoreographerMethod(callbackQueues[CALLBACK_TRAVERSAL], ADD_CALLBACK, long.class, Object.class, Object.class);     // 代碼 4 
        frameIntervalNanos = reflectObject(choreographer, "mFrameIntervalNanos");

        LooperMonitor.register(new LooperMonitor.LooperDispatchListener() {                                                                  // 代碼 5 
            @Override
            public boolean isValid() {
                return isAlive;
            }

            @Override
            public void dispatchStart() {
                super.dispatchStart();
                UIThreadMonitor.this.dispatchBegin();                                                                                       // 代碼 6
            }

            @Override
            public void dispatchEnd() {
                super.dispatchEnd();
                UIThreadMonitor.this.dispatchEnd();                                                                                      // 代碼 7
            }

        });

                ......

    }  
複製代碼
- 代碼 1:經過反射拿到了 Choreographer 實例的 mCallbackQueues 屬性,mCallbackQueues 是一個回調隊列數組 CallbackQueue[] mCallbackQueues,其中包括四個回調隊列,
    第一個是輸入事件回調隊列 CALLBACK_INPUT = 0,
    第二個是動畫回調隊列 CALLBACK_ANIMATION = 1,
    第三個是遍歷繪製回調隊列 CALLBACK_TRAVERSAL = 2,
    第四個是提交回調隊列 CALLBACK_COMMIT = 3。
這四個階段在每一幀的 UI 渲染中是依次執行的,每一幀中各個階段開始時都會回調 mCallbackQueues 中對應的回調隊列的回調方法。
- 代碼 2:經過反射拿到輸入事件回調隊列的 addCallbackLocked 方法
- 代碼 3:經過反射拿到動畫回調隊列的 addCallbackLocked 方法
- 代碼 4:經過反射拿到遍歷繪製回調隊列的addCallbackLocked 方法
- 代碼 5:經過 LooperMonitor.register(LooperDispatchListener listener) 方法向 LooperMonitor 中設置 LooperDispatchListener listener
- 代碼 6:在 Looper.loop() 中的消息處理開始時的回調
- 代碼 7:在 Looper.loop() 中的消息處理結束時的回調
複製代碼

核心:

private void dispatchBegin() {
  //記錄2個時間 線程起始時間 和CPU的開始時間
        token = dispatchTimeMs[0] = SystemClock.uptimeMillis();
        dispatchTimeMs[2] = SystemClock.currentThreadTimeMillis();
        AppMethodBeat.i(AppMethodBeat.METHOD_ID_DISPATCH);

        synchronized (observers) {
            for (LooperObserver observer : observers) {
                if (!observer.isDispatchBegin()) {
                    observer.dispatchBegin(dispatchTimeMs[0], dispatchTimeMs[2], token);
                }
            }
        }
    }  
複製代碼
private void dispatchEnd() {                                                                                            // 代碼 3
        if (isBelongFrame) {
            doFrameEnd(token);
        }

        dispatchTimeMs[3] = SystemClock.currentThreadTimeMillis();
        dispatchTimeMs[1] = SystemClock.uptimeMillis();

        AppMethodBeat.o(AppMethodBeat.METHOD_ID_DISPATCH);

        synchronized (observers) {
            for (LooperObserver observer : observers) {
                if (observer.isDispatchBegin()) {
                    observer.dispatchEnd(dispatchTimeMs[0], dispatchTimeMs[2], dispatchTimeMs[1], dispatchTimeMs[3], token, isBelongFrame);
                }
            }
        }

    }  
複製代碼

核心點總結:

  • 在 UIThreadMonitor 中有兩個長度是三的數組 queueStatusqueueCost 分別對應着每一幀中輸入事件階段、動畫階段、遍歷繪製階段的狀態和耗時,queueStatus 有三個值:DO_QUEUE_DEFAULT、DO_QUEUE_BEGIN 和 DO_QUEUE_END。
  • UIThreadMonitor 實現 Runnable 接口,也是爲了將 UIThreadMonitor 做爲輸入事件回調 CALLBACK_INPUT 的回調方法,設置到 Choreographer 中去的。

看到這裏應該搞明白了卡頓的檢測原理,那麼FPS的計算呢?

每一幀的時間信息經過 HashSet<LooperObserver> observers 回調出去,看一下是在哪裏向 observers 添加 LooperObserver 回調的。主要看一下 FrameTracer 這個類,其中涉及到了幀率 FPS 的計算相關的代碼。

FPSCollectorFrameTracer 的一個內部類,實現了 IDoFrameListener 接口,主要邏輯是在 doFrameAsync() 方法中

  • 代碼 1:會根據當前 ActivityName 建立一個對應的 FrameCollectItem 對象,存放在 HashMap 中
  • 代碼 2:調用 FrameCollectItem#collect(),計算幀率 FPS 等一些信息
  • 代碼 3:若是此 Activity 的繪製總時間超過 timeSliceMs(默認是 10s),則調用 FrameCollectItem#report() 上報統計數據,並從 HashMap 中移除當前 ActivityName 和對應的 FrameCollectItem 對象
private class FPSCollector extends IDoFrameListener {

        private Handler frameHandler = new Handler(MatrixHandlerThread.getDefaultHandlerThread().getLooper());
        private HashMap<String, FrameCollectItem> map = new HashMap<>();

        @Override
        public Handler getHandler() {
            return frameHandler;
        }

        @Override
        public void doFrameAsync(String focusedActivityName, long frameCost, int droppedFrames) {
            super.doFrameAsync(focusedActivityName, frameCost, droppedFrames);
            if (Utils.isEmpty(focusedActivityName)) {
                return;
            }

            FrameCollectItem item = map.get(focusedActivityName);       // 代碼 1
            if (null == item) {
                item = new FrameCollectItem(focusedActivityName);
                map.put(focusedActivityName, item);
            }

            item.collect(droppedFrames);                                                                // 代碼 2

            if (item.sumFrameCost >= timeSliceMs) { // report // 代碼 3
                map.remove(focusedActivityName);
                item.report();
            }
        }

    }
複製代碼

FrameCollectItem:計算FPS。

  • 根據 float fps = Math.min(60.f, 1000.f * sumFrame / sumFrameCost) 計算 fps 值

  • sumDroppedFrames 統計總的掉幀個數

  • sumFrameCost 表明總耗時

計算幀率參考資料:

github.com/friendlyrob… 熟讀代碼 並應用到本身的項目中。

關於hook的部分:

ActivityThreadHacker.java

利用了反射機制進行了hook,代碼比較清晰,目的很明確就是利用本身寫的HackCallback來替換ActivityThread類裏的mCallback,達到偷樑換柱的效果,這樣作的意義就是能夠攔截mCallback的原有的消息,而後選擇本身要處理的消息。搞清楚Activity的啓動流程 AMS和ActivityThread進程之間的交互。

image-20190921190329689

獲取到Activity的啓動時機。

AppMethodBeat 經過hook記錄每一個方法執行的時間。

/** * hook method when it's called in. * * @param methodId */
    public static void i(int methodId) {

        if (status <= STATUS_STOPPED) {
            return;
        }
        if (methodId >= METHOD_ID_MAX) {
            return;
        }

        if (status == STATUS_DEFAULT) {
            synchronized (statusLock) {
                if (status == STATUS_DEFAULT) {
                    realExecute();
                    status = STATUS_READY;
                }
            }
        }

        if (Thread.currentThread().getId() == sMainThread.getId()) {
            if (assertIn) {
                android.util.Log.e(TAG, "ERROR!!! AppMethodBeat.i Recursive calls!!!");
                return;
            }
            assertIn = true;
            if (sIndex < Constants.BUFFER_SIZE) {
                mergeData(methodId, sIndex, true);
            } else {
                sIndex = -1;
            }
            ++sIndex;
            assertIn = false;
        }
    }

    /** * hook method when it's called out. * * @param methodId */
    public static void o(int methodId) {

        if (status <= STATUS_STOPPED) {
            return;
        }
        if (methodId >= METHOD_ID_MAX) {
            return;
        }
        if (Thread.currentThread().getId() == sMainThread.getId()) {
            if (sIndex < Constants.BUFFER_SIZE) {
                mergeData(methodId, sIndex, false);
            } else {
                sIndex = -1;
            }
            ++sIndex;
        }
    }

 /** * merge trace info as a long data * * @param methodId * @param index * @param isIn */
    private static void mergeData(int methodId, int index, boolean isIn) {
        if (methodId == AppMethodBeat.METHOD_ID_DISPATCH) {
          //記錄上面2個方法的時間差
            sCurrentDiffTime = SystemClock.uptimeMillis() - sDiffTime;
        }
        long trueId = 0L;
        if (isIn) {
            trueId |= 1L << 63;
        }
        trueId |= (long) methodId << 43;
        trueId |= sCurrentDiffTime & 0x7FFFFFFFFFFL;
        sBuffer[index] = trueId;
        checkPileup(index);
        sLastIndex = index;
    }

複製代碼

源碼讀到這裏是否是能夠想想,咱們應該怎麼找方法的調用?

github.com/Tencent/mat…

利用ASM技術完成在方法前執行「com/tencent/matrix/trace/core/AppMethodBeat」這個class裏的i()方法,在每一個方法最後執行o()方法。

鄭重聲明

本文原做者爲課程主講師禪宗,由Android研習社代發

版權©️歸Android 研習社全部,侵權必究

相關文章
相關標籤/搜索