版權聲明:javascript
本帳號發佈文章均來自公衆號,承香墨影(cxmyDev),版權歸承香墨影全部。java
未經容許,不得轉載。android
回顧一下,以前說的美團的無埋點方案,是重寫須要的UI 控件,而後在其中監聽對應的事件,事件觸發的時候,上報統計點便可。以前也講解了,如何使用 AppCompatDelegate 來替換咱們項目內的系統控件。api
本文就,如何使用一個 Gradle Plugin(如下簡稱 Plugin),來實如今編譯期間,修改 class 字節碼的功能,作一個簡單的講解。函數
由於涉及的點比較多,因此有一些地方只是點一下,不會詳細深刻說明,有興趣的話,能夠看看文內推薦的文章,或者自行搜索相關資料。工具
Gradle 是一個自動化構建工具,可使用一種基於 Groovy 的特性領域語言(DSL)來聲明項目設置。Gradle 也提供了一些默認的 Plugin 幫助咱們構建項目,若是想要在構建期間定製的操做,就須要單獨開發一款和本身功能相關的 Gradle Plugin。post
而 Gradle Plugin,是可使用 Groovy、Java、Scala 進行開發的。自己對 Scala 不熟悉,通常我都是使用 Groovy 來開發 Gradle 插件,而 Groovy 又是能夠和 Java 代碼混編的,上手還算容易。gradle
Gradle 的插件,能夠理解爲編寫的一個庫,因此它和咱們編寫的 Library同樣,有在項目內供本項目使用的,也有能夠發佈出去,供其餘項目使用的。ui
這兩種方式的區別:spa
Gradle 的構建過程,是一個鏈式的過程,A → B → C,這的一個過程。也就是說,咱們依賴 Gradle Plugin,來完成咱們指定的任務,就須要瞭解到,咱們的操做是須要插入到 Gradle 構建過程當中的那一步。
Project 接口是你寫的插件代碼和 Gradle 交互的主要接口,經過 Project 能夠在插件內,使用 Gradle 特性,而 Project 與 build.gradle 文件是一對一的關係。
在 Gradle 中,經過 Extension 在不一樣的 Gradle Plugin 之間傳遞處理後的結果。
例如上面,就是一個 Plugin 的入口,用到了 Project 來操做 Gradle 的構建過程,在其中註冊了一個 Transform,這個 Transform 纔是在編譯期間修改 class 字節碼的關鍵。
前面提到,開發 Gradle Plugin 的時候,必定要明確須要在什麼地方作什麼操做。
而從 Gradle 1.5.0 版本開始,Android 的 Gradle 插件中就引入了 Transform API 。和上面鏈式調用的思想同樣,Transform 每次都會獲得一個輸入,而後作對應的處理,再將輸出的結果,輸出出去,做爲下一個 Transform 的輸入。
Transform 的相關內容,能夠查閱文檔:
tools.android.com/tech-docs/n…
因此咱們在上面,使用 registerTransform() 註冊了一個咱們本身的 Transform ,供咱們在編譯期間作對應的修改。
Transform 是一個虛類,須要對其進行實現。而最重要的方法就是 transform()。
這差很少算是一個標準實現,能夠看到它須要區分出是當前項目內的包,還有第三方庫的 Jar 包,進行單獨處理。
已經明確,能夠在 Transform 中修改 class 字節碼,而作這個修改就須要用到:Javassit。
固然這裏不是必定須要使用 Javassit ,其餘的字節碼操做庫應該也能夠,例如:ASM。
Javassist 能夠徹底替換一個方法或者構造函數的字節碼正文,這裏就不展開討論了。具體 Javassist 的使用,能夠自行查閱資料。
使用 Javassist 還須要在 build.gradle 中添加依賴關係:
compile 'org.javassist:javassist:3.20.0-GA'複製代碼
推薦一篇講解 Javassist 的文章:
既然關鍵技術點已經介紹過了,那麼就以一個簡單的例子,試着編寫一個 Gradle Plugin ,在其中修改其內的 class 字節碼,最簡單的,在構造方法中添加一行代碼。
建立一個空項目,只有一個 Activity 頁面。開始編寫注入邏輯。
這裏查找到須要的 class 文件以後,首先判斷是否有構造方法,若是沒有構造方法就建立一個,而後在其構造方法內注入一行代碼。
在 Transform.transform() 中,調用注入的方法。
在主項目中,添加這個 Gradle Plugin 插件。
最終運行以後,咱們來看看反編譯後的效果。
可是這樣實際上並無辦法修改第三方庫裏的類,這個須要咱們特殊處理。前面已經提到,在 Transform 中,須要區分當前項目的目錄,而針對第三方庫的 Jar 包,咱們須要先對其進行解壓,而後修改完成以後,再壓縮回去。
接下來在 Demo 中,新建一個叫"lib" 的 module ,在其內只有一個類,編譯成 Jar 包,在主項目中引用它。
那麼咱們開始編寫解壓的邏輯。
而後再從新壓縮成 Jar 包的方法也跟上。
最後,咱們還須要修改 Transform.transform() 方法。
這裏爲了方便,指定須要解壓的 Jar 包,而且解壓在根目錄下。最終會從新打包成一個 Jar 包,給主項目引用。
接下來看看反編譯後的 apk 。
能夠看到,對 Jar 包內的類,用這樣的方式也是能夠將其修改的。
最後看看整個項目的目錄結構。
有須要 Demo 源碼的朋友,能夠在本公衆號回覆 "GradlePlugin" 得到。
實際上,完整的替換方案,會比這裏複雜不少。有須要能夠先了解一下這些技術細節再嘗試編寫接下去的邏輯,有時間的話,以後再繼續分享其涉及到的細節。
本文參加掘金技術徵文:juejin.im/post/58d8e9…