隨著產(chǎn)品功能需求的增加,我們開發(fā)的安卓項(xiàng)目不得不入引入越來(lái)越多的第三方庫(kù)。這些三方庫(kù)可能以 Jar 包的形式放置在 libs 目錄下,可能以 Gradle 遠(yuǎn)程依賴的形式下載引入,也可能是以 Library Module 的形式放置在工程目錄下,等等。
隨之而來(lái)的問(wèn)題是,復(fù)雜的依賴關(guān)系很可能導(dǎo)致重復(fù)引入包的問(wèn)題。比較常見的使用場(chǎng)景就是 support-v4 包的重復(fù)引入。這樣就會(huì)導(dǎo)致,執(zhí)行 Run 操作打包生成 Apk 文件時(shí)出現(xiàn)類似這樣的 DexException 錯(cuò)誤提示,導(dǎo)致編譯失?。?/p>
Error:Execution failed for task ':app:transformClassesWithDexForDebug'.
> com.android.build.api.transform.TransformException:
com.android.ide.common.process.ProcessException:
java.util.concurrent.ExecutionException: com.android.dex.DexException:
Multiple dex files define Landroid/support/v4/app/ActivityCompatHoneycomb;
有多種使用場(chǎng)景會(huì)出現(xiàn)這種問(wèn)題。根據(jù)解決方案的不同,大體上可以分為兩種:本地 Jar 包重復(fù)嵌入和 Gradle 遠(yuǎn)程重復(fù)依賴。
第一種,比較好理解。比如 app module 與 library module 各自 libs 目錄中嵌入了相同的 Jar 包。這種情況也比較好解決,只需要將 app module 下的重復(fù) jar 包刪除即可。
第二種,稍微復(fù)雜一點(diǎn)。比如對(duì)于 Gradle 遠(yuǎn)程依賴的兩個(gè)第三方庫(kù),他們內(nèi)部同時(shí)依賴相同的另一個(gè)輔助第三方庫(kù)。這個(gè)時(shí)候我們就沒辦法像第
一種情況那樣手動(dòng)刪除本地文件。好在 Gradle 插件提供了相應(yīng)的解決方案,即使用 exclude 語(yǔ)法,如:
compile 'com.yifeng.example:example-1:1.0'
compile ('com.yifeng.example:example-2:1.0') {
exclude group: 'com.android.support', module: 'support-v4'
}
如例子中所示,遠(yuǎn)程依賴的第三方庫(kù) example-1 與 example-2 內(nèi)部同時(shí)引入 support-v4 包,那么只需要在其中一個(gè)的引入地方添加 exclude 語(yǔ)句,根據(jù) group 和 module 過(guò)濾規(guī)則,將相同引入的 v4 包剔除在外即可。
當(dāng)需要在一個(gè)依賴中去除多個(gè)遞歸依賴項(xiàng)時(shí),可以使用多條 exclude 語(yǔ)句,比如:
compile ('com.wdullaer:materialdatetimepicker:3.2.2') {
exclude group: 'com.android.support', module: 'support-v4'
exclude group: 'com.android.support', module: 'design'
}
還有一種更簡(jiǎn)單的寫法,使用 Groove 語(yǔ)言的循環(huán)語(yǔ)句,模板如下(同樣適用于 module 規(guī)則):
compile() { dep ->
[group1, group2].each{ group -> dep.exclude group: group }
}
對(duì)于上面的例子,便可以改造成:
compile() { dep ->
['support-v4', 'support-v13', 'design'].each{ module -> dep.exclude module: module }
}
更多細(xì)節(jié),參考:ModuleDependency。
以上兩種場(chǎng)景算是比較好處理的。還有一種特殊情況,就是不同第三方庫(kù)內(nèi)部出現(xiàn)相同包名相同文件名的 java 類。這種情況出現(xiàn)的概率很低,但是不幸的是我在工作中就遇見過(guò)。
當(dāng)時(shí)項(xiàng)目中引入的 友盟統(tǒng)計(jì) 和 移動(dòng)統(tǒng)一認(rèn)證 的 SDK 出現(xiàn)重復(fù)引入問(wèn)題,執(zhí)行 Run 操作編譯打包時(shí)出現(xiàn) duplicate entry 錯(cuò)誤,如圖:

通過(guò)錯(cuò)誤提示,很容易就找到錯(cuò)誤出處,我們看下兩個(gè) SDK 中的 jar 包源碼:


雖然相同包名相同類名的文件在不同 SDK 中出現(xiàn)的概率極低,但是一旦出現(xiàn),處理起來(lái)就比較棘手。最好的解決方案就是聯(lián)系提供 SDK 的技術(shù)人員反映問(wèn)題,讓其通過(guò)修改源碼重新打包一個(gè)新的 Jar 包。
還有一個(gè)解決辦法就是,重新命名 Jar 包里的包名或者文件名。網(wǎng)上也有一個(gè)工具:jarjar.jar,可以幫助我們重命名包名和文件名,以及 Jar 包中的相關(guān)代碼引用路徑。參考地址如下:
- GitHub 源碼:https://github.com/shevek/jarjar
- Jar 文件下載:https://code.google.com/archive/p/jarjar/downloads
該工具提供有多種使用方式,最簡(jiǎn)單實(shí)用的就是通過(guò)命令行使用。舉個(gè)例子,打開命令行工具,執(zhí)行:
java -jar jarjar-1.4.jar process rule.txt example.jar example_output.jar
其中,rule.txt 是包含重命名規(guī)則的文件,內(nèi)容如下:
rule p.rn.asm.** com.yifeng.example.@1
我們看一下重命名前后 Jar 包內(nèi)容的代碼對(duì)比圖:


可以看到,不僅包名改變,Jar 包中的相關(guān)類引用路徑也自動(dòng)改變。這種 Jar 包重命名的方式雖然能解決重復(fù)引入包的問(wèn)題,但不是長(zhǎng)久之計(jì),需要后續(xù)持續(xù)關(guān)注 SDK 的升級(jí)替換。
不同 Jar 包包含相同文件(路徑也相同)的情況還有一種,就是 duplicate files 錯(cuò)誤。多個(gè) Jar 包包含重復(fù)的文件。這種情況在網(wǎng)上看到過(guò),出自 StackOverFlow,錯(cuò)誤提示類似:
Error:duplicate files during packaging of APK E:\Code\iDoc\app\build\outputs\apk\app-debug-unaligned.apk
Path in archive: META-INF/license.txt
Origin 1: E:\Code\iDoc\app\libs\spring-core-3.1.0.RELEASE.jar
Origin 2: E:\Code\iDoc\app\libs\spring-web-3.1.0.RELEASE.jar
You can ignore those files in your build.gradle:
android {
packagingOptions {
exclude 'META-INF/license.txt'
}
}
可以看到,解決方案已經(jīng)在錯(cuò)誤提示中有給出,使用 packagingOptions 配置的 exclude 語(yǔ)句刪除重復(fù)文件即可,比如:
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}
最后再補(bǔ)充一點(diǎn),關(guān)于 Gradle 依賴的 Scope 問(wèn)題。通常依賴某個(gè)第三方庫(kù)我們使用的都是 compile 關(guān)鍵字,實(shí)際上,還有一個(gè) Provided 關(guān)鍵字偶爾也會(huì)用到。
打開 Project Structure,查看 Modules 的 Dependencies 內(nèi)容時(shí),可以看到每一個(gè)依賴項(xiàng)的右邊有個(gè) Scope 選項(xiàng):

compile 表示依賴的第三方庫(kù)在工程編譯階段、測(cè)試階段和 Apk 運(yùn)行階段都需要使用到;而 provided 表示該第三方庫(kù)僅僅是在編譯階段和測(cè)試階段使用到,不會(huì)出現(xiàn)在運(yùn)行階段,即表示不會(huì)打包到最終的 apk 文件中去,目標(biāo)運(yùn)行環(huán)境已經(jīng)包含有該第三方庫(kù)。(貌似 Dagger2 的使用可能會(huì)涉及到這個(gè)問(wèn)題。)
關(guān)于 Scope 的詳細(xì)介紹,可以參考文章:PROVIDED SCOPE IN GRADLE。