隨著產品功能需求的增加,我們開發的安卓專案不得不入引入越來越多的第三方庫。這些三方庫可能以 Jar 包的形式放置在 libs 目錄下,可能以 Gradle 遠端依賴的形式下載引入,也可能是以 Library Module 的形式放置在工程目錄下,等等。
隨之而來的問題是,複雜的依賴關係很可能導致重複引入包的問題。比較常見的使用場景就是 support-v4 包的重複引入。這樣就會導致,執行 Run 操作打包生成 Apk 檔案時出現類似這樣的 DexException 錯誤提示,導致編譯失敗:
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;複製程式碼
有多種使用場景會出現這種問題。根據解決方案的不同,大體上可以分為兩種:本地 Jar 包重複嵌入和 Gradle 遠端重複依賴。
第一種,比較好理解。比如 app module 與 library module 各自 libs 目錄中嵌入了相同的 Jar 包。這種情況也比較好解決,只需要將 app module 下的重複 jar 包刪除即可。
第二種,稍微複雜一點。比如對於 Gradle 遠端依賴的兩個第三方庫,他們內部同時依賴相同的另一個輔助第三方庫。這個時候我們就沒辦法像第一種情況那樣手動刪除本地檔案。好在 Gradle 外掛提供了相應的解決方案,即使用 exclude group 語法,如:
compile 'com.yifeng.example:example-1:1.0'
compile 'com.yifeng.example:example-2:1.0'{
exclude group: 'com.android.support:support-v4:21.0.0'
}複製程式碼
如例子中所示,遠端依賴的第三方庫 example-1 與 example-2 內部同時引入 support-v4 包,那麼只需要在其中一個的引入地方新增 exclude group 語句,將相同引入的 v4 包剔除在外即可。
以上兩種場景算是比較好處理的。還有一種特殊情況,就是不同第三方庫內部出現相同包名相同檔名的 java 類。這種情況出現的概率很低,但是不幸的是我在工作中就遇見過。
當時專案中引入的 友盟統計 和 移動統一認證 的 SDK 出現重複引入問題,執行 Run 操作編譯打包時出現 duplicate entry 錯誤,如圖:
通過錯誤提示,很容易就找到錯誤出處,我們看下兩個 SDK 中的 jar 包原始碼:
雖然相同包名相同類名的檔案在不同 SDK 中出現的概率極低,但是一旦出現,處理起來就比較棘手。最好的解決方案就是聯絡提供 SDK 的技術人員反映問題,讓其通過修改原始碼重新打包一個新的 Jar 包。
還有一個解決辦法就是,重新命名 Jar 包裡的包名或者檔名。網上也有一個工具:jarjar.jar,可以幫助我們重新命名包名和檔名,以及 Jar 包中的相關程式碼引用路徑。參考地址如下:
GitHub 原始碼:github.com/shevek/jarj…
Jar 檔案下載:code.google.com/archive/p/j…
該工具提供有多種使用方式,最簡單實用的就是通過命令列使用。舉個例子,開啟命令列工具,執行:
java -jar jarjar-1.4.jar process rule.txt example.jar example_output.jar複製程式碼
其中,rule.txt 是包含重新命名規則的檔案,內容如下:
rule p.rn.asm.** com.yifeng.example.@1複製程式碼
我們看一下重新命名前後 Jar 包內容的程式碼對比圖:
可以看到,不僅包名改變,Jar 包中的相關類引用路徑也自動改變。這種 Jar 包重新命名的方式雖然能解決重複引入包的問題,但不是長久之計,需要後續持續關注 SDK 的升級替換。
不同 Jar 包包含相同檔案(路徑也相同)的情況還有一種,就是 duplicate files 錯誤。多個 Jar 包包含重複的檔案。這種情況在網上看到過,出自 StackOverFlow,錯誤提示類似:
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'
}
}複製程式碼
可以看到,解決方案已經在錯誤提示中有給出,使用 packagingOptions 配置的 exclude 語句刪除重複檔案即可,比如:
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}複製程式碼
最後再補充一點,關於 Gradle 依賴的 Scope 問題。通常依賴某個第三方庫我們使用的都是 compile 關鍵字,實際上,還有一個 Provided 關鍵字偶爾也會用到。
開啟 Project Structure,檢視 Modules 的 Dependencies 內容時,可以看到每一個依賴項的右邊有個 Scope 選項:
compile 表示依賴的第三方庫在工程編譯階段、測試階段和 Apk 執行階段都需要使用到;而 provided 表示該第三方庫僅僅是在編譯階段和測試階段使用到,不會出現在執行階段,即表示不會打包到最終的 apk 檔案中去,目標執行環境已經包含有該第三方庫。(貌似 Dagger2 的使用可能會涉及到這個問題。)
關於 Scope 的詳細介紹,可以參考文章:PROVIDED SCOPE IN GRADLE。
關於我:亦楓,部落格地址:yifeng.studio/,新浪微博:IT亦楓
微信掃描二維碼,歡迎關注我的個人公眾號:安卓筆記俠
不僅分享我的原創技術文章,還有程式設計師的職場遐想