歡迎關注微信公眾號:BaronTalk,獲取更多精彩好文!
一. 前言
效能問題是導致 App 使用者流失的罪魁禍首之一,如果使用者在使用我們 App 的時候遇到諸如頁面卡頓、響應速度慢、發熱嚴重、流量電量消耗大等問題的時候,很可能就會解除安裝掉我們的 App。而往往獲取使用者的成本是高昂的,因此因為效能問題導致使用者流失的情況是我們要極力避免的,做不好這一點是我們開發人員的失職。
去年我們團隊完成了整個專案架構方面的重構(有興趣的同學可以參考我之前的文章安居客 Android 專案架構演進 與Android 模組化探索與實踐 ),目前已經能夠很好的支撐我們的業務,並對團隊的開發效率也有了一定的提升、專案質量也有了大幅的進步。
但是專案上線後,到底有沒有效能問題?使用者體驗到底怎麼樣?在使用者的使用場景中到底會遇到哪些效能問題?我們專案的效能短板又在哪裡?這些問題的答案我們都不得而知,因此開發一套完善的效能監控體系勢在必行。我們團隊在今年開始著手開發自己的效能監控元件 APM,希望通過它來採集線上效能資料,找到效能短板,針對性的優化使用者體驗。
APM 全稱 Application Performance Management & Monitoring (應用效能管理/監控)
後面我會通過一系列的文章來介紹 APM 的原理、框架設計與實現等等。本篇就是這個系列的第一篇,主要從實現原理方面來介紹 APM。按照目前的計劃,這個系列大致會從如下幾個方面來展開:
- 原理篇:主要介紹 APM 的實現原理;
- 設計篇:介紹整個 APM 框架設計;
- 實現篇-Gradle Plugin:介紹 Gradle 外掛在 APM 專案中的應用,以及如何開發一個 Gradle Plugin;
- 實現篇-Javassist/ASM:Javassist、ASM 等位元組碼操作庫的介紹,以及如何使用它們在編譯時插入程式碼來採集各項效能資料;
- 實現篇-資料儲存及上報:介紹 APM 框架的儲存上報機制及實現過程;
- 釋出整合:最後會介紹如何將庫釋出到 jCenter() 以及如何在生產專案中整合。
這裡要向大家交代一點是,之前的文章為了極力做到將複雜的問題用通俗易懂的方式解釋清楚,又要面面俱到,往往篇幅過長;諸如之前寫過的RxJava系列6(從微觀角度解讀RxJava原始碼) 、神兵利器Dagger2 、安居客 Android 專案架構演進 、Android 模組化探索與實踐 、寫給 Android 應用工程師的 Binder 原理剖析等文章,篇幅通常都在 8000~10000字以上,通篇閱讀下來可能需要近半個小時的時間,不太符合當下碎片化閱讀的需求;因此在後面的寫作上會控制篇幅,儘量控制在 10 分鐘以內的長度。
這也是我為什麼會將 APM 作為一個系列來介紹的原因,同時這也能保證後面在介紹 APM 的時候能夠深入到實現細節,避免泛泛而談。
二. Android APM 的基本原理
市場上有很多商業化的 APM 平臺,比如著名的 NewRelic,還有國內的 聽雲、OneAPM 等等。這些平臺的工作流程基本都是一致的:
- 首先在客戶端(Android、iOS、Web等)採集資料;
- 接著將採集到的資料整理上報到伺服器;
- 伺服器接收到資料後建模、儲存、挖掘分析,讓後將資料視覺化,供使用者使用。
如下圖:
我們介紹的 Android APM 框架其實就是在 Android 平臺上應用的一個資料採集上報 SDK。主要包含三大模組:
- 資料採集
- 資料儲存
- 資料上報
其中資料採集是整個 APM 框架的核心。
資料採集我們可以通過手動埋點的方式,但這種方式工作量巨大、不靈活,而且無法覆蓋到所有場景;因此只能通過自動化的方式來採集資料。在應用構建期間,通過修改位元組碼的方式來進行位元組碼插樁就是實現自動化的方案之一。
三. Android 打包流程及位元組碼插樁原理
在談位元組碼插樁的原理之前,首先我們看看 Android 的打包流程,如下圖:
從上面這張打包流程圖我們可以看到,一個 App 的所有 class 檔案,包括第三方的 class 檔案都會經過 dex 的過程打包成一個或者多個 dex 檔案。
這其中涉及到兩個很關鍵的環節:
- javac:將 .java 格式的原始碼檔案編譯成 class 檔案;
- dex: 將 class 格式的檔案打包彙總,組成一個或者多個 dex 檔案。
我們想要對位元組碼進行修改,只需要在 javac 之後 dex 之前遍歷所有的位元組碼檔案,並按照一定的規則過濾修改就好了,這裡便是位元組碼插樁的入口。
那麼我們到底如何介入打包過程,在 class 轉換為 dex 檔案的時候實現對位元組碼的修改呢?
答案是 transform api
Android Gradle Plugin 1.5.0 及以上版本,Google 官方提供了 transform api 作為位元組碼插樁的入口。我們只需要實現一個自定義的 Gradle Plugin,然後在編譯階段去修改位元組碼檔案。對於 Gradle Plugin 的具體實現後面的文章再做詳細講解。
四. 修改位元組碼
找到了插樁入口,接下來就要對位元組碼進行修改。對於位元組碼的修改,比較常用的框架有 Javassist 和 ASM。
-
Javassist 是一個開源的分析、編輯和建立 Java 位元組碼的類庫,它提供了原始碼級別的 API 以及位元組碼級別的 API,原始碼級別的 API,直接使用 Java 編碼的形式,而不需要深入瞭解虛擬機器指令,就能動態改變類的結構或者動態生成類。
-
ASM 是一個 Java 位元組碼操控框架。它能被用來動態生成類或者增強既有類的功能。ASM 可以直接產生二進位制 class 檔案,也可以在類被載入入 Java 虛擬機器之前動態改變類行為。
ASM 和 Javassit 相比,API 貼近底層,比較難使用,需要對 Java 位元組碼和虛擬機器方面有一定程度的瞭解。ASM 的優點就在於效能上的優勢,且更加靈活;Javassist 的實現中大量使用的反射,所以效能偏低。
簡單的說就是 ASM 雖然難以使用,但是功能強大效率高。是很多無痕埋點、APM框架的首選方案。
ASM 的具體時候我們放到這個系列後面的文章介紹。
五. 總結
Android APM 的原理其實非常簡單,用一句話總結就是:
依據打包原理,在 class 轉換為 dex 的過程中,呼叫 gradle transform api 遍歷 class 檔案,藉助 Javassist、ASM 等框架修改位元組碼,插入我們自己的程式碼實現效能資料的統計。
以上所有過程都是在編譯期完成的。
其實 Android 上的無痕埋點也是同樣的原理,區別只不過是我們 hook 的點不同,採集的資料不同,因此掌握了 APM 的實現原理同樣可以實現無痕埋點系統。
原理很簡單,難的是實現細節。比如如何插樁採集到頁面幀率、流量、耗電量等等。這些具體細節我們放到後面一一介紹。至於為什麼放到後面……因為很多東西自己沒做過我也不知道啊……?
如果你喜歡我的文章,就關注下我的公眾號 BaronTalk 、 知乎專欄 或者在 GitHub 上添個 Star 吧!
- 微信公眾號:BaronTalk
- 知乎專欄:zhuanlan.zhihu.com/baron
- GitHub:github.com/BaronZ88
- 個人部落格:baronzhang.com