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

承香墨影發表於2017-04-25

版權宣告:

本賬號釋出文章均來自公眾號,承香墨影(cxmyDev),版權歸承香墨影所有。

未經允許,不得轉載。

一、前言

回顧一下,之前說的美團的無埋點方案,是重寫需要的UI 控制元件,然後在其中監聽對應的事件,事件觸發的時候,上報統計點即可。之前也講解了,如何使用 AppCompatDelegate 來替換我們專案內的系統控制元件。

本文就,如何使用一個 Gradle Plugin(以下簡稱 Plugin),來實現在編譯期間,修改 class 位元組碼的功能,做一個簡單的講解。

二、技術要點

因為涉及的點比較多,所以有一些地方只是點一下,不會詳細深入說明,有興趣的話,可以看看文內推薦的文章,或者自行搜尋相關資料。

1、什麼是 Gradle 外掛

Gradle 是一個自動化構建工具,可以使用一種基於 Groovy 的特性領域語言(DSL)來宣告專案設定。Gradle 也提供了一些預設的 Plugin 幫助我們構建專案,如果想要在構建期間定製的操作,就需要單獨開發一款和自己功能相關的 Gradle Plugin。

而 Gradle Plugin,是可以使用 Groovy、Java、Scala 進行開發的。本身對 Scala 不熟悉,一般我都是使用 Groovy 來開發 Gradle 外掛,而 Groovy 又是可以和 Java 程式碼混編的,上手還算容易。

Gradle 的外掛,可以理解為編寫的一個庫,所以它和我們編寫的 Library一樣,有在專案內供本專案使用的,也有可以釋出出去,供其他專案使用的。

這兩種方式的區別:

  1. 在專案內的 build.gradle 檔案中編寫,或者直接以一個單獨的 Module 存在。這種方式的確定就是不方便移植和複用。
  2. 另外一種就是一個單獨的外掛,可以釋出出去,供其他專案使用的。有點是方便移植和複用。

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

2、Gradle 的 Project 介面

Project 介面是你寫的外掛程式碼和 Gradle 互動的主要介面,通過 Project 可以在外掛內,使用 Gradle 特性,而 Project 與 build.gradle 檔案是一對一的關係。

在 Gradle 中,通過 Extension 在不同的 Gradle Plugin 之間傳遞處理後的結果。

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

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

3、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()。

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

這差不多算是一個標準實現,可以看到它需要區分出是當前專案內的包,還有第三方庫的 Jar 包,進行單獨處理。

4、使用 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…

三、舉個例子

既然關鍵技術點已經介紹過了,那麼就以一個簡單的例子,試著編寫一個 Gradle Plugin ,在其中修改其內的 class 位元組碼,最簡單的,在構造方法中新增一行程式碼。

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

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

這裡查詢到需要的 class 檔案之後,首先判斷是否有構造方法,如果沒有構造方法就建立一個,然後在其構造方法內注入一行程式碼。

在 Transform.transform() 中,呼叫注入的方法。

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

在主專案中,新增這個 Gradle Plugin 外掛。

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

最終執行之後,我們來看看反編譯後的效果。

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

但是這樣實際上並沒有辦法修改第三方庫裡的類,這個需要我們特殊處理。前面已經提到,在 Transform 中,需要區分當前專案的目錄,而針對第三方庫的 Jar 包,我們需要先對其進行解壓,然後修改完成之後,再壓縮回去。

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

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

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

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

然後再重新壓縮成 Jar 包的方法也跟上。

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

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

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

這裡為了方便,指定需要解壓的 Jar 包,並且解壓在根目錄下。最終會重新打包成一個 Jar 包,給主專案引用。

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

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

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

最後看看整個專案的目錄結構。

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

四、結語

有需要 Demo 原始碼的朋友,可以在本公眾號回覆 "GradlePlugin" 獲得。

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

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

美團無埋點方案 - Gradle Plugin 的方式,在編譯期間修改 class | 掘金技術徵文
公眾號二維碼.jpg

相關文章