⭐️ 本文已收录到 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, alldependency 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-1
    • 1.2 字母和数字分开: 字母和数字会划分为不同分段,即 1a1 存在三个等级,和 1a1 == 1.a.1
    • 1.3 高等级优先: 高等级分段优先确认版别高低,即 2.1 > 1.2
  • 2、同分段比照规矩 同分段中,数字按数值排序,数字优先于字母:

    • 2.1 数字版别高于字母版别: 即 1.1 > 1.a
    • 2.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.0
    • 3.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 又冲突了!如何理解依赖冲突与版本决议?