美團無埋點方案 - Gradle Plugin 的方式,在編譯期間修改 class | 掘金技術徵文

版權聲明:javascript

本帳號發佈文章均來自公衆號,承香墨影(cxmyDev),版權歸承香墨影全部。java

未經容許,不得轉載。android

1、前言

回顧一下,以前說的美團的無埋點方案,是重寫須要的UI 控件,而後在其中監聽對應的事件,事件觸發的時候,上報統計點便可。以前也講解了,如何使用 AppCompatDelegate 來替換咱們項目內的系統控件。api

本文就,如何使用一個 Gradle Plugin(如下簡稱 Plugin),來實如今編譯期間,修改 class 字節碼的功能,作一個簡單的講解。函數

2、技術要點

由於涉及的點比較多,因此有一些地方只是點一下,不會詳細深刻說明,有興趣的話,能夠看看文內推薦的文章,或者自行搜索相關資料。工具

一、什麼是 Gradle 插件

Gradle 是一個自動化構建工具,可使用一種基於 Groovy 的特性領域語言(DSL)來聲明項目設置。Gradle 也提供了一些默認的 Plugin 幫助咱們構建項目,若是想要在構建期間定製的操做,就須要單獨開發一款和本身功能相關的 Gradle Plugin。post

而 Gradle Plugin,是可使用 Groovy、Java、Scala 進行開發的。自己對 Scala 不熟悉,通常我都是使用 Groovy 來開發 Gradle 插件,而 Groovy 又是能夠和 Java 代碼混編的,上手還算容易。gradle

Gradle 的插件,能夠理解爲編寫的一個庫,因此它和咱們編寫的 Library同樣,有在項目內供本項目使用的,也有能夠發佈出去,供其餘項目使用的。ui

這兩種方式的區別:spa

  1. 在項目內的 build.gradle 文件中編寫,或者直接以一個單獨的 Module 存在。這種方式的肯定就是不方便移植和複用。
  2. 另一種就是一個單獨的插件,能夠發佈出去,供其餘項目使用的。有點是方便移植和複用。

Gradle 的構建過程,是一個鏈式的過程,A → B → C,這的一個過程。也就是說,咱們依賴 Gradle Plugin,來完成咱們指定的任務,就須要瞭解到,咱們的操做是須要插入到 Gradle 構建過程當中的那一步。

二、Gradle 的 Project 接口

Project 接口是你寫的插件代碼和 Gradle 交互的主要接口,經過 Project 能夠在插件內,使用 Gradle 特性,而 Project 與 build.gradle 文件是一對一的關係。

在 Gradle 中,經過 Extension 在不一樣的 Gradle Plugin 之間傳遞處理後的結果。

例如上面,就是一個 Plugin 的入口,用到了 Project 來操做 Gradle 的構建過程,在其中註冊了一個 Transform,這個 Transform 纔是在編譯期間修改 class 字節碼的關鍵。

三、Gradle 的 Transform

前面提到,開發 Gradle Plugin 的時候,必定要明確須要在什麼地方作什麼操做。

而從 Gradle 1.5.0 版本開始,Android 的 Gradle 插件中就引入了 Transform API 。和上面鏈式調用的思想同樣,Transform 每次都會獲得一個輸入,而後作對應的處理,再將輸出的結果,輸出出去,做爲下一個 Transform 的輸入。

Transform 的相關內容,能夠查閱文檔:

tools.android.com/tech-docs/n…

因此咱們在上面,使用 registerTransform() 註冊了一個咱們本身的 Transform ,供咱們在編譯期間作對應的修改。

Transform 是一個虛類,須要對其進行實現。而最重要的方法就是 transform()。

這差很少算是一個標準實現,能夠看到它須要區分出是當前項目內的包,還有第三方庫的 Jar 包,進行單獨處理。

四、使用 Javassist 修改 class

已經明確,能夠在 Transform 中修改 class 字節碼,而作這個修改就須要用到:Javassit。

固然這裏不是必定須要使用 Javassit ,其餘的字節碼操做庫應該也能夠,例如:ASM。

Javassist 能夠徹底替換一個方法或者構造函數的字節碼正文,這裏就不展開討論了。具體 Javassist 的使用,能夠自行查閱資料。

使用 Javassist 還須要在 build.gradle 中添加依賴關係:

compile 'org.javassist:javassist:3.20.0-GA'複製代碼

推薦一篇講解 Javassist 的文章:

www.ibm.com/developerwo…

3、舉個例子

既然關鍵技術點已經介紹過了,那麼就以一個簡單的例子,試着編寫一個 Gradle Plugin ,在其中修改其內的 class 字節碼,最簡單的,在構造方法中添加一行代碼。

建立一個空項目,只有一個 Activity 頁面。開始編寫注入邏輯。

這裏查找到須要的 class 文件以後,首先判斷是否有構造方法,若是沒有構造方法就建立一個,而後在其構造方法內注入一行代碼。

在 Transform.transform() 中,調用注入的方法。

在主項目中,添加這個 Gradle Plugin 插件。

最終運行以後,咱們來看看反編譯後的效果。

可是這樣實際上並無辦法修改第三方庫裏的類,這個須要咱們特殊處理。前面已經提到,在 Transform 中,須要區分當前項目的目錄,而針對第三方庫的 Jar 包,咱們須要先對其進行解壓,而後修改完成以後,再壓縮回去。

接下來在 Demo 中,新建一個叫"lib" 的 module ,在其內只有一個類,編譯成 Jar 包,在主項目中引用它。

那麼咱們開始編寫解壓的邏輯。

而後再從新壓縮成 Jar 包的方法也跟上。

最後,咱們還須要修改 Transform.transform() 方法。

這裏爲了方便,指定須要解壓的 Jar 包,而且解壓在根目錄下。最終會從新打包成一個 Jar 包,給主項目引用。

接下來看看反編譯後的 apk 。

能夠看到,對 Jar 包內的類,用這樣的方式也是能夠將其修改的。

最後看看整個項目的目錄結構。

4、結語

有須要 Demo 源碼的朋友,能夠在本公衆號回覆 "GradlePlugin" 得到。

實際上,完整的替換方案,會比這裏複雜不少。有須要能夠先了解一下這些技術細節再嘗試編寫接下去的邏輯,有時間的話,以後再繼續分享其涉及到的細節。

本文參加掘金技術徵文:juejin.im/post/58d8e9…

公衆號二維碼.jpg
相關文章
相關標籤/搜索