徹底認識包(依賴)衝突 小結

什麼是包(依賴)衝突?

     包衝突是指在一個項目的不同部分開發時調用的代碼庫、資源包的版本號不一致。 包衝突的實質是不同部分同一個功能實現用的代碼和資源不一致。
     舉例說明:在一個Android項目中,假設主工程是A ,它調用(依賴)代碼庫B版本爲2.0,簡稱B2.0,同時依賴代碼庫C,而C又依賴庫B1.0,可以看到項目不同部分依賴的B出現了版本不一致,這就叫包依賴衝突。

包(依賴)衝突會帶來什麼影響?

     在回答這個問題之前,我們首先要明確:在最終運行階段每個類是唯一的,也就是說一個代碼庫只有一個版本,所以在打包時只能選一個版本B打包進Apk中
     基於以上原因,不管採用B的版本2.0和1.0都貌似不妥,都有可能出現運行時候異常,比如C調用B1.0中一個類SayHello,假如該類在B2.0中被刪掉,那麼C的代碼中用到SayHello類就會在運行時報Class not found的錯誤。除了舉例中的錯誤,你還可能遇到各種各樣的運行錯誤,比如用的是B1.0版本,那麼主工程A發現B2.0已經解決的bug在運行中依舊存在。
     所以包衝突帶來最主要問題就是:運行時異常。
     談到這裏,你可能有一個疑惑:爲啥不把把整個項目各個模塊依賴的庫版本統一後再做編譯,有問題提前暴露出來,至少能夠有效避免Class not found這類運行期間crash。
     在回答這個問題之前先看下簡化的Android打包流程:

在編譯階段,有的依賴庫是已經編譯好的,並且是按照他們自己設定的依賴版本編譯好的,比如最開始那個Demo中,庫C是是按照B1.0編譯好的,但不包含Lib B的代碼。然後主工程編譯就是按照B2.0庫編譯,同樣不包含庫B代碼,Apk中三方庫的版本就是編譯時候主工程的確定的版本,所以最後會將B2.0打包進入dex中。同樣我們自己的Library Module跟三方庫一樣,也是提前先按照自己設定依賴編譯好,這裏也解釋了,爲啥有些三方庫引入後會出現編譯通過,運行報錯的bug。
      繼續上面疑問,爲啥打包流程這樣設計,不把把整個項目各個模塊依賴的庫版本統一後再做編譯?
      1、一般主工程和三方庫不是統一時期,同一批人做的,所以他們包含的同一個庫很大概率是不同的。 比如還是例子中的三方庫C,可能是團隊C在2016年已經做成,它當時用的是最新的庫B1.0,我們做主工程時候是2021年,這期間B從1.0 進化到2.0解決了不少Bug、代碼發生很大變化,我們做主工程當然要用最新的B2.0,但是團隊C已經解散,沒人去維護升級C庫,所以對於三方庫C來說,如果它不用它自己設定的B1.0提前編譯,而是提供源碼給我們,讓我們用2.0編譯,就會出現編譯不過的現象。
      2 如果要設計編譯階段版本統一,就可能要求三方庫把源碼提供給我們而不是已經編譯好的jar包,這顯然對三方庫的代碼安全性、商業機密造成嚴重泄漏。
      3 提前編譯好,可以使我們編譯時候,只需要編譯主工程和少量lib工程,大大加快了編譯速度。
     由於以上原因,我們很難在做到的整個項目各個模塊版本統一再編譯。
     其實上面也提到包衝突依賴可能造成的第二個問題:編譯失敗。這個問題暫且放一下,大家思考下,1 編譯失敗一般是指誰編譯失敗? 2 在什麼場景下會出現這個問題?這個問題答案會在解決包衝突中逐步揭開。

怎麼解決包(依賴)衝突?

     一切衝突解決的終極理想方案是開發調用版本統一! 比如之前例子中,我們添加庫B的時候,發現C中用了B1.0,那麼最好的解決方案就是,在滿足主工程A功能需求情況下A也用B1.0,如果B1.0不滿足,通知C升級到B2.0。
     雖然很多情況下做不到開發用統一版本,但我們可以把統一版本做爲衝突依賴的解決重要方向。比如庫B在從1.0升級到2.0時候,B2.0兼容所有B1.0所有功能,我們便可以放心把B2.0打包進最後Apk中。
     但現實往往不能通過這個理想方案去解決,比如下面的情況:

三方庫C和三方庫D使用的版本是B1.0 和B1.1,我們作爲主工程開發者很難去推動庫C 和庫D的開發者更新它們的依賴庫B,如果不是一個公司的更是不知道怎麼去有效推動。因爲整個項目Apk最後運行的時候,只能是B某一個版本,那這時間我們就要做各種各樣的折中平衡方案了,比如比如選擇B1.1作爲運行時候版本,把用到庫B的地方反覆測試,並祈禱他沒有測試到的地方也沒有問題,雖然聽起來很可笑,但這的確是事實。
     所以包衝突依賴解決思路大致如下流程:


現在就流程中用到的一些工具和方法做說明:

1如何檢測包依賴衝突

window 環境下->Andorid Studio命令行->gradlew app:dependencies > dps.txt
Mac環境下->Andorid Studio命令行->./gradlew app:dependencies > dpes
都會生成依賴圖,如下圖所示,就能看到每個庫和整個項目的依賴圖



當然命令還可以設置多種參數用來過濾顯示不同的關注部分。

2 如何設定選取的版本號

     如果不做特殊設定,主工程編譯和最後的項目運行都選擇的是依賴庫的最高版本號。可以決定庫版本號的工具主要有:
     force
     exclude
他們都是在主工程的構建配置文件 build.gradle中使用,也只是對主工程編譯起作用。其中force是強制使用一個庫的版本來編譯和運行,不去考慮是否有更高的版本;
exclude指明在依賴一個三方庫時候,不考慮它依賴的某個特定庫版本號對主工程編譯和最後運行使用的庫版本的影響。比如主工程A依賴B1.0,依賴C,C依賴B2.0,我們在添加依賴C的時候,採用exclude B,那麼主工程編譯成Apk決定使用B的版本時候就不考慮C依賴的B版本號。
具體使用可以通過搜索引擎搜:依賴衝突解決,Gradle依賴項學習等
比如:https://www.paincker.com/gradle-dependencies

小結

     包衝突是指在一個項目的不同部分引用(依賴)的代碼庫、資源包的版本號不一致,由於在最終運行階段,一個代碼庫只能存在一份,所以衝突解決的終極理想方案是開發用版本統一,但是現實情況往往做不到版本統一,於是我們需要用各種折中方案,找到一個最適合的版本庫,既可以能夠通過編譯,也能夠滿足項目業務需求。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章