环境

Gradle:4.4.1

Configuration 介绍

通过声明的方式定义了一组依赖。Gradle 通过声明的方式查找对应依赖产物和他们的自身的依赖产物。它代表一组文件。只是这些文件是根据声明信息从本地或远程仓库中获取的。

Configuration 状态

状态分为三个 UNRESOLVED(未解析),RESOLVED(解析成功)/ RESOLVED_WITH_FAILURES(解析失败)
从未解析到解析成功或解析失败主要涉及两个过程,一 解析依赖图,二 获取产物。

一 解析依赖图

主要的功能是使用广度优先算法遍历解析依赖图。同时解决依赖图中相同依赖的版本冲突。

解析冲突策略

解决版本冲突方式主要使用 3 种策略:

1 版本优先:

LatestModuleConflictResolver
一个版本号版本被分为2两部分。数字版本号+限定版本号数字版本号开始的数字的部分。剩下的为限定版本号在通常版本号分为
image.png

eg:1.2.3-bate3,[1,2,3] 为数字版本号,[bate,3] 为 限定版本号
比较原则

  1. 数字版本号比较大小,逐个比较数字部分,数字大的版本号大
    eg: 1.2.3 > 1.1.19 ,1.2.3.1 > 1.2.3
  2. 限定版本号比较大小,数字 > final > release > rc > (任意非数字字符) > dev 。任意非数字字符比较方式是逐个比较 Char 的 ASCII码值。
  3. 数字版本号大的版本号大。
  4. 数字版本号 相同,没有限定版本号 大于有限定版本号
    eg:1.2.3 >1.2.3-beta
  5. 数字版本号 相同,限定版本号大的版本号大。
    eg: 1.2.3-final > 1.2.3-release > 1.2.3-rc3 > 1.2.3-rc > 1.2.3-beta > 1.2.3-dev
  6. 数字版本号相同,非 SNAPSHOT 大于 SNAPSHOT版本。
    eg:1.2.3-beta > 1.2.3-SNAPSHOT(注意全部大小)
  7. 版本声明顺序不影响版本号的比较。
  8. 一个依赖版本被选中,那么它的父节点也要被选中。
版本比较中的魔幻

Q:版本 0.1.1-20181030.154719-1 跟 0.1.1-beta 哪个版本比较新?
A:0.1.1-beta。 虽然数字版本号 相同,限定版本 [20181030,154719,1] 大于 [beta],但是 0.1.1-20181030.154719-1 满足正则(.+)-\\d{8}\\.\\d{6}-\\d+·会被解析成 0.1.1-SNAPSHOT 中的 20181030.154719-1版本 , 所以 0.1.1-20181030.154719-1 是个 SNAPSHOT 版本。对应比较原则中的第6原则,非 SNAPSHOT 大于 SNAPSHOT版本。

Q:版本 0.1.1-20181030.154719-1 跟 0.1.1-SNAPSHOT 哪个版本比较新?
A: 0.1.1-20181030.154719-1

Q:1.2.1-SNAPSHOT VS 1.2.0

1
2
3
4
5
6
情况A:
'com.dim.red:one:1.2.0'
'com.dim.red:one:1.2.1-SNAPSHOT'
情况B:
'com.dim.red:one:1.2.1-SNAPSHOT'
'com.dim.red:one:1.2.0'

1.2.0 跟 1.2.1-SNAPSHOT 哪个版本比较,谁比较新?情况 A 和情况 B 会有不同吗?
A: 通过比较原则中 1 和 6 。那么 1.2.1-SNAPSHOT 不管在情况 A 和情况 B 中都应该始终大于 1.2.0 。但是在 Gradle 在 4.4 - 4.6 版本中存在一个BUG。
将导致的问题是情况 A 1.2.0 版本大于 1.2.1-SNAPSHOT。 情况 B 1.2.1-SNAPSHOT 大于 1.2.0。具体的原因可以比对版本 4.4.1 和 4.10.2 的实现。
LatestModuleConflictResolver.select(details) (4.4.1)
LatestModuleConflictResolver.select(details)(4.10.2 )

Q:

1
2
3
4
5
6
7
8
9
10
11
12
情况A
com.dim.red:a:2.0.0
\--- com.dim.red:b:1.2.0
com.dim.red:c:1.0
\--- com.dim.red:a:2.2.0
\--- com.dim.red:b:1.0.0
情况B
com.dim.red:c:1.0.0
\--- com.dim.red:a:2.2.0
\--- com.dim.red:b:1.0.0
com.dim.red:a:2.0.0
\--- com.dim.red:b:1.2.0

依赖 com.dim.red:b 会选中什么版本?情况 A 和情况 B 会有不同吗?
A:通过比较原则中 7 和 8。所以应该是 c:1.0,a:2.2.0,b:1.0.0。
但是在 Gradle 的不同版本实现中表现却不一样。在 Gradle 4.9 以下情况 A 会选中版本 c:1.0.0,a:2.2.0,b:1.2.0。而情况 B 选中版本 c:1.0.0,a:2.2.0,b:1.0.0 这里明显是一个BUG。 4.9 修正这个BUG。原因在于 4.9 以下在版本比较中没有过滤掉父节点为空的版本。但是 4.9 的版本中存在一个新的BUG #7050 。该 BUG 在5.0-rc中修复。但是 Gradle 5.0 - rc 的版本还不兼容现有的 Android Gradle Plugin 3.2.1 版本。

2 严格模式

StrictConflictResolver
不允许出现版本冲突。当出现版本冲突的时候,需要手动强制声明锁定版本。

3 项目优先

ProjectDependencyForcingResolver
本地项目版本大于远程依赖版本。当没有远程或多个本地项目,使用版本优先算法进行比较。

强制声明锁定版本

声明以后,在发生版本冲突的时候会直接选择锁定版本。而不需要经过策略。
锁定版本方式有两种:

1
2
3
implementation ('com.google.code.gson:gson:2.8.5'){
force = true
}


1
2
3
4
5
configurations.all {
resolutionStrategy {
force 'com.google.code.gson:gson:2.8.5‘
}
}

策略设置

默认策略为版本优先。

1
2
3
4
5
6
7
configurations.all {
resolutionStrategy {
// 默认为版本优先模式
// failOnVersionConflict() // 切换策略为严格模式
// preferProjectModules() // 切换策略为项目优先模式。
}
}

总结

解析依赖图是一件复杂的事情。Gradle 在上面做的并不好。 或许在 5.0 上会有更稳定,更健壮的表现。所以我们需要特别关心最终打到 APK 的依赖版本。可以使用命令 gradlew :{moduleName}:dependencies 或参考 输出 Apk 所有的依赖 文章分析 APK 的依赖。

二 获取产物

缓存策略

在获取产物过程中,会先从缓存中获取。如果缓存有效。则直接从缓存中获取。缓存无效则请求远程仓库。
设置缓存策略:

1
2
3
4
5
6
7
8
configurations.all {
resolutionStrategy {
// cache dynamic versions for 10 minutes
cacheDynamicVersionsFor 10*60, 'seconds'
// don't cache changing modules at all
cacheChangingModulesFor 0, 'seconds'
}
}

这里可以配置两种缓存规则,

  • 第一种是生效在动态版本上,动态版本包括最新版本和区间版本。最新版本是版本号使用latest.开头。 区间版本包括使用 + 和使用开闭区间[()]限制。

  • 第二种是生效在可变版本上。可变版本指的用 -SNAPSHOT 结尾的版本。注意全部为大写

默认缓存时间是一天。
所有非动态且非可变版本的依赖的缓存不受缓存策略的影响,默认是一直有效的。

属性匹配

在获取产物的时候可能会遇到一个异常。

Could not resolve all files for configuration ':app:dim'.
   > Could not resolve project :lib.
     Required by:
         project :app
      > Cannot choose between the following configurations of project :test1:
          - debugApiElements
          - debugRuntimeElements
          - releaseApiElements
          - releaseRuntimeElements

这是在Gradle 3.0 以上会出现的问题。 原因在于依赖是可以存在多个变种。在没有属性值匹配的时候会找到多个的产物。Gradle 会抛出异常。因为 Gradle 也不知道应该返回哪一个变种。这个时候需要对 Configuration 加上属性,来筛选出唯一的变种。
Gradle 支持属性值的转换。通过定义一些 Transform 提供一些属性转换规则。在属性匹配不上的时候,尝试组合一条最短的 Transform 路径, 进行转换匹配。 筛选出唯一的产物。关于属性匹配和 Transform 相关知识。这里就不展开聊,可以查看相关链接 Gradle Transform 初探 了解更多细节。

相关

Understanding Maven Version Numbers
Gradle Transform 初探