前言
大家好,我是小彭液南。
在前文 Gradle 構(gòu)建工具 #3 Maven 發(fā)布插件使用攻略(以 Nexus / Jitpack 為例) 和 Gradle 構(gòu)建工具 #4 來(lái)開(kāi)源吧壳猜!發(fā)布開(kāi)源組件到 MavenCentral 倉(cāng)庫(kù)超詳細(xì)攻略 文章中,我們已經(jīng)討論過(guò)如何發(fā)布組件到 Nexus 企業(yè)私有倉(cāng)庫(kù)或 MavenCentral 中央倉(cāng)庫(kù)的方法滑凉。
在發(fā)布組件的新版本時(shí)统扳,開(kāi)發(fā)者需要描述該組件的 GAV 基本信息,包括:groupId畅姊、artifactId咒钟、version 和 packaging 等。在協(xié)同開(kāi)發(fā)的另一側(cè)若未,依賴(lài)方也需要通過(guò)相同的 GAV 坐標(biāo)來(lái)定位依賴(lài)項(xiàng):
build.gradle
dependencies {
implementation 'io.github.pengxurui:modular-eventbus-annotation:1.0.0'
}
然而朱嘴,當(dāng)工程中的依賴(lài)關(guān)系增多就很容易會(huì)遇到依賴(lài)版本沖突問(wèn)題,這個(gè)時(shí)候 Gradle 構(gòu)建工具是否有統(tǒng)一的規(guī)則來(lái)處理沖突,而開(kāi)發(fā)者又需要采用什么樣的手段來(lái)應(yīng)對(duì)沖突呢萍嬉?
目錄
1乌昔、如何聲明依賴(lài)版本?
1.1 靜態(tài)版本與不穩(wěn)定版本的區(qū)別(What & What's Diff)
1.2 動(dòng)態(tài)版本和變化版本的區(qū)別(What & What's Diff)
1.3 如何調(diào)整不穩(wěn)定版本的解析策略(How)
2壤追、依賴(lài)沖突是怎么發(fā)生的磕道?
2.1 什么是依賴(lài)傳遞(What)
2.2 什么是依賴(lài)沖突(What)
2.3 如何查看依賴(lài)版本沖突(How)
3、Gradle 依賴(lài)版本決議
3.1 對(duì)比 Maven 和 Gradle 的解析策略(What’s Diff)
3.2 版本排序規(guī)則(Detail)
3.3 Dependency API:strictly行冰、require溺蕉、reject、prefer资柔、exclude焙贷、transitive(Detail)
3.4 DependencyConstraintHandler API(Detail)
3.5 ResolutionStrategy API(Detail)
4撵割、總結(jié)
1. 如何聲明依賴(lài)版本贿堰?
首先,我們先盤(pán)點(diǎn)出 Gradle 構(gòu)建系統(tǒng)中聲明依賴(lài)版本的方式:
1.1 靜態(tài)版本與不穩(wěn)定版本
在 Gradle 構(gòu)建聲明依賴(lài)的語(yǔ)法想必各位都了然于胸了:
build.gradle
dependencies {
// 簡(jiǎn)寫(xiě)格式
implementation 'com.google.guava:guava.20.0'
// 完整格式:
implementation group 'com.google.guava', name: 'guava:guava', version '20.0'
}
其實(shí) Gradle 不僅支持精確地指定版本號(hào)外啡彬,還支持豐富的版本聲明方法羹与,我這里總結(jié)了一些比較實(shí)用的使用方式:
靜態(tài)版本(精確版本): 最簡(jiǎn)單的方式,例如 1.1
區(qū)間版本: 使用 () 或 [] 定義開(kāi)閉區(qū)間庶灿,例如 [1.0,) 表示高于 1.0 版本
前綴版本: 通過(guò)
+
指定版本號(hào)前綴纵搁,相當(dāng)于特殊的區(qū)間版本,例如 1.1.+最新版本: 通過(guò) latest-status 指定最新版本往踢,例如 latest-release
SNAPSHOT 版本: Maven 風(fēng)格的快照版本腾誉,例如 1.1-SNAPSHOT
除了精確版本外,其它所有的版本聲明方式的構(gòu)建都是不穩(wěn)定的峻呕,比如 [1.0,)
到底是依賴(lài) 1.1 還是 1.2利职?而 1.1.+
到底是依賴(lài) 1.1.0 還是 1.1.1?
那么瘦癌,這些不穩(wěn)定版本存在的意義是什么猪贪?
1.2 如何理解兩種不穩(wěn)定版本 —— 動(dòng)態(tài)版本和變化版本
我原本是計(jì)劃將靜態(tài)版本以外的聲明方式理解為「動(dòng)態(tài)版本」,但是按照 Gradle 官方文檔來(lái)理解的話(huà)讯私,其實(shí)會(huì)細(xì)分為「Dynamic Version 動(dòng)態(tài)版本」和「Changing Version 變化版本」热押,為避免混淆概念,我們就統(tǒng)一將后者理解為「不穩(wěn)定版本」好了斤寇。
可是桶癣,Gradle 官方的也未免太學(xué)術(shù)化了吧 ?? 應(yīng)該如何理解呢?
一句話(huà)概括:
「動(dòng)態(tài)版本是版本不穩(wěn)定娘锁,變化版本是產(chǎn)物不穩(wěn)定」
- Dynamic 動(dòng)態(tài)版本
動(dòng)態(tài)版本是指版本號(hào)不固定的聲明方式鬼廓,例如前面提到的區(qū)間版本、前綴版本和最新版本都屬于動(dòng)態(tài)化版本致盟,最終依賴(lài)的版本號(hào)之后在構(gòu)建時(shí)才能確定(如 2.+?2.3 只有在構(gòu)建時(shí)才能確定)碎税。
因此尤慰,動(dòng)態(tài)版本適合用在強(qiáng)調(diào)使用依賴(lài)項(xiàng)最新版本的場(chǎng)景,項(xiàng)目會(huì)更加積極地?fù)肀б蕾?lài)項(xiàng)的最新版本雷蹂,當(dāng)倉(cāng)庫(kù)中存在依賴(lài)項(xiàng)的最新版本時(shí)伟端,動(dòng)態(tài)版本直接解析為依賴(lài)項(xiàng)的最新版本(還需要滿(mǎn)足緩存超時(shí)的前提)。
- Changing 變化版本
變化版本是指版本號(hào)固定但產(chǎn)物不固定的聲明方式匪煌,比如 Maven 的 SNAPSHOT 快照版本责蝠。快照版本會(huì)在每次構(gòu)建時(shí)到遠(yuǎn)程倉(cāng)庫(kù)中檢查依賴(lài)項(xiàng)產(chǎn)物的最新版本(還需要滿(mǎn)足緩存超時(shí)的前提)萎庭。
例如霜医,在大型軟件項(xiàng)目中,往往是多個(gè)團(tuán)隊(duì)(或多名同學(xué))協(xié)同開(kāi)發(fā)不同模塊驳规,例如 A 模塊依賴(lài) B 模塊肴敛,兩個(gè)模塊并行開(kāi)發(fā)。如果模塊 B 不使用快照版本(例如版本為 1.0.0)吗购,那么當(dāng) B 模塊在開(kāi)發(fā)階段需要更新医男,A 模塊就無(wú)法接收到更新。因?yàn)?A 模塊本地倉(cāng)庫(kù)中已經(jīng)下載了 B 模塊的 1.0.0 版本捻勉,所以構(gòu)建時(shí)不會(huì)重復(fù)去下載遠(yuǎn)程倉(cāng)庫(kù)中更新的版本镀梭。
直接的解決辦法可以清除 A 模塊的本地倉(cāng)庫(kù)緩存,或者每次 B 模塊更新都升級(jí)版本踱启,很顯然兩個(gè)辦法都不靈活报账,頻繁升級(jí)版本也是對(duì)版本號(hào)的濫用,不利于版本管理埠偿。而如果模塊 B 使用快照版本(1.0.0-SNAPSHOT)透罢,A 模塊每次構(gòu)建都會(huì)去檢查遠(yuǎn)程倉(cāng)庫(kù)是否有 B 模塊的新快照(還需要滿(mǎn)足緩存超時(shí)的前提),就可以保證一直依賴(lài) B 模塊的最新版本胚想。
總的來(lái)說(shuō)琐凭,動(dòng)態(tài)版本傾向于積極擁抱最新版本,而快照版本傾向于積極集成開(kāi)發(fā)版本浊服,要根據(jù)具體的協(xié)同開(kāi)發(fā)場(chǎng)景來(lái)選擇统屈,在實(shí)踐經(jīng)驗(yàn)中,變化版本(快照版本)的使用頻率更大牙躺。
需要注意的是:這兩種版本均不應(yīng)該用在生產(chǎn)環(huán)境配置中愁憔,因?yàn)檫@兩種不穩(wěn)定版本共同存在的問(wèn)題是: 「輸入相同的構(gòu)建配置可能會(huì)產(chǎn)生不同的構(gòu)建產(chǎn)物輸出」 ,會(huì)導(dǎo)致重復(fù)構(gòu)建正式產(chǎn)物的不確定性孽拷。在實(shí)踐中吨掌,也確實(shí)暴露過(guò)一些不穩(wěn)定版本濫用而造成的生產(chǎn)事故,最終我和同事優(yōu)化了這個(gè)問(wèn)題,這個(gè)我們后文再分享(沒(méi)錯(cuò)膜宋,我又來(lái)挖坑了)窿侈。
1.3 調(diào)整不穩(wěn)定版本的解析策略
在默認(rèn)情況下, Gradle 會(huì)按照 24 小時(shí)緩存有效期緩存動(dòng)態(tài)版本和變化版本的解析結(jié)果秋茫,在緩存有效期間史简,Gradle 不會(huì)檢查遠(yuǎn)程倉(cāng)庫(kù)來(lái)獲取最新的依賴(lài)項(xiàng)。在默認(rèn)配置的基礎(chǔ)上肛著,Gradle 還提供了「時(shí)間和鎖定」兩個(gè)層面來(lái)控制不穩(wěn)定版本的解析策略的 API:
By default, Gradle caches changing versions of dependencies for 24 hours, …
By default, Gradle caches dynamic versions and changing modules for 24 hours, …
- 修改緩存時(shí)間
通過(guò)修改依賴(lài)分組的 ResolutionStrategy 決議策略對(duì)象圆兵,可以修改緩存時(shí)間:
build.gradle
configurations.all {
// 修改 Dynamic 版本的緩存時(shí)間
resolutionStrategy.cacheDynamicVersionsFor 10, 'minutes'
// 修改 Changing 版本的緩存時(shí)間
resolutionStrategy.cacheChangingModulesFor 10, 'minutes'
}
- 鎖定動(dòng)態(tài)版本
通過(guò)控制依賴(lài)分組的 ResolutionStrategy 決議策略對(duì)象,可以設(shè)置版本鎖定枢贿,但只針對(duì)動(dòng)態(tài)版本有效殉农,對(duì)于變化版本(快照版本)不生效。版本鎖定的細(xì)節(jié)比較多局荚,目前在社區(qū)上沒(méi)查找到開(kāi)發(fā)者的應(yīng)用實(shí)踐超凳,我們就先不展開(kāi)了(又挖坑?)
build.gradle
configurations {
compileClasspath {
resolutionStrategy.activateDependencyLocking()
}
}
????♀? 現(xiàn)在有一個(gè)疑問(wèn):既然 Gradle 都會(huì)按照解析規(guī)則選擇精確精確版本或者不穩(wěn)定版本的最新版本危队。那么聪建,我們說(shuō)的依賴(lài)沖突到底是怎么發(fā)生的呢钙畔?
2. 依賴(lài)沖突是怎么發(fā)生的茫陆?
2.1 什么是依賴(lài)傳遞?
用最簡(jiǎn)單的話(huà)說(shuō)擎析,A 依賴(lài) B簿盅,B 依賴(lài) C,那么 A 也會(huì)依賴(lài) C揍魂,這就是依賴(lài)傳遞桨醋。
在 Gradle 生命周期的配置階段,Gradle 會(huì)解析組件之間的依賴(lài)關(guān)系现斋。當(dāng)一個(gè)組件被添加到依賴(lài)關(guān)系圖中時(shí)喜最,還會(huì)遞歸地解析該組件所依賴(lài)的其他組件,同時(shí)將「間接依賴(lài)」也添加到依賴(lài)關(guān)系圖中庄蹋,直到組件自身沒(méi)有依賴(lài)時(shí)終止瞬内。
- Direct Dependency 直接依賴(lài)
表示模塊需要直接依賴(lài)和使用的特性,例如模塊依賴(lài)了 com.squareup.okhttp3:okhttp
限书,那么 OkHttp 就是直接依賴(lài)虫蝶;
- Transitive Dependency 間接依賴(lài)
如果在被直接依賴(lài)的組件中,如果該組件還依賴(lài)了其他組件倦西,那么其它組件就被間接依賴(lài)能真,例如 com.squareup.okio:okio
Okio 就是間接依賴(lài)。
這就是 Gradle 的依賴(lài)傳遞,很容易理解吧粉铐。
2.2 什么是依賴(lài)依賴(lài)沖突疼约?
在大型項(xiàng)目中,當(dāng)工程中的依賴(lài)關(guān)系增多就很容易會(huì)遇到依賴(lài)沖突問(wèn)題蝙泼,想必各位在工作中也遇到過(guò)各種各樣的依賴(lài)沖突問(wèn)題忆谓。你遇到過(guò)什么樣的依賴(lài)沖突問(wèn)題,可以在評(píng)論區(qū)發(fā)表一下觀點(diǎn) ??
社區(qū)中通常會(huì)將依賴(lài)沖突和依賴(lài)版本沖突劃上等號(hào)踱承,比如 20 年百度 App 技術(shù)團(tuán)隊(duì)的公開(kāi)資料 《Gradle 與 Android 構(gòu)建入門(mén)》倡缠。其實(shí),如果我們結(jié)合實(shí)踐中暴露的問(wèn)題茎活,Gradle 的依賴(lài)沖突可以細(xì)分為 2 類(lèi)問(wèn)題:
Version Conflict 版本沖突: 在項(xiàng)目依賴(lài)關(guān)系圖中昙沦,某個(gè)依賴(lài)項(xiàng)存在多個(gè)版本;
Implementation conflict 實(shí)現(xiàn)沖突: 在項(xiàng)目依賴(lài)關(guān)系圖中载荔,多個(gè)依賴(lài)項(xiàng)存在相同實(shí)現(xiàn)盾饮。
版本沖突大家都很熟悉,我們今天要討論就是版本決議問(wèn)題懒熙。
那么「實(shí)現(xiàn)沖突」又怎么理解呢丘损,兩個(gè)組件存在相同實(shí)現(xiàn)聽(tīng)起來(lái)就很離譜啊 ??
其實(shí)把 Build Output 報(bào)錯(cuò)日志貼出來(lái),你就懂了工扎。
Build Output
> Task :app:checkDebugDuplicateClasses FAILED
Duplicate class org.objectweb.asm.AnnotationVisitor found in modules asm-3.3.1 (asm:asm:3.3.1) and asm-4.0 (org.ow2.asm:asm:4.0)
Duplicate class org.objectweb.asm.AnnotationWriter found in modules asm-3.3.1 (asm:asm:3.3.1) and asm-4.0 (org.ow2.asm:asm:4.0)
...
由于項(xiàng)目依賴(lài)中 "asm:asm:3.3.1" 和 "org.ow2.asm:asm:4.0" 都存在相同的 ASM 特性徘钥,所以當(dāng)依賴(lài)關(guān)系樹(shù)中存在兩個(gè)相同實(shí)現(xiàn)時(shí),構(gòu)建就 Fail 掉了肢娘,不可能同一個(gè)類(lèi)打包兩份對(duì)吧呈础。
build.gradle
dependencies {
implementation "asm:asm:3.3.1"
implementation "org.ow2.asm:asm:4.0"
}
源碼
// asm:asm:3.3.1
package org.objectweb.asm;
public interface AnnotationVisitor {
}
// org.ow2.asm:asm:4.0
package org.objectweb.asm;
public abstract class AnnotationVisitor {
}
老司機(jī)們見(jiàn)多識(shí)廣,懂的都懂 ??
2.3 如何查看依賴(lài)版本沖突橱健?
相比于依賴(lài)實(shí)現(xiàn)沖突而钞,依賴(lài)版本沖突通常更加隱蔽,畢竟不同版本之間會(huì)考慮兼容性拘荡,所以構(gòu)建時(shí)不會(huì)直接構(gòu)建失斁式凇(構(gòu)建成功不代表運(yùn)行時(shí)不會(huì) Crash,這是一個(gè)坑哦 ??)
那么珊皿,我們?cè)趺床榭垂こ讨写嬖诘囊蕾?lài)版本沖突呢网缝,方法比較多:
1、Task dependencies
2亮隙、Task dependencyInsight
3途凫、Build Scan
4、新版 Android Studio 的 Gradle Dependency Analyzer 分析器(推薦)
// 依賴(lài)樹(shù)信息:
androidx.savedstate:savedstate-ktx:1.2.0
androidx.annotation:annotation:1.0.0 -> 1.5.0 (*)
org.jetbrains.kotlin:kotlin-stdlib:1.7.10 (*)
androidx.collection:collection:{strictly 1.0.0} -> 1.0.0 (c)
>
:表示沖突溢吻,比如這個(gè)1.1.0 -> 1.3.0维费,>
表示 1.1.0 版本被拉高到 1.3.0果元;:
表示省略不重要的層級(jí);c
:c 是constraints
的簡(jiǎn)稱(chēng)犀盟,表示 DependencyConstraintHandler API 約束的版本而晒;strictly
:表示 Dependency API strictly 強(qiáng)制指定的版本。
理解了依賴(lài)傳遞和依賴(lài)沖突后阅畴,現(xiàn)在我們來(lái)討論 Gradle 的依賴(lài)版本決議機(jī)制:
3. Gradle 依賴(lài)版本決議
比如以下依賴(lài)關(guān)系中倡怎,項(xiàng)目工程中直接或間接依賴(lài) OkHttp 的兩個(gè)版本,可以看到依賴(lài)關(guān)系樹(shù)上存在 okhttp:3.10.0 和 okhttp 3.14.9 兩個(gè)版本:
直接依賴(lài) com.squareup.okhttp3:okhttp:3.10.0
直接依賴(lài) com.squareup.retrofit2:retrofit:2.9.0 → com.squareup.okhttp3:okhttp:3.14.9
dependencies {
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.okhttp3:okhttp:3.10.0"
}
現(xiàn)在的問(wèn)題是:Gradle 應(yīng)該選擇哪個(gè)依賴(lài)項(xiàng)版本呢?
這就是版本決議(Dependency Resolution)要討論的問(wèn)題贱枣,結(jié)論先行 ????
Gralde 依賴(lài)版本決議會(huì)綜合考慮依賴(lài)關(guān)系圖上所有的直接依賴(lài)监署、間接依賴(lài)和依賴(lài)約束規(guī)則(API),并從中選擇出符合所有約束規(guī)則的最高依賴(lài)項(xiàng)版本纽哥。如果不存在滿(mǎn)足約束規(guī)則的依賴(lài)項(xiàng)版本钠乏,則會(huì)拋出構(gòu)建失敗錯(cuò)誤。
When Gradle attempts to resolve a dependency to a module version, all dependency declarations with version, all transitive dependencies and all dependency constraints for that module are taken into consideration. The highest version that matches all conditions is selected. If no such version is found, Gradle fails with an error showing the conflicting declarations. —— 官方文檔原文
我把這個(gè)結(jié)論可視化出來(lái)春塌,就很清晰了:
我們把依賴(lài)信息打印出來(lái)晓避,也確實(shí)是采用最高版本:
+--- com.squareup.retrofit2:retrofit:2.9.0
| \--- com.squareup.okhttp3:okhttp:3.14.9
| \--- com.squareup.okio:okio:1.17.2
\--- com.squareup.okhttp3:okhttp:3.10.0 -> 3.14.9 (*)
3.1 對(duì)比 Maven 和 Gradle 的解析策略
不同的構(gòu)建系統(tǒng)設(shè)計(jì)的解析策略不同,我們以 Maven 為對(duì)比:
- Maven 最短路徑策略
Maven 構(gòu)建系統(tǒng)會(huì)采用最短路策略只壳,構(gòu)建系統(tǒng)會(huì)選擇從根模塊到依賴(lài)項(xiàng)的最短路來(lái)選擇版本俏拱。例如在本節(jié)開(kāi)頭的例子總,在 Maven 構(gòu)建系統(tǒng)中就會(huì)選擇 com.squareup.okhttp3:okhttp:3.10.0
這個(gè)版本吼句。
- Gradle 最高版本策略
Gradle 構(gòu)建系統(tǒng)會(huì)采用最高版本策略锅必,構(gòu)建系統(tǒng)會(huì)選擇依賴(lài)關(guān)系圖中滿(mǎn)足約束規(guī)則的最高版本。例如在本節(jié)開(kāi)頭的例子中命辖,在 Gradle 構(gòu)建系統(tǒng)中就會(huì)選擇 com.squareup.okhttp3:okhttp:3.14.9
這個(gè)版本况毅。
一個(gè)誤區(qū): 需要避免混淆的是分蓖,在 Gradle 中使用 Maven 倉(cāng)庫(kù)尔艇,并不會(huì)左右 Gradle 的沖突解決策略,這里的 Maven 倉(cāng)庫(kù)僅用于提供依賴(lài)項(xiàng)么鹤,而依賴(lài)管理依然是在 Gradle 的框架內(nèi)運(yùn)行的终娃。
3.2 版本排序規(guī)則(面試題)
OK,既然在出現(xiàn)版本沖突時(shí)蒸甜,Gradle 會(huì)選擇依賴(lài)關(guān)系圖中最高的版本號(hào)棠耕,那么版本號(hào)的排序規(guī)則是怎樣的呢?比如 1.1.0-alpha 和 1.0.0 會(huì)選擇哪個(gè)版本呢柠新?完整的規(guī)則文檔在 Declaring Versions and Ranges 中窍荧。
有毒啊,文檔這也太復(fù)雜了哦恨憎,我將整個(gè)文檔提煉為 3 條基本規(guī)則蕊退,已經(jīng)可以滿(mǎn)足大部分開(kāi)發(fā)場(chǎng)景了:
-
1郊楣、分段對(duì)比規(guī)則
版本號(hào)字符串會(huì)被分隔符劃分為多個(gè)分段,高分段優(yōu)先:- 1.1 分隔符: 支持使用 [.-_+] 分隔符瓤荔,分隔符沒(méi)有差異净蚤,即 1.a.1 == 1-a-1
- 1.2 字母和數(shù)字分開(kāi): 字母和數(shù)字會(huì)劃分為不同分段,即 1a1 存在三個(gè)級(jí)別输硝,和 1a1 == 1.a.1
- 1.3 高級(jí)別優(yōu)先: 高級(jí)別分段優(yōu)先確定版本高低今瀑,即 2.1 > 1.2
-
2、同分段對(duì)比規(guī)則
同分段中点把,數(shù)字按數(shù)值排序橘荠,數(shù)字優(yōu)先于字母:- 2.1 數(shù)字版本高于字母版本: 即 1.1 > 1.a
- 2.2 數(shù)字版本按數(shù)值排序: 即 1.10 > 1.2(易錯(cuò),并不是按照「字典排序」規(guī)則郎逃,如果按照字典排序 1.2 > 1.10)
- 2.3 字母版本按字母順序排序砾医,大寫(xiě)優(yōu)先: 即 1.Bc > 1.B > 1.A > 1.a
-
3、特殊字符串規(guī)則
特殊字符串有特殊的排序規(guī)則:- 3.1 發(fā)布序列: 即 1.0-dev < 1.0-alpha- < 1.0-rc < 1.0-release < 1.0
- 3.2 snapshot 快照版本低于正式版本: 即 1.0-rc < 1.0-snapshot < 1.0-release < 1.0
就是說(shuō) Gradle 會(huì)分段對(duì)齊對(duì)比衣厘,字母和數(shù)字屬于不同分段如蚜,而同級(jí)別分段按照數(shù)值排序,而不是字典序排序影暴。OK错邦,那我明白了,按規(guī)則排列 1.1.0-alpha < 1.0.0 的型宙,因此會(huì)選擇 1.0.0(Gradle 最高版本策略)這個(gè)版本撬呢。
雖然 Gradle 在平臺(tái)層提供了一套依賴(lài)解析決議機(jī)制,但 Gradle 版本決議的默認(rèn)規(guī)則是選擇的最高版本妆兑,最高版本不一定與項(xiàng)目兼容魂拦,所以開(kāi)發(fā)者有時(shí)候要使用版本決議規(guī)則 API 來(lái)配置和干預(yù) Gradle 的決議規(guī)則。
3.3 Dependency API
- strictly 嚴(yán)格版本: 強(qiáng)制選擇此版本搁嗓,由于 Gradle 采用高版本優(yōu)先策略芯勘,因此 strictly 的應(yīng)用場(chǎng)景是為了降低版本(等價(jià)于 !! 雙感嘆號(hào)語(yǔ)法):
dependencies {
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation("com.squareup.okhttp3:okhttp") {
version{
strictly("3.10.0")
}
}
// 等價(jià)于
implementation("com.squareup.okhttp3:okhttp:3.10.0!!")
}
+--- com.squareup.retrofit2:retrofit:2.9.0
| \--- com.squareup.okhttp3:okhttp:3.14.9 -> 3.10.0
| \--- com.squareup.okio:okio:1.14.0
\--- com.squareup.okhttp3:okhttp:{strictly 3.10.0} -> 3.10.0 (*)
- require 最低版本: 不低于此版本
dependencies {
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation("com.squareup.okhttp3:okhttp") {
version{
require("3.10.0")
}
}
}
+--- com.squareup.retrofit2:retrofit:2.9.0
| \--- com.squareup.okhttp3:okhttp:3.14.9
| \--- com.squareup.okio:okio:1.17.2
\--- com.squareup.okhttp3:okhttp:3.10.0 -> 3.14.9 (*)
- reject 拒絕版本: 拒絕選擇此版本
dependencies {
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation("com.squareup.okhttp3:okhttp") {
version{
reject("3.10.0")
}
}
}
+--- com.squareup.retrofit2:retrofit:2.9.0
| \--- com.squareup.okhttp3:okhttp:3.14.9
| \--- com.squareup.okio:okio:1.17.2
\--- com.squareup.okhttp3:okhttp:{reject 3.10.0} -> 3.14.9 (*)
- prefer 優(yōu)先版本: 如果不存在更高版本,則優(yōu)先使用此版本腺逛。
dependencies {
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation("com.squareup.okhttp3:okhttp") {
version{
prefer("3.10.0")
}
}
}
+--- com.squareup.retrofit2:retrofit:2.9.0
| \--- com.squareup.okhttp3:okhttp:3.14.9
| \--- com.squareup.okio:okio:1.17.2
\--- com.squareup.okhttp3:okhttp:{prefer 3.10.0} -> 3.14.9 (*)
需要注意的時(shí)荷愕,strictly 和 require 語(yǔ)句會(huì)相互覆蓋,要以最后聲明的語(yǔ)句為準(zhǔn)棍矛,strictly 和 require 語(yǔ)句還會(huì)清除之前聲明的 reject 語(yǔ)句安疗,因此應(yīng)該把 reject 語(yǔ)句放在最后。
dependencies {
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation("com.squareup.okhttp3:okhttp") {
version{
require '3.10.0'
reject '3.14.9'
strictly '4.10.0'
prefer '3.10.0'
}
}
}
+--- com.squareup.retrofit2:retrofit:2.9.0
| \--- com.squareup.okhttp3:okhttp:3.14.9 -> 4.10.0
| +--- com.squareup.okio:okio:3.0.0
| | \--- com.squareup.okio:okio-jvm:3.0.0
| | +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31 -> 1.7.20 (*)
| | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31 -> 1.7.20
| \--- org.jetbrains.kotlin:kotlin-stdlib:1.6.20 -> 1.7.20 (*)
\--- com.squareup.okhttp3:okhttp:{strictly 4.10.0; prefer 3.10.0} -> 4.10.0 (*)
- exclude 排除規(guī)則
使用 exclude 可以根據(jù) GAV 坐標(biāo)排除間接依賴(lài)够委,也常用于解決前面提到的依賴(lài)實(shí)現(xiàn)沖突問(wèn)題荐类。
dependencies {
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation("com.squareup.okhttp3:okhttp") {
version{
require '3.10.0'
reject '3.14.9'
strictly '4.10.0'
prefer '3.10.0'
}
}
}
+--- com.squareup.retrofit2:retrofit:2.9.0
\--- com.squareup.okhttp3:okhttp:3.10.0
\--- com.squareup.okio:okio:1.14.0
- transitive 傳遞規(guī)則
使用 transitive 可以控制是否傳遞間接依賴(lài):
dependencies {
implementation("com.squareup.retrofit2:retrofit:2.9.0") {
transitive(false) // 不傳遞
}
implementation("com.squareup.okhttp3:okhttp:3.10.0")
}
+--- com.squareup.retrofit2:retrofit:2.9.0
\--- com.squareup.okhttp3:okhttp:3.10.0
\--- com.squareup.okio:okio:1.14.0
3.4 DependencyConstraintHandler API
constraints 約束規(guī)則提供了一個(gè)統(tǒng)一的位置來(lái)控制項(xiàng)目的依賴(lài)版本,而在聲明依賴(lài)的位置甚至可以不需要指定版本茁帽。但是如果模塊想單獨(dú)編譯玉罐,那么還是需要指定版本的真竖,畢竟沒(méi)有約束源就無(wú)法確定版本。
子模塊 build.gradle
dependencies {
implementation("com.squareup.retrofit2:retrofit") // 不指定版本
implementation("com.squareup.okhttp3:okhttp:3.10.0") // 指定 3.10.0
}
主模塊 build.gradle
dependencies {
implementation project(':mylibrary')
}
dependencies {
constraints {
implementation 'com.squareup.retrofit2:retrofit:2.9.0' // 指定版本
implementation('com.squareup.okhttp3:okhttp') {
version {
strictly("4.10.0") // 強(qiáng)制修改版本
}
}
}
}
打印子模塊的依賴(lài)信息:
+--- com.squareup.retrofit2:retrofit FAILED // 無(wú)法解析(單獨(dú)編譯缺少約束來(lái)源)
\--- com.squareup.okhttp3:okhttp:3.10.0
\--- com.squareup.okio:okio:1.14.0
打印主模塊的依賴(lài)信息:
+--- project :mylibrary
| +--- com.squareup.retrofit2:retrofit -> 2.9.0
| | \--- com.squareup.okhttp3:okhttp:3.14.9 -> 4.10.0 // 強(qiáng)制修改版本
| | +--- com.squareup.okio:okio:3.0.0
| | | \--- com.squareup.okio:okio-jvm:3.0.0
| | | +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31 -> 1.7.20 (*)
| | | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31 -> 1.7.20
| | \--- org.jetbrains.kotlin:kotlin-stdlib:1.6.20 -> 1.7.20 (*)
| \--- com.squareup.okhttp3:okhttp:3.10.0 -> 4.10.0 (*)
+--- com.squareup.retrofit2:retrofit:2.9.0 (c)
\--- com.squareup.okhttp3:okhttp:{strictly 4.10.0} -> 4.10.0 (c)
3.5 ResolutionStrategy API
Configuration 提供一個(gè) ResolutionStrategy 策略厌小,ResolutionStrategy API 的優(yōu)先級(jí)是比 Dependency API 和 DependencyConstraintHandler API 更高的恢共,可以最為后置手段統(tǒng)一更改依賴(lài)庫(kù)版本。
主模塊 build.gradle
dependencies {
implementation project(':mylibrary')
}
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
def requested = details.requested
if (requested.group == 'com.squareup.okhttp3' && requested.name == 'okhttp') {
details.useVersion '4.10.0' // 強(qiáng)制修改版本
}
}
}
dependencies {
constraints {
// implementation 'com.squareup.retrofit2:retrofit:2.9.0' // 不指定版本璧亚,ResolutionStrategy API 也能解析
implementation('com.squareup.okhttp3:okhttp') {
version {
strictly("3.10.0") // 強(qiáng)制修改版本
}
}
}
}
打印子模塊的依賴(lài)信息:
+--- project :mylibrary
| +--- com.squareup.retrofit2:retrofit -> 2.9.0
| | \--- com.squareup.okhttp3:okhttp:3.14.9 -> 4.10.0
| | +--- com.squareup.okio:okio:3.0.0
| | | \--- com.squareup.okio:okio-jvm:3.0.0
| | | +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31 -> 1.7.20 (*)
| | | \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31 -> 1.7.20
| | \--- org.jetbrains.kotlin:kotlin-stdlib:1.6.20 -> 1.7.20 (*)
| \--- com.squareup.okhttp3:okhttp:3.10.0 -> 4.10.0 (*)
+--- com.squareup.retrofit2:retrofit:2.9.0 (c)
\--- com.squareup.okhttp3:okhttp:{strictly 3.10.0} -> 4.10.0 (c)
4. 總結(jié)
1讨韭、在 Gradle 構(gòu)建工具中可以聲明穩(wěn)定版本和不穩(wěn)定版本,其中不穩(wěn)定版本中的 Dynamic 變化版本指版本號(hào)不穩(wěn)定癣蟋,而 Changing 變化版本(如 SNAPSHOT)指產(chǎn)物不穩(wěn)定透硝;
2、Gralde 依賴(lài)版本決議機(jī)制會(huì)綜合考慮依賴(lài)關(guān)系圖上所有的直接依賴(lài)疯搅、間接依賴(lài)和依賴(lài)約束規(guī)則(API)濒生,并從中選擇出符合所有約束規(guī)則的最高依賴(lài)項(xiàng)版本。如果不存在滿(mǎn)足約束規(guī)則的依賴(lài)項(xiàng)版本幔欧,則會(huì)拋出構(gòu)建失敗錯(cuò)誤罪治;
3、雖然 Gradle 在平臺(tái)層提供了一套依賴(lài)解析決議機(jī)制礁蔗,但 Gradle 版本決議的默認(rèn)規(guī)則是選擇的最高版本觉义,最高版本不一定與項(xiàng)目兼容,所以需要開(kāi)發(fā)者使用相關(guān)版本決議規(guī)則 API 來(lái)配置和干預(yù) Gradle 的決議規(guī)則浴井。
今天我們學(xué)習(xí)了 Gradle 的依賴(lài)沖突與版本決議原理晒骇,在下一篇文章中我們將會(huì)落實(shí)到 Gradle 源碼上進(jìn)行分析,請(qǐng)關(guān)注磺浙。
參考資料
- Working with Dependencies —— Gradle 官方文檔
- Gradle 與 Android 構(gòu)建入門(mén) —— xuduokai(百度)著
- 一文搞懂 Gradle 的依賴(lài)管理和版本決議 —— yechaoa(阿里)著
推薦閱讀
Gradle 構(gòu)建工具完整目錄如下(2023/07/12 更新):
整理中...
?? 永遠(yuǎn)相信美好的事情即將發(fā)生瘤缩,歡迎加入小彭的 Android 交流社群~