有關 Android Studio 重複引入包的問題和解決方案

逝雪飄零發表於2019-07-29

隨著產品功能需求的增加,我們開發的安卓專案不得不入引入越來越多的第三方庫。這些三方庫可能以 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 錯誤,如圖:

有關 Android Studio 重複引入包的問題和解決方案

通過錯誤提示,很容易就找到錯誤出處,我們看下兩個 SDK 中的 jar 包原始碼:

有關 Android Studio 重複引入包的問題和解決方案
友盟統計 SDK

有關 Android Studio 重複引入包的問題和解決方案
移動統一認證 SDK

雖然相同包名相同類名的檔案在不同 SDK 中出現的概率極低,但是一旦出現,處理起來就比較棘手。最好的解決方案就是聯絡提供 SDK 的技術人員反映問題,讓其通過修改原始碼重新打包一個新的 Jar 包。

還有一個解決辦法就是,重新命名 Jar 包裡的包名或者檔名。網上也有一個工具:jarjar.jar,可以幫助我們重新命名包名和檔名,以及 Jar 包中的相關程式碼引用路徑。參考地址如下:

該工具提供有多種使用方式,最簡單實用的就是通過命令列使用。舉個例子,開啟命令列工具,執行:

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 包內容的程式碼對比圖:

有關 Android Studio 重複引入包的問題和解決方案

有關 Android Studio 重複引入包的問題和解決方案

可以看到,不僅包名改變,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 選項:

有關 Android Studio 重複引入包的問題和解決方案

compile 表示依賴的第三方庫在工程編譯階段、測試階段和 Apk 執行階段都需要使用到;而 provided 表示該第三方庫僅僅是在編譯階段和測試階段使用到,不會出現在執行階段,即表示不會打包到最終的 apk 檔案中去,目標執行環境已經包含有該第三方庫。(貌似 Dagger2 的使用可能會涉及到這個問題。)

關於 Scope 的詳細介紹,可以參考文章:PROVIDED SCOPE IN GRADLE

關於我:亦楓,部落格地址:yifeng.studio/,新浪微博:IT亦楓

微信掃描二維碼,歡迎關注我的個人公眾號:安卓筆記俠

不僅分享我的原創技術文章,還有程式設計師的職場遐想

有關 Android Studio 重複引入包的問題和解決方案

相關文章