◉
在 Android 開發中,很多時候,我們可能想要修改第三方庫裡的某些類。那麼我們有兩個主流的方式來實現:
- 下載並引入第三方庫的所有原始碼,然後修改對應的類的原始碼
- 使用 ASM 等工具,直接修改對應類的位元組碼
第一種方式,優點是方法簡單不容易出錯,而且 Debug 的時候不會有什麼問題。缺點就是操作麻煩,升級的時候需要和上游進行 Rebase。而且當遇到 OkHttp 這種用 Maven 組織的專案,對慣用 Gradle 組織專案的 Android 工程師更是個災難。
第二種方式,優點是非侵入,不需要下載和引入第三方庫的原始碼。而缺點的話,就是實現相對複雜(涉及到位元組碼操作),而且因為位元組碼已被修改,沒法和原始碼對應,所以 Debug 的時候也可能出問題。
Duplicate Class
相信很多人在打包 Apk 的編譯期(Build Time)遇到過「Duplicate Class」的錯誤。原因是 Dex 構建工具在把所有類打包進同一個 Dex 的時候,如果發現如果有重複的類要打包進 Dex 的話,就會報錯。
而在編輯期(Edit Time)的時候,如果我們的所有原始碼中有重複類的話,IDE 也會提示錯誤。但是如果我們的原始碼裡有一個和第三方 Jar 包裡重複的類,IDE 是不會提示錯誤的。為什麼呢?
因為重複類並不在同一個 Archive 裡。Archive 可以看作類的容器,可以是一個 Jar 包,也可以是一個 Dex。當然你某個 Module 中的所有原始碼,也可以想像成是一個 Archive,所以原始碼有重複類是會提示錯誤的。
總結以下,在原始碼中的類和 Jar 包裡的類有重複的情況下,在編輯期是不會提示錯誤的。只有當到了編譯期,構建工具將你原始碼編譯出來的類,和第三方 Jar 裡的重複的類,混合到同一個 Archive 裡的時候才會報錯。
偷天換日
既然,IDE 允許原始碼中有和 Jar 裡的類重複的類。那麼,對於開頭提到的問題,我們可以提出一種新的解決方案:
只把 Jar 裡想要修改的類的對應原始碼拷貝到專案中修改,然後在最終編譯的時候把 Jar 包裡這些類給刪除掉。保證在打包進 Dex 的時候只有我們拷貝的原始碼編譯出來的類即可。
JarFilterPlugin
我們可以使用 JarFilterPlugin 來幫我們在最後打包進 Dex 之前,把所有依賴的 Jar 包裡指定的 Class 檔案全部過濾掉。
我們先把 Plugin 引入到我們的專案中:
buildscript {
repositories {
maven { url "https://jitpack.io" }
}
dependencies {
classpath "com.github.nekocode:JarFilterPlugin:1.1"
}
}
複製程式碼
然後我們開始使用外掛來過濾 Class 檔案了。
假設我們想要修改 Android Support V7 包裡的 AppCompatActivity 類。我們只需要把 AppCompatActivity 類的原始碼拷貝到我們的專案中,然後在 App Module 的 budil.gradle 下新增如下配置:
apply plugin: 'jar-filter'
jarFilter {
skipFiles = [
'android/support/v7/app/AppCompatActivity.class',
'android/support/v7/app/AppCompatActivity\\$(.*).class'
]
}
複製程式碼
這樣,在執行 Assemble 任務打包出的 Apk 中就只會有我們拷貝的 AppCompatActivity 類。