天天看點

Gradle 建構工具 #5 又沖突了!如何了解依賴沖突與版本決議?

⭐️ 本文已收錄到 AndroidFamily,技術和職場問題,請關注公衆号 [彭旭銳] 和 [BaguTree Pro] 知識星球提問。

Gradle 作為官方主推的建構系統,目前已經深度應用于 Android 的多個技術體系中,例如元件化開發、産物建構、單元測試等。可見,要成為 Android 進階工程師 Gradle 是必須掌握的知識點。

本文是 Gradle 建構工具系列的第 5 篇文章,完整文章目錄請移步到文章末尾~

前言

大家好,我是小彭。

在前文 Gradle 建構工具 #3 Maven 釋出插件使用攻略(以 Nexus / Jitpack 為例) 和 Gradle 建構工具 #4 來開源吧!釋出開源元件到 MavenCentral 倉庫超詳細攻略 文章中,我們已經讨論過如何釋出元件到 Nexus 企業私有倉庫或 MavenCentral 中央倉庫的方法。

在釋出元件的新版本時,開發者需要描述該元件的 GAV 基本資訊,包括:groupId、artifactId、version 和 packaging 等。在協同開發的另一側,依賴方也需要通過相同的 GAV 坐标來定位依賴項:

build.gradle

dependencies {
    implementation 'io.github.pengxurui:modular-eventbus-annotation:1.0.0'
}
           

然而,當工程中的依賴關系增多就很容易會遇到依賴版本沖突問題,這個時候 Gradle 建構工具是否有統一的規則來處理沖突,而開發者又需要采用什麼樣的手段來應對沖突呢?

目錄

1、如何聲明依賴版本?

1.1 靜态版本與不穩定版本的差別(What & What's Diff)

1.2 動态版本和變化版本的差別(What & What's Diff)

1.3 如何調整不穩定版本的解析政策(How)

2、依賴沖突是怎麼發生的?

2.1 什麼是依賴傳遞(What)

2.2 什麼是依賴沖突(What)

2.3 如何檢視依賴版本沖突(How)

3、Gradle 依賴版本決議

3.1 對比 Maven 和 Gradle 的解析政策(What’s Diff)

3.2 版本排序規則(Detail)

3.3 Dependency API:strictly、require、reject、prefer、exclude、transitive(Detail)

3.4 DependencyConstraintHandler API(Detail)

3.5 ResolutionStrategy API(Detail)

4、總結

1. 如何聲明依賴版本?

首先,我們先盤點出 Gradle 建構系統中聲明依賴版本的方式:

1.1 靜态版本與不穩定版本

在 Gradle 建構聲明依賴的文法想必各位都了然于胸了:

build.gradle

dependencies {
    // 簡寫格式
    implementation 'com.google.guava:guava.20.0'
    // 完整格式:
    implementation group 'com.google.guava', name: 'guava:guava', version '20.0'
}
           

其實 Gradle 不僅支援精确地指定版本号外,還支援豐富的版本聲明方法,我這裡總結了一些比較實用的使用方式:

  • 靜态版本(精确版本): 最簡單的方式,例如 1.1
  • 區間版本: 使用 () 或 [] 定義開閉區間,例如 [1.0,) 表示高于 1.0 版本
  • 字首版本: 通過 + 指定版本号字首,相當于特殊的區間版本,例如 1.1.+
  • 最新版本: 通過 latest-status 指定最新版本,例如 latest-release
  • SNAPSHOT 版本: Maven 風格的快照版本,例如 1.1-SNAPSHOT

除了精确版本外,其它所有的版本聲明方式的建構都是不穩定的,比如 [1.0,) 到底是依賴 1.1 還是 1.2?而 1.1.+ 到底是依賴 1.1.0 還是 1.1.1?

那麼,這些不穩定版本存在的意義是什麼?

1.2 如何了解兩種不穩定版本 ——動态版本和變化版本

我原本是計劃将靜态版本以外的聲明方式了解為「動态版本」,但是按照 Gradle 官方文檔來了解的話,其實會細分為「Dynamic Version 動态版本」和「Changing Version 變化版本」,為避免混淆概念,我們就統一将後者了解為「不穩定版本」好了。

可是,Gradle 官方的也未免太學術化了吧 應該如何了解呢?

一句話概括:

「動态版本是版本不穩定,變化版本是産物不穩定」

  • Dynamic 動态版本

動态版本是指版本号不固定的聲明方式,例如前面提到的區間版本、字首版本和最新版本都屬于動态化版本,最終依賴的版本号之後在建構時才能确定(如 2.+⇒2.3 隻有在建構時才能确定)。

是以,動态版本适合用在強調使用依賴項最新版本的場景,項目會更加積極地擁抱依賴項的最新版本,當倉庫中存在依賴項的最新版本時,動态版本直接解析為依賴項的最新版本(還需要滿足緩存逾時的前提)。

  • Changing 變化版本

變化版本是指版本号固定但産物不固定的聲明方式,比如 Maven 的 SNAPSHOT 快照版本。快照版本會在每次建構時到遠端倉庫中檢查依賴項産物的最新版本(還需要滿足緩存逾時的前提)。

例如,在大型軟體項目中,往往是多個團隊(或多名同學)協同開發不同子產品,例如 A 子產品依賴 B 子產品,兩個子產品并行開發。如果子產品 B 不使用快照版本(例如版本為 1.0.0),那麼當 B 子產品在開發階段需要更新,A 子產品就無法接收到更新。因為 A 子產品本地倉庫中已經下載下傳了 B 子產品的 1.0.0 版本,是以建構時不會重複去下載下傳遠端倉庫中更新的版本。

直接的解決辦法可以清除 A 子產品的本地倉庫緩存,或者每次 B 子產品更新都更新版本,很顯然兩個辦法都不靈活,頻繁更新版本也是對版本号的濫用,不利于版本管理。而如果子產品 B 使用快照版本(1.0.0-SNAPSHOT),A 子產品每次建構都會去檢查遠端倉庫是否有 B 子產品的新快照(還需要滿足緩存逾時的前提),就可以保證一直依賴 B 子產品的最新版本。

Gradle 建構工具 #5 又沖突了!如何了解依賴沖突與版本決議?

總的來說,動态版本傾向于積極擁抱最新版本,而快照版本傾向于積極內建開發版本,要根據具體的協同開發場景來選擇,在實踐經驗中,變化版本(快照版本)的使用頻率更大。

需要注意的是:這兩種版本均不應該用在生産環境配置中,因為這兩種不穩定版本共同存在的問題是: 「輸入相同的建構配置可能會産生不同的建構産物輸出」 ,會導緻重複建構正式産物的不确定性。在實踐中,也确實暴露過一些不穩定版本濫用而造成的生産事故,最終我和同僚優化了這個問題,這個我們後文再分享(沒錯,我又來挖坑了)。

1.3 調整不穩定版本的解析政策

在預設情況下, Gradle 會按照 24 小時緩存有效期緩存動态版本和變化版本的解析結果,在緩存有效期間,Gradle 不會檢查遠端倉庫來擷取最新的依賴項。在預設配置的基礎上,Gradle 還提供了「時間和鎖定」兩個層面來控制不穩定版本的解析政策的 API:

By default, Gradle caches changing versions of dependencies for 24 hours, … By default, Gradle caches dynamic versions and changing modules for 24 hours, …
  • 修改緩存時間

通過修改依賴分組的 ResolutionStrategy 決議政策對象,可以修改緩存時間:

build.gradle

configurations.all {
    // 修改 Dynamic 版本的緩存時間
    resolutionStrategy.cacheDynamicVersionsFor 10, 'minutes'
    // 修改 Changing 版本的緩存時間
    resolutionStrategy.cacheChangingModulesFor 10, 'minutes'
}
           
  • 鎖定動态版本

通過控制依賴分組的 ResolutionStrategy 決議政策對象,可以設定版本鎖定,但隻針對動态版本有效,對于變化版本(快照版本)不生效。版本鎖定的細節比較多,目前在社群上沒查找到開發者的應用實踐,我們就先不展開了(又挖坑?)

build.gradle

configurations {
    compileClasspath {
        resolutionStrategy.activateDependencyLocking()
    }
}
           

‍♀️ 現在有一個疑問:既然 Gradle 都會按照解析規則選擇精确精确版本或者不穩定版本的最新版本。那麼,我們說的依賴沖突到底是怎麼發生的呢?

2. 依賴沖突是怎麼發生的?

2.1 什麼是依賴傳遞?

用最簡單的話說,A 依賴 B,B 依賴 C,那麼 A 也會依賴 C,這就是依賴傳遞。

在 Gradle 生命周期的配置階段,Gradle 會解析元件之間的依賴關系。當一個元件被添加到依賴關系圖中時,還會遞歸地解析該元件所依賴的其他元件,同時将「間接依賴」也添加到依賴關系圖中,直到元件自身沒有依賴時終止。

  • Direct Dependency 直接依賴

表示子產品需要直接依賴和使用的特性,例如子產品依賴了 com.squareup.okhttp3:okhttp,那麼 OkHttp 就是直接依賴;

  • Transitive Dependency 間接依賴

如果在被直接依賴的元件中,如果該元件還依賴了其他元件,那麼其它元件就被間接依賴,例如 com.squareup.okio:okio Okio 就是間接依賴。

Gradle 建構工具 #5 又沖突了!如何了解依賴沖突與版本決議?

這就是 Gradle 的依賴傳遞,很容易了解吧。

2.2 什麼是依賴依賴沖突?

在大型項目中,當工程中的依賴關系增多就很容易會遇到依賴沖突問題,想必各位在工作中也遇到過各種各樣的依賴沖突問題。你遇到過什麼樣的依賴沖突問題,可以在評論區發表一下觀點

社群中通常會将依賴沖突和依賴版本沖突劃上等号,比如 20 年百度 App 技術團隊的公開資料 《Gradle 與 Android 建構入門》。其實,如果我們結合實踐中暴露的問題,Gradle 的依賴沖突可以細分為 2 類問題:

  • Version Conflict 版本沖突: 在項目依賴關系圖中,某個依賴項存在多個版本;
  • Implementation conflict 實作沖突: 在項目依賴關系圖中,多個依賴項存在相同實作。
Gradle 建構工具 #5 又沖突了!如何了解依賴沖突與版本決議?

版本沖突大家都很熟悉,我們今天要讨論就是版本決議問題。

那麼「實作沖突」又怎麼了解呢,兩個元件存在相同實作聽起來就很離譜啊

其實把 Build Output 報錯日志貼出來,你就懂了。

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)
...
           

由于項目依賴中 "asm:asm:3.3.1" 和 "org.ow2.asm:asm:4.0" 都存在相同的 ASM 特性,是以當依賴關系樹中存在兩個相同實作時,建構就 Fail 掉了,不可能同一個類打包兩份對吧。

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 {
}
           

老司機們見多識廣,懂的都懂

2.3 如何檢視依賴版本沖突?

相比于依賴實作沖突,依賴版本沖突通常更加隐蔽,畢竟不同版本之間會考慮相容性,是以建構時不會直接建構失敗(建構成功不代表運作時不會 Crash,這是一個坑哦 )

那麼,我們怎麼檢視工程中存在的依賴版本沖突呢,方法比較多:

  • 1、Task dependencies
  • 2、Task dependencyInsight
  • 3、Build Scan
  • 4、新版 Android Studio 的 Gradle Dependency Analyzer 分析器(推薦)
// 依賴樹資訊:
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)
           
  • >:表示沖突,比如這個1.1.0 -> 1.3.0,> 表示 1.1.0 版本被拉高到 1.3.0;
  • : 表示省略不重要的層級;
  • c:c 是 constraints 的簡稱,表示 DependencyConstraintHandler API 限制的版本;
  • strictly:表示 Dependency API strictly 強制指定的版本。

了解了依賴傳遞和依賴沖突後,現在我們來讨論 Gradle 的依賴版本決議機制:

3. Gradle 依賴版本決議

比如以下依賴關系中,項目工程中直接或間接依賴 OkHttp 的兩個版本,可以看到依賴關系樹上存在 okhttp:3.10.0 和 okhttp 3.14.9 兩個版本:

  • 直接依賴 com.squareup.okhttp3:okhttp:3.10.0
  • 直接依賴 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"
}
           

現在的問題是:Gradle 應該選擇哪個依賴項版本呢?

這就是版本決議(Dependency Resolution)要讨論的問題,結論先行

Gralde 依賴版本決議會綜合考慮依賴關系圖上所有的直接依賴、間接依賴和依賴限制規則(API),并從中選擇出符合所有限制規則的最高依賴項版本。如果不存在滿足限制規則的依賴項版本,則會抛出建構失敗錯誤。

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. —— 官方文檔原文

我把這個結論可視化出來,就很清晰了:

Gradle 建構工具 #5 又沖突了!如何了解依賴沖突與版本決議?

我們把依賴資訊列印出來,也确實是采用最高版本:

+--- 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 對比 Maven 和 Gradle 的解析政策

不同的建構系統設計的解析政策不同,我們以 Maven 為對比:

  • Maven 最短路徑政策

Maven 建構系統會采用最短路政策,建構系統會選擇從根子產品到依賴項的最短路來選擇版本。例如在本節開頭的例子總,在 Maven 建構系統中就會選擇 com.squareup.okhttp3:okhttp:3.10.0 這個版本。

  • Gradle 最高版本政策

Gradle 建構系統會采用最高版本政策,建構系統會選擇依賴關系圖中滿足限制規則的最高版本。例如在本節開頭的例子中,在 Gradle 建構系統中就會選擇 com.squareup.okhttp3:okhttp:3.14.9 這個版本。

一個誤區: 需要避免混淆的是,在 Gradle 中使用 Maven 倉庫,并不會左右 Gradle 的沖突解決政策,這裡的 Maven 倉庫僅用于提供依賴項,而依賴管理依然是在 Gradle 的架構内運作的。

3.2 版本排序規則(面試題)

OK,既然在出現版本沖突時,Gradle 會選擇依賴關系圖中最高的版本号,那麼版本号的排序規則是怎樣的呢?比如 1.1.0-alpha 和 1.0.0 會選擇哪個版本呢?完整的規則文檔在 Declaring Versions and Ranges 中。

有毒啊,文檔這也太複雜了哦,我将整個文檔提煉為 3 條基本規則,已經可以滿足大部分開發場景了:

  • 1、分段對比規則 版本号字元串會被分隔符劃分為多個分段,高分段優先:1.1 分隔符: 支援使用 [.-_+] 分隔符,分隔符沒有差異,即 1.a.1 == 1-a-11.2 字母和數字分開: 字母和數字會劃分為不同分段,即 1a1 存在三個級别,和 1a1 == 1.a.11.3 進階别優先: 進階别分段優先确定版本高低,即 2.1 > 1.2
  • 2、同分段對比規則 同分段中,數字按數值排序,數字優先于字母:2.1 數字版本高于字母版本: 即 1.1 > 1.a2.2 數字版本按數值排序: 即 1.10 > 1.2(易錯,并不是按照「字典排序」規則,如果按照字典排序 1.2 > 1.10)2.3 字母版本按字母順序排序,大寫優先: 即 1.Bc > 1.B > 1.A > 1.a
  • 3、特殊字元串規則 特殊字元串有特殊的排序規則:3.1 釋出序列: 即 1.0-dev < 1.0-alpha- < 1.0-rc < 1.0-release < 1.03.2 snapshot 快照版本低于正式版本: 即 1.0-rc < 1.0-snapshot < 1.0-release < 1.0

就是說 Gradle 會分段對齊對比,字母和數字屬于不同分段,而同級别分段按照數值排序,而不是字典序排序。OK,那我明白了,按規則排列 1.1.0-alpha < 1.0.0 的,是以會選擇 1.0.0(Gradle 最高版本政策)這個版本。

雖然 Gradle 在平台層提供了一套依賴解析決議機制,但 Gradle 版本決議的預設規則是選擇的最高版本,最高版本不一定與項目相容,是以開發者有時候要使用版本決議規則 API 來配置和幹預 Gradle 的決議規則。

3.3 Dependency API

  • strictly 嚴格版本: 強制選擇此版本,由于 Gradle 采用高版本優先政策,是以 strictly 的應用場景是為了降低版本(等價于 !! 雙感歎号文法):
dependencies {
    implementation "com.squareup.retrofit2:retrofit:2.9.0"
    implementation("com.squareup.okhttp3:okhttp") {
        version{
            strictly("3.10.0")
        }
    }
    // 等價于
    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 優先版本: 如果不存在更高版本,則優先使用此版本。
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 (*)
           

需要注意的時,strictly 和 require 語句會互相覆寫,要以最後聲明的語句為準,strictly 和 require 語句還會清除之前聲明的 reject 語句,是以應該把 reject 語句放在最後。

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 排除規則

使用 exclude 可以根據 GAV 坐标排除間接依賴,也常用于解決前面提到的依賴實作沖突問題。

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 傳遞規則

使用 transitive 可以控制是否傳遞間接依賴:

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 限制規則提供了一個統一的位置來控制項目的依賴版本,而在聲明依賴的位置甚至可以不需要指定版本。但是如果子產品想單獨編譯,那麼還是需要指定版本的,畢竟沒有限制源就無法确定版本。

子子產品 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") // 強制修改版本
            }
        }
    }
}
           

列印子子產品的依賴資訊:

+--- com.squareup.retrofit2:retrofit FAILED // 無法解析(單獨編譯缺少限制來源) 
\--- com.squareup.okhttp3:okhttp:3.10.0
     \--- com.squareup.okio:okio:1.14.0
           

列印主子產品的依賴資訊:

+--- 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 4.10.0} -> 4.10.0 (c)
           

3.5 ResolutionStrategy API

Configuration 提供一個 ResolutionStrategy 政策,ResolutionStrategy API 的優先級是比 Dependency API 和 DependencyConstraintHandler API 更高的,可以最為後置手段統一更改依賴庫版本。

主子產品 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' // 強制修改版本
        }
    }
}

dependencies {
    constraints {
        // implementation 'com.squareup.retrofit2:retrofit:2.9.0' // 不指定版本,ResolutionStrategy API 也能解析
        implementation('com.squareup.okhttp3:okhttp') {
            version {
                strictly("3.10.0") // 強制修改版本
            }
        }
    }
}
           

列印子子產品的依賴資訊:

+--- 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. 總結

  • 1、在 Gradle 建構工具中可以聲明穩定版本和不穩定版本,其中不穩定版本中的 Dynamic 變化版本指版本号不穩定,而 Changing 變化版本(如 SNAPSHOT)指産物不穩定;
  • 2、Gralde 依賴版本決議機制會綜合考慮依賴關系圖上所有的直接依賴、間接依賴和依賴限制規則(API),并從中選擇出符合所有限制規則的最高依賴項版本。如果不存在滿足限制規則的依賴項版本,則會抛出建構失敗錯誤;
  • 3、雖然 Gradle 在平台層提供了一套依賴解析決議機制,但 Gradle 版本決議的預設規則是選擇的最高版本,最高版本不一定與項目相容,是以需要開發者使用相關版本決議規則 API 來配置和幹預 Gradle 的決議規則。

今天我們學習了 Gradle 的依賴沖突與版本決議原理,在下一篇文章中我們将會落實到 Gradle 源碼上進行分析,請關注。

參考資料

  • Working with Dependencies —— Gradle 官方文檔
  • Gradle 與 Android 建構入門 —— xuduokai(百度)著
  • 一文搞懂 Gradle 的依賴管理和版本決議 —— yechaoa(阿裡)著

推薦閱讀

Gradle 建構工具完整目錄如下(2023/07/12 更新):

#1 為什麼說 Gradle 是 Android 進階繞不去的坎

#2 手把手帶你自定義 Gradle 插件

#3 Maven 釋出插件使用攻略(以 Nexus / Jitpack 為例)

#4 來開源吧!釋出開源元件到 MavenCentral 倉庫超詳細攻略

#5 又沖突了!如何了解依賴沖突與版本決議?

整理中...

⭐️ 永遠相信美好的事情即将發生,歡迎加入小彭的 Android 交流社群~
Gradle 建構工具 #5 又沖突了!如何了解依賴沖突與版本決議?

繼續閱讀