文章

03.Gradle依赖

03.Gradle依赖

Configuration

Configuration 基础

Configuration 概述

大部分项目都是由诸多第三方依赖组成的,在 Gradle 中,依赖不是独立存在的,每个依赖都会归属于一个 Configuration。每个 Configuration 实际上是 dependency 的集合, 便于不同构建步骤引用依赖,依赖和 Configuration 它们的关系如下:

![image.png300](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/1684476054389-5c192828-21e2-47ee-be84-99cf684c352b.png)

每个 Project 可以有多个 Configuration. 我们可以通过 configurations 引用到当前项目的所有的 configuration

常见的 Configuration

在 Android 中, 每个模块都有上百个 configuration, 它几乎出现在我们的所有脚本中:

1
2
3
4
5
// build.gradle.kts
dependencies {
    // configurationName dependencyNotation
    implementation("com.jakewharton:butterknife:8.4.0")
}

configurationName:是配置的名称, 比如在 Android 中我们常见的:

  1. implementation 依赖会出现在编译产物中.但是最终产物中的该依赖不会向外露接口. 这种方式可以有效减少 recompile 时所需要编译的模块, 提高编译速度.
  2. api 同上, 但是在编译期会传递依赖给外部.
  3. compileOnly 仅编译期有效, 不会出现在最终产物中
  4. runtimeOnly 仅运行期有效, 会出现在编译产物中
  5. annotationProcessor 注解处理器依赖 (包含 META-INF/services/javax.annotation.processing.Processor 的 jar 包)

dependencyNotation:是我们依赖的模块描述

Android 常用 Configuration

  • debugCompileClasspath / releaseCompileClasspath 编译期发生 ‘xxx Not Found’, 本质就是在这个 configuration 中没有对应的代码。通常是由于没有依赖正确的库导致的。

  • debugRuntimeClasspath / releaseRuntimeClasspath 运行期发生 ‘xxx Not Found’, 本质就是在这个 configuration 中没有对应的代码 通常是由于使用了 compileOnly(xxx) 仅仅在编译期依赖某些库导致。

Configuration 继承

继承常见的用法是定义公共依赖,比如 testandroidTest 都需要 junitassetj-android, 通常我们分别依赖:

1
2
3
4
5
6
7
// build.gradle.kts
dependencies {
    testCompile 'com.squareup.assertj:assertj-android:1.1.1'
    testCompile 'junit:junit:4.12'
    androidTestCompile 'com.squareup.assertj:assertj-android:1.1.1'
    androidTestCompile 'junit:junit:4.12'
}

很明显 testCompileandroidTestCompile 共同的依赖被书写了两次. 这时就可以通过定义一个公共的 configuration, 同时让 testCompile 和 androidTestCompile 继承与这个公共依赖即可:

1
2
3
4
5
6
7
8
// build.gradle.kts
configurations {
    [androidTestCompile, testCompile].each { it.extendsFrom commonTestCompile }
}
dependencies {
    commonTestCompile 'com.squareup.assertj:assertj-android:1.1.1'
    commonTestCompile 'junit:junit:4.12'
}

在 Android 中, 我们会发现出现在 api 中的依赖同时会出现在 implementation 中, 正是因为 implementation 继承于 api.但是一定要注意, 在 Android 中, debugImplementation 并不继承于 implementation.

Configuration 之 implementation

implementation 是众多 configuration 中最常用的一个。implementation 某个依赖时, 在依赖标识后面, 可以跟一个 DefaultExternalModuleDependency 的闭包对依赖进行设置

ModuleDependency

isForce 强制

isFoce: 决议冲突时是否强制使用当前版本, boolean:

当某个依赖, 无论是否是传递依赖, 有多个版本发生决议时, 如果某个依赖 isForce == true 则依赖决议为该版本.
同一个模块中, 如果某个依赖有多个版本都被 force, 则以出现的第一个为准.

1
2
3
4
5
6
7
8
9
// build.gradle.kts
dependencies {
    implementation("io.reactivex.rxjava2:rxjava:2.2.6") {
        isForce = true
    }
    implementation("io.reactivex.rxjava2:rxjava:2.2.10") {
        isForce = true
    }
}

最终决议为 2.2.6  。
force 只作用于当前模块, 不同模块中的 force 相互独立. 例如, A 中 force rxjava:2.2.6, B 中 force rxjava:2.2.10. 此时即便 app 模块依赖 lib, 那么最终决议依然是 2.2.6.

工程依赖中, force 降级导致编译错误:使用 force 需要注意的一点问题是, 在工程依赖中, 如果被依赖的模块 force 的某个依赖比主模块版本低则会发生编译错误. 比如 app 模块依赖于 lib, 如果 app 中引入了 rxjava: 2.2.10, lib 却 force 了 rxjava: 2.2.6 则会发生编译错误:

![image.png500](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/1684475500231-704be5f4-55e2-4f8a-88a7-e38c581e9534.png)

这也告诉我们尽量不要在 lib 中 force 某个依赖的版本,出现上诉也有解决方案:

  1. 去掉 lib 中的 force
  2. 可以在 app 中再次 force 该依赖即可. 无论高低版本
  3. 使用 resolutionStrategy.eachDependency 替换依赖版本
exclude 用于排除某个或者某组传递依赖

直接写在 dependencies 代码块中的依赖叫做直接依赖. 而通过某个依赖从而间接引入的其它依赖项叫做间接依赖或传递依赖. Gradle 中提供了排除间接依赖的方式:

1
2
3
4
5
// build.gradle.kts
implementation("io.reactivex.rxjava2:rxandroid:2.1.1") {
    exclude(group = "io.reactivex.rxjava2", module = "rxjava")
    exclude(group = "io.reactivex.rxjava2")
}
isChanging 是否为易变版本, boolean

默认情况下 (isChanging == false), 如果某个依赖被下载到本地后则会被缓存, 以便下次构建可以直接从本地获取. 一旦将某个依赖 isChanging = true 则会为下载到本地的依赖设定一个缓存有效时间 (默认 24h), 该缓存超过时间后则依然会连接远程仓库检查是否有新包, 有则重新下载后缓存

我们最常见的情况是依赖了某个以 '-SNAPSHOT' 结尾的 aar. 例如:  implementation("com.walfud:foo: 1.2.3-SNAPSHOT") . 这是 Maven 的一种约定, 又称作 快照版本. Gradle 对于快照版本的依赖相当于 隐式声明(虽然 isChanging 的值依然是 false, 但是却有易变版本的特点) 了 isChanging = true.

‘-SNAPSHOT’ 约定对于 ivy 仓库无效

api implementation compileOnly 区别

  • implementation

对于使用了该命令编译的依赖,对该项目有依赖的项目将无法访问到使用该命令编译的依赖中的任何程序,也就是将该依赖隐藏在内部,而不对外部公开。

使用 implementation 会使编译速度有所增快:比如我在一个 library 中使用 implementation 依赖了 gson 库,然后我的主项目依赖了 library,那么,我的主项目就无法访问 gson 库中的方法。这样的好处是编译速度会加快,我换了一个版本的 Gson 库,但只要 library 的代码不改动,就不会重新编译主项目的代码。

  • api

等同于 compile 指令,依赖传递

  • compileOnly

等同于 provided,只在编译时有效,不会参与打包,不会包含到 apk 文件中。可以用来解决重复导入库的冲突

查看 Configuration

获取某个 module 下所有的 configuration

背景:不同项目,配置的 productFlavorbuildType 不同,configuration 就不同,如何快速查找某个 module 所有的 configuration,然后根据 configuration 查找某些库的依赖关系

1
2
3
4
5
6
7
8
9
10
tasks.register("printFilesModule") {
    setGroup('hacket')
    doLast {
        configurations.each {
            if (!it.name.toLowerCase().contains('test') && !it.name.toLowerCase().contains('kapt') && !it.name.toLowerCase().contains('lint') && !it.name.toLowerCase().contains('androidtest') && it.name.toLowerCase().contains('compileclasspath')) {
                println("------------->>>> configurations=> ${it}")
            }
        }
    }
}

执行命令:./gradlew printFilesModule

1
2
3
4
5
6
7
8
9
10
> Task :xxx:printFilesModule
------------->>>> configurations=> configuration ':xxx:xxxGoogleBetaServerDebugCompileClasspath'
------------->>>> configurations=> configuration ':xxx:xxxGoogleBetaServerMonkeyCompileClasspath'
------------->>>> configurations=> configuration ':xxx:xxxGoogleBetaServerReleaseCompileClasspath'
------------->>>> configurations=> configuration ':xxx:xxxGoogleLocalServerDebugCompileClasspath'
------------->>>> configurations=> configuration ':xxx:xxxGoogleLocalServerMonkeyCompileClasspath'
------------->>>> configurations=> configuration ':xxx:xxxGoogleLocalServerReleaseCompileClasspath'
------------->>>> configurations=> configuration ':xxx:xxxGooglePreReleaseDebugCompileClasspath'
------------->>>> configurations=> configuration ':xxx:xxxGooglePreReleaseMonkeyCompileClasspath'
------------->>>> configurations=> configuration ':xxx:xxxGooglePreReleaseReleaseCompileClasspath'

找到指定的的 configuration,如查看 debug Configuration 的依赖:

1
./gradlew app:dependencies > app_deps.txt  --configuration appDebugCompileClasspath

Gradle dependencies

dependencies 基础

声明依赖的版本

在新版的 Gradle 中,依赖声明也发生了一些变化。过去我们直接在使用的地方一次性声明依赖与版本,而新版本 Gradle 更强调将依赖和版本分开声明,例如:

1
2
3
4
5
6
7
8
9
// build.gradle.kts
dependencies {
    // Dependency declaration
    implementation("io.reactivex.rxjava2:rxjava") 
    // Version declaration
    constraints {
        implementation("io.reactivex.rxjava2:rxjava:2.2.0")
    }
}

无论直接依赖还是间接依赖, 都会受到 constraint 的影响

多个 module 之间共享依赖版本 platform

之前,我们通过在根项目中的 ext 属性里面定义依赖库的版本, 然后在子 module 声明依赖时使用 rootProject.ext["…"] 获取到统一定义的版本

新版本中专门为共享依赖库版本增加了一个叫做 platform 的设施, 使用 constraint 实现在所有子 module 共享版本. 如下:

  • 创建一个子 module, 比如叫做 bom, 专门用于管理版本
1
2
3
4
5
6
7
8
9
// bom/build.gradle.kts
plugins {
    `java-platform`
}
dependencies {
    constraints {
        implementation("io.reactivex.rxjava2:rxjava:2.2.0")
    }
}
  • 在其他子项目中通过 platform 引入 “ 版本项目 “ bom
1
2
3
4
5
6
// foo/build.gradle.kts
dependencies {
    implementation(platform(project(":bom")))
    // No version needed
    implementation("io.reactivex.rxjava2:rxjava")
}

更多的子 module 只需要 implementation(platform(project(": bom"))) 即可将 bom 工程所有的依赖约束引入, 方便在子 module 中共享。

platform 引入 require 力度的依赖, 如果想引入 strictly 力度的依赖, 可以使用 enforcedPlatform

版本号

自从 Gradle 6.0.1 开始, 版本号也变得丰富起来:

表达式描述 
1.3, 1.3.0-beta3, 1.0-20150201.131010-1固定版本 
[1.0.0, 1.3.0)大于等于 1.0.0 且小于 1.3.0 的版本. 其中 ‘)’ 可以使用 ‘[’ 代替, 例如: ‘[1.0.0, 1.3.0[’6.0.1 新增
1.+, [1.0,)大于等于 1.0 的版本 
latest.integration, latest.release最新版本 
### 依赖版本的力度  
对于依赖的版本, Gradle 还提供了四种不同的约束力度:  
#### require  
默认情况下, 当我们使用版本声明一个依赖时, 实际上只是限定了该依赖的最低版本. 例如:  
1
2
3
4
5
6
// build.gradle.kts
dependencies {
    implementation("io.reactivex.rxjava2:rxjava:2.2.0")
    implementation("io.reactivex.rxjava2:rxandroid:2.1.1")  // transitive dependency: rxjava:2.2.6
    // 虽然我们声明了 rxjava:2.2.0 依赖, 但是由于 rxandroid 间接依赖了 rxjava:2.2.6, 因此最终版本被提升为 2.2.6. 因此, 其含义是: "我要依赖这个版本, 但是如果别的依赖提供了更高的版本则优先更高的版本".
}

实际上我们写的 "io.reactivex.rxjava2:rxjava:2.2.0" 是依赖声明的简洁语法, 它的完整形式如下:

1
2
3
4
5
6
7
8
9
10
// build.gradle.kts
dependencies {
    // implementation("io.reactivex.rxjava2:rxjava:2.2.0") 
    // equal to:
    implementation("io.reactivex.rxjava2:rxjava") {
        version {
            require("2.2.0")
        }
    }
}

strictly 强制依赖

通过 isForce [[03.Gradle依赖#IsForce 强制]] 来强制限制依赖的版本, 不过由于该方式存在 “ 顺序问题 “(即设置 force 的先后会对结果产生影响) 同时还不支持范围声明, Gradle 已经不建议使用了。

如果我们希望严格限制版本, 可以使用 strictly 的方式. 这种方式下, 会限制依赖的版本必须符合约束的条件, 否则将产生编译错误,声明形式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// build.gradle.kts
dependencies {
    implementation("io.reactivex.rxjava2:rxjava:2.2.0!!") 
    // Or
    implementation("io.reactivex.rxjava2:rxjava") {
        version {
            strictly("2.2.0")
        }
    }
    // Compile error if uncomment
    // implementation("io.reactivex.rxjava2:rxjava:2.0.0") {
    //     isForce = true
    // }
}

同样, 我们以可以使用区间来进行约束, 例如: implementation("io.reactivex.rxjava2:rxjava:[2.0.0, 2.2.0]!!")

strictly 力度还有一个功能, 就是使依赖版本降级, 比如下面这个例子:

1
2
3
4
5
// build.gradle.kts
dependencies {
    implementation("io.reactivex.rxjava2:rxandroid:2.1.1")  // transitive dependency: rxjava:2.2.6
    implementation("io.reactivex.rxjava2:rxjava:2.2.0!!")
}

虽然 rxandroid:2.1.1 间接依赖了 rxjava:2.2.6 版本, 但是由于 rxjava:2.2.0!! 严格约束的存在, 最终决议为 rxjava:2.2.0 版本

prefer 和 reject

prefer 的含义是: “ 当没有别的更强的约束 (比如, require 或者 strictly) 时, 则优先使用某个版本 “

reject 一般用于排除某个版本, 例如排除特定有 bug 的版本

dependencies resolution(依赖决议)

什么是依赖决议?

在项目开发过程中,不可避免的需要引入一些第三方库,而不同的第三方库之间,可能存在一些依赖关系。例如:你依赖了库 A 与 B,而同时 B 也依赖于 A。这样就可能存在这种情况:你依赖的 A 的版本与 B 中依赖的 A 的版本不同。

1
2
3
4
5
6
±-- io.reactivex.rxjava2:rxjava:2.1.6 -> 2.2.10
| \— org.reactivestreams:reactive-streams:1.0.2
±-- io.reactivex.rxjava2:rxandroid:2.1.1
| \— io.reactivex.rxjava2:rxjava:2.2.6 -> 2.2.10 (*)
\— io.reactivex.rxjava2:rxkotlin:2.4.0
\— io.reactivex.rxjava2:rxjava:2.2.10 (*)

依赖决议是指 “ 在编译过程中, 如果存在某个依赖的多个版本, 构建系统应该选择哪个进行构建的问题 “。 编译失败大部分情况都是由决议错误导致的.

默认决议策略

当要决议某个组件的最终版本时, Gradle 会考虑三个地方的版本关系: 直接依赖, 间接依赖, 以及 依赖约束:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// build.gradle.kts
dependencies {
    // direct dependency
    implementation("io.reactivex.rxjava2:rxandroid:2.1.0")        
    // transitive dependency
    // "io.reactivex.rxjava2:rxandroid:2.1.0" depends on "io.reactivex.rxjava2:rxjava:2.2.0"
    // So, the "io.reactivex.rxjava2:rxjava:2.2.0" is transitive dependency

    // dependency constraint
    constraints {
        implementation("io.reactivex.rxjava2:rxandroid:2.1.1")
        implementation("io.reactivex.rxjava2:rxjava:2.0.0")       
    }
}

默认情况下, Gradle 会选择其中最高的那个版本

例如上面的例子中直接依赖了 rxandroid: 2.1.0 版本, 而 constraint 中约束了 rxandroid: 2.1.1, 那么最终会决议为 “2.1.1”. 同样对于间接依赖 rxjava 而言, 间接依赖的 “2.2.0” 版本与 constraint 约束中的 “2.0.0” 决议结果为 “2.2.0”. 正如所说, 总是选取最高的版本。

除了这种简单的比大小外还有诸多特殊情况:

分类例子决议结果说明
全数字, 段数相同, 位数相同1.2.3 vs 1.2.41.2.4-
全数字, 段数相同, 位数不同1.2.3 vs 1.2.101.2.10每段按照数字进行比较, 数字大胜出
全数字, 段数不同1.2.3 vs 1.2.3.01.2.3.0段数多的胜出
段数相同, 字母比较1.2.a vs 1.2.b1.2.b字母通过 String (Java Platform SE 8 )
段数相同, 数字与非数字1.2.3 vs 1.2.abc1.2.3数字优于字母

更多见:Declaring Versions and Ranges

改变决议策略

除了默认的 “ 选择最高版本 “ 以外, 我们也可以手工干预决议的策略, 在 Gradle 有四个地方的设置会影响决议的结果.

1.通过 strictly 版本约束

在新版本 Gradle 中, 首选使用 constraint 对依赖的版本进行约束, 如下:

1
2
3
4
5
6
7
8
// build.gradle.kts
dependencies {
    implementation("io.reactivex.rxjava2:rxjava:2.2.6!!")
    // or
    constraints {
        implementation("io.reactivex.rxjava2:rxjava:2.2.6!!")
    }
}

上面的代码使用了 strictly 约束 !! 实现对依赖版本的锁定.

这里要注意 strictly 与 isForce 之间的细微差别: “ 如果 strictly 依赖比间接 require 依赖版本低时, 会将该依赖降级到 strictly 约束的版本. 但是当 strictly 比直接 require 依赖版本低时, 则会发生编译错误 “. 换句话说也就是 strictly 无法降级那些直接依赖的组件的版本, 但是 isForce 却可以做到. 例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
// build.gradle.kts
dependencies {
    implementation("io.reactivex.rxjava2:rxjava:2.2.4")
    // Error if uncomment
    // constraints {
    //     implementation("io.reactivex.rxjava2:rxjava:2.2.0!!")
    // }

    implementation("io.reactivex.rxjava2:rxjava:2.2.0") {
        isForce = true
    }
}
// 这个例子中, 我们直接依赖了 rxjava:2.2.4, 如果放开注释中 constraints 的一段将会产生编译错误. 相对的, isForce 却可以实现直接依赖的降级.

2.通过 isForce 锁定版本 (不建议使用. 新版本建议使用 strictly 约束)

isForce 会影响决议结果, 使之强制使用该版本.

1
2
3
4
5
6
7
8
9
10
11
12
// build.gradle.kts
dependencies {
    implementation("com.orhanobut:logger:2.0.0") {
        isForce = true
    }
    // or
    constraints {
        implementation("com.orhanobut:logger:2.0.0") {
            isForce = true
        } 
    }
}

这种情况下, 即便有别的依赖间接依赖了更高版本的 logger 库, 也会被强制决议为当前版本。

不要在 library 中设置 isForce

在 library 中, 则要避免 ExternalModuleDependency 锁定的版本! 因为 library 中 force 的版本并不会反映到 POM 文件中, 因此当组件发布后 force 信息便失效了. 此外, 如果别人工程依赖这个模块还可能导致额外版本冲突的编译错误,因此要避免在 library 的依赖配置中直接 force 版本.

3.修改 configuration 决议策略 (不建议使用. 新版本建议使用 strictly 约束)

除了 implementation 的方式对决议产生影响, 我们还可以通过直接对 configuration 设置决议的解决策略:

1
2
3
4
5
6
// build.gradle.kts
configurations.all {
    resolutionStrategy {
        setForcedModules(arrayOf("io.reactivex.rxjava2:rxjava:2.2.10"))
    }
}

这时你会发现, 一旦指定了 configuration 的决议策略, 那么你在依赖配置中即便 force 了某个版本也是无效的.

使用 forcedModules 需要注意的是一点是它仅仅在决议的情况下才会生效. 也就是说仅仅在这里设置强制依赖, 而不在 dependencies 中设置依赖项的话, 该依赖是不会被加入到任何 configuration 的.

4.通过 substitution 禁用决议

substitution 实际上不仅仅会 “ 影响 “ 决议策略, 它原本是设计用来直接替换某个依赖。

比如将 A 组件的任何版本替换为另一个 B 组件的指定版本. 当然, 这个机制也可以用来实现版本的 “ 替换 “

1
2
3
4
5
6
7
8
9
// build.gradle.kts
configurations.all {
    resolutionStrategy {
        dependencySubstitution {
            substitute(module("com.orhanobut:logger:2.0.0"))
                    .with(module("com.orhanobut:logger:2.1.0"))
        }
    }
}

一旦某个依赖存在 substitution, 则不再参与版本决议, 直接使用被替换后的版本.

从优先级来说, resolutionStrategy.substitution > resolutionStrategy.forcedModules > isForce

禁用传递的依赖项

1
2
3
4
5
6
7
8
// 在引入时配置,禁用该库的传递依赖项
implementation("io.reactivex.rxjava2:rxkotlin:2.4.0") {
    transitive = false
}
// 在configuration 级别指定,一杆子打死,谁也别想使用传递依赖
configurations.all {
    transitive = false
}

还有 exclude

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
configurations.all {
//  > A failure occurred while executing com.android.build.gradle.internal.tasks.CheckDuplicatesRunnable
//        > Duplicate class com.facebook.imagepipeline.cache.CountingMemoryCacheInspector found in modules jetified-imagepipeline-base-2.2.0-runtime (com.facebook.fresco:imagepipeline-base:2.2.0) and jetified-stetho-2.0.0-runtime (com.facebook.fresco:stetho:2.0.0)
//        Duplicate class com.facebook.imagepipeline.cache.CountingMemoryCacheInspector$DumpInfo found in modules jetified-imagepipeline-base-2.2.0-runtime (com.facebook.fresco:imagepipeline-base:2.2.0) and jetified-stetho-2.0.0-runtime (com.facebook.fresco:stetho:2.0.0)
//        Duplicate class com.facebook.imagepipeline.cache.CountingMemoryCacheInspector$DumpInfoEntry found in modules jetified-imagepipeline-base-2.2.0-runtime (com.facebook.fresco:imagepipeline-base:2.2.0) and jetified-stetho-2.0.0-runtime (com.facebook.fresco:stetho:2.0.0)
    exclude group: 'com.facebook.fresco', module: 'stetho' // 解决上面问题

    exclude group: 'com.android.support', module: 'support-v13'
    exclude group: 'com.nineoldandroids', module: 'library'
    exclude group: 'com.google.guava', module: 'listenablefuture'
    exclude group: 'org.jetbrains', module: 'annotations'
    exclude group: 'org.jetbrains.kotlin', module: 'kotlin-compiler-embeddable'
    exclude group: "junit", module: "junit"

    resolutionStrategy {
        force 'org.jetbrains:annotations:16.0.2'
    }
}

自定义依赖解析策略

背景:

仍以 rxjava 举例,众所周知,RxJava 最近发布了 3.0.0 的 RC 版本,而 Rxjava3 的 groupid 与 Rxjava2 不同,gradle 的默认策略已经无法解决了,force 也只能饮恨,唯 exclude 和 transitive 可堪一战。于是 A 库来了 exclude,B 库来个 exclude。

dependencySubstitution

dependencySubstitution 接收一系列替换规则,允许你通过 substitute 函数为项目中的依赖替换为你希望的依赖项

1
2
3
4
5
configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute module("io.reactivex.rxjava2:rxjava") with module("io.reactivex.rxjava3:rxjava:3.0.0-RC1")
    }
}

substitute 的参数不一定是 module(),针对外部依赖和内部依赖,你有两种选择:module()project(),视具体情况自由组合:

1
2
3
4
5
6
7
8
9
10
11
// add dependency substitution rules
configurations.all {
    resolutionStrategy.dependencySubstitution {
        // Substitute project and module dependencies
        substitute module('org.gradle:api') with project(':api')
        substitute project(':util') with module('org.gradle:util:3.0')
        
        // Substitute one module dependency for another
        substitute module('org.gradle:api:2.0') with module('org.gradle:api:2.1')
    }
}

eachDependency

eachDependency 允许你在 gradle 解析配置时为每个依赖项添加一个替换规则,DependencyResolveDetails 类型的参数可以让你获取一个 requested 和使用 useVersion()useTarget() 两个函数指定依赖版本和目标依赖。

  • request
    request 中存放了依赖项的 groupid、module name 以及 version,你可以通过这些值来筛选你想要替换的依赖项,再通过 useVersion 或 useTarget 指定你想要的依赖
  • useVersion()
  • useTarget()
1
2
3
4
5
6
7
8
configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        if (details.requested.name == 'rxjava') {
            //由于useVersion只能指定版本号,不适用于group不同的情况
            details.useTarget group: 'io.reactivex.rxjava3', name: 'rxjava', version: '3.0.0-RC1'
        }
    }
}

其他

官网介绍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
configurations.all {
  resolutionStrategy {
    // fail eagerly on version conflict (includes transitive dependencies)
    // e.g. multiple different versions of the same dependency (group and name are equal)
    failOnVersionConflict()

    // prefer modules that are part of this build (multi-project or composite build) over external modules
    preferProjectModules()

    // force certain versions of dependencies (including transitive)
    //  *append new forced modules:
    force 'asm:asm-all:3.3.1', 'commons-io:commons-io:1.4'
    //  *replace existing forced modules with new ones:
    forcedModules = ['asm:asm-all:3.3.1']

    // add dependency substitution rules
    dependencySubstitution {
      substitute module('org.gradle:api') using project(':api')
      substitute project(':util') using module('org.gradle:util:3.0')
    }

    // cache dynamic versions for 10 minutes
    cacheDynamicVersionsFor 10*60, 'seconds'
    // don't cache changing modules at all
    cacheChangingModulesFor 0, 'seconds'
  }
}

dependencies 依赖查找与分析

依赖的传递特性导致项目中所依赖的版本实际上是决议的结果. 当项目变大或者引入大量三方依赖后, 很难了解当前项目中所有的依赖关系以及对应的版本. 这时就需要通过依赖分析

依赖查找

dependencies gradle task

查看所有 configuration 依赖

作用:查看 app module 所有 configuration 的依赖树

命令格式:./gradlew :模块名:dependencies

1
2
# 指定 module 的全部 dependencies 输出到指定文件中
./gradlew :app:dependencies > deps.txt

dependencies 列出了所有 variant 的依赖关系和版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
> Task :app:dependencies

------------------------------------------------------------
Project :app
------------------------------------------------------------

......

debugCompileClasspath - Compile classpath for compilation 'debug' (target  (androidJvm)).
+--- org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.3.72
|    \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72
|         +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72
|         \--- org.jetbrains:annotations:13.0
.....

releaseCompileClasspath - Compile classpath for compilation 'release' (target  (androidJvm)).
+--- org.jetbrains.kotlin:kotlin-android-extensions-runtime:1.3.72
|    \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72
|         +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72
|         \--- org.jetbrains:annotations:13.0
.....


(c) - dependency constraint
(*) - dependencies omitted (listed previously)

(n) - Not resolved (configuration is not meant to be resolved)

A web-based, searchable dependency report is available by adding the --scan option.

BUILD SUCCESSFUL in 642ms
1 actionable task: 1 executed

  • 如果是 org.jetbrains.kotlin:kotlin-stdlib:1.3.50 -> 1.3.61 表明当前依赖了 kotlin-stdlib 1.3.50 版本, 但是由于发生依赖决议导致最终依赖了 1.3.61 版本.
  • 如果是 com.google.code.gson:gson: 2.8.6 (c), 其中 (c) 表明版本被 constraint 约束了. 所以不会被其他传递依赖提升版本.
  • 如果是 org.jetbrains.kotlin: kotlin-stdlib: 1.3.61 (_), 其中 (_) 表示这个依赖的版本在当前输出的前面已经有了, 这里不再重复.

任务执行后会生成一个 txt 文件,里面内容非常多,把所有环境的依赖都给你列出来了,非常多,我们一般只需要关心以下几个:

  • debugRuntimeClasspath debug 运行时依赖
  • debugCompileClasspath debug 编译时依赖
  • releaseRuntimeClasspath release 运行时依赖
  • releaseCompileClasspath release 编译时依赖

常见的 configuration

  • compileClasspath - Compile classpath for compilation ‘main’ (target (jvm)).
  • kotlinCompilerClasspath
  • kotlinCompilerPluginClasspath
  • kotlinNativeCompilerPluginClasspath
  • runtimeClasspath - Runtime classpath of compilation ‘main’ (target (jvm)).
  • testCompileClasspath - Compile classpath for compilation ‘test’ (target (jvm)).
  • testRuntimeClasspath - Runtime classpath of compilation ‘test’ (target (jvm)).
查看指定 configuration 的依赖

debugCompileClasspathreleaseCompileClasspathvariant 以及 configuration 的组合. 一般来说我们排查问题无需打印所有变体的 configuration. 因此可以通过参数控制 dependencies 的输出, 如下用法更加常见:

命令格式:`

1
./gradlew moduleName:dependencies --configuration configurationName
  • 示例 1:指定模块指定 Configuration 输出到控制台
1
2
# 指定 app 模块的 `releaseCompileClasspath` 的依赖输出到控制台
./gradlew app:dependencies --configuration releaseCompileClasspath`
  • 示例 2:指定模块的 releaseCompileClasspath 的依赖输出到 deps.txt 文件中
1
2
3
4
5
# app 模块的 releaseCompileClasspath 的依赖输出到 deps.txt
./gradlew app:dependencies > deps.txt  --configuration releaseCompileClasspath

# xxx模块 xxxGoogleLocalServerDebugCompileClasspath 的依赖输出到dep.txt
./gradlew :xxx:dependencies --configuration xxxGoogleLocalServerDebugCompileClasspath > dep.txt
  • 示例 3:如果不知道 configuration,先获取 configuration,再获取 dependencies
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 获取configuration包含compileclasspath
tasks.register("printFilesModule") {
    setGroup('hacket')
    doLast {
        configurations.each {
            if (!it.name.toLowerCase().contains('test') 
                && !it.name.toLowerCase().contains('kapt') 
                && !it.name.toLowerCase().contains('lint') 
                && !it.name.toLowerCase().contains('androidtest') 
                && it.name.toLowerCase().contains('compileclasspath')) {
                println("------------->>>> configurations=> ${it}")
            }
        }
    }
}
// 执行printFilesModule获取该module所有的configuration
// 再获取dependencies
./gradlew moduleName:dependencies > deps.txt  --configuration releaseCompileClasspath  
productFlavor 的情况除外

注意: 配置了 productFlavor 的情况 但是如果你配置了 productFlavors,直接执行上面的命令会报错误,提示找不到 releaseCompileClasspath
报错提示:
image.png|500 原因也比较简单, 因为环境名称变了,把 releaseCompileClasspath 变更为带 productFlavors 前缀的即可,如 prdReleaseCompileClasspath

androidDependencies task 平铺查看依赖

作用:该 task 会平铺展示依赖树,并且只展示几个主要的 variant,看起来较为清爽,但是缺点是不能像方式一那样指定 configuration。
执行以下命令:./gradlew :app:androidDependencies
![image.png
500](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/1684475284613-02fa6208-724a-407d-9924-f4674cf7b31c.png)

Build Scan

命令格式:gradlew app:dependencies --scan

  • 执行 ./gradlew build --scan 命令,根据提示输入 yes
  • 点击链接会首次会让你输入邮箱,等一会会给你发个邮件
  • 点击邮箱中的链接,可以查看到非常详细的构建信息, 这里我们只看一下依赖模块及查看指定依赖库内部又依赖了哪些东西
![image.png500](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/1684476054389-5c192828-21e2-47ee-be84-99cf684c352b.png)

project-report 插件(可以折叠,推荐)

使用步骤:

  1. apply plugin: 'project-report'
  2. 控制台找到生成的 html 文件
  3. 查看结果 3-1. 按 configuration 查看依赖 image.png|500 3-2. 点击某个库,可以反向查看什么库和项目依赖了该库 image.png|500

依赖分析

dependencyInsight:反向查看依赖

作用和命令格式

作用: 可以用于查看某个项目的依赖项树状结构,也可以用于查看某个依赖库在某个项目中的依赖情况

命令格式:./gradlew :moduleName:app:dependencyInsight --configuration 指定configuration --dependency 指定库group:module

  • 必须有 --configuration--dependency 选项
  • --configuration,指定 configuration;如 debugCompileClasspath,要注意 productFlavors 是否配置
  • --dependency 要查看的依赖
示例 1
使用 dependencyInsight 罗列出 androidx.core:core: 1.7.0 库被依赖的所有信息
1
./gradlew :app:dependencyInsight --configuration releaseRuntimeClassPath --dependency androidx.core:core:1.7.0

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
> Task :app:dependencyInsight
androidx.core:core:1.7.0
   variant "releaseVariantReleaseRuntimePublication" [
      org.gradle.category                             = library (not requested)
      org.gradle.dependency.bundling                  = external (not requested)
      org.gradle.libraryelements                      = aar (not requested)
      org.gradle.usage                                = java-runtime
      org.gradle.status                               = release (not requested)

      Requested attributes not found in the selected variant:
         com.android.build.api.attributes.BuildTypeAttr  = release
         org.gradle.jvm.environment                      = android
         com.android.build.api.attributes.AgpVersionAttr = 7.2.0
         org.jetbrains.kotlin.platform.type              = androidJvm
   ]
   Selection reasons:
      - By conflict resolution : between versions 1.7.0, 1.5.0, 1.1.0, 1.2.0, 1.0.1, 1.3.0, 1.3.1 and 1.0.0

androidx.core:core:1.7.0
+--- androidx.appcompat:appcompat:1.4.1
|    +--- releaseRuntimeClasspath
|    +--- com.google.android.material:material:1.5.0 (requested androidx.appcompat:appcompat:1.1.0)
|    |    \--- releaseRuntimeClasspath
|    \--- androidx.constraintlayout:constraintlayout:2.0.1 (requested androidx.appcompat:appcompat:1.2.0)
|         \--- com.google.android.material:material:1.5.0 (*)
\--- androidx.core:core-ktx:1.7.0
     \--- releaseRuntimeClasspath

androidx.core:core:1.0.0 -> 1.7.0
+--- androidx.dynamicanimation:dynamicanimation:1.0.0
|    \--- com.google.android.material:material:1.5.0
|         \--- releaseRuntimeClasspath
+--- androidx.legacy:legacy-support-core-utils:1.0.0
|    \--- androidx.dynamicanimation:dynamicanimation:1.0.0 (*)
+--- androidx.loader:loader:1.0.0
|    +--- androidx.fragment:fragment:1.3.6
|    |    +--- com.google.android.material:material:1.5.0 (requested androidx.fragment:fragment:1.0.0) (*)
|    |    +--- androidx.appcompat:appcompat:1.4.1
|    |    |    +--- releaseRuntimeClasspath
|    |    |    +--- com.google.android.material:material:1.5.0 (requested androidx.appcompat:appcompat:1.1.0) (*)
|    |    |    \--- androidx.constraintlayout:constraintlayout:2.0.1 (requested androidx.appcompat:appcompat:1.2.0)
|    |    |         \--- com.google.android.material:material:1.5.0 (*)
|    |    \--- androidx.viewpager2:viewpager2:1.0.0 (requested androidx.fragment:fragment:1.1.0)
|    |         \--- com.google.android.material:material:1.5.0 (*)
|    \--- androidx.legacy:legacy-support-core-utils:1.0.0 (*)
\--- androidx.viewpager:viewpager:1.0.0
     \--- androidx.fragment:fragment:1.3.6 (*)

//......省略

androidx.core:core:1.3.1 -> 1.7.0
\--- androidx.constraintlayout:constraintlayout:2.0.1
     \--- com.google.android.material:material:1.5.0
          \--- releaseRuntimeClasspath

androidx.core:core:1.5.0 -> 1.7.0
\--- com.google.android.material:material:1.5.0
     \--- releaseRuntimeClasspath

(*) - dependencies omitted (listed previously)
  • 可以看到 androidx.core: core 库在项目中有多个版本:

    1
    2
    
    Selection reasons:
      - By conflict resolution : between versions 1.7.0, 1.5.0, 1.1.0, 1.2.0, 1.0.1, 1.3.0, 1.3.1 and 1.0.0
    
  • 查看库被依赖的情况

1
2
3
4
5
6
7
8
9
10
androidx.core:core:1.7.0
+--- androidx.appcompat:appcompat:1.4.1
|    +--- releaseRuntimeClasspath
|    +--- com.google.android.material:material:1.5.0 (requested androidx.appcompat:appcompat:1.1.0)
|    |    \--- releaseRuntimeClasspath
|    \--- androidx.constraintlayout:constraintlayout:2.0.1 (requested androidx.appcompat:appcompat:1.2.0)
|         \--- com.google.android.material:material:1.5.0 (*)
\--- androidx.core:core-ktx:1.7.0
     \--- releaseRuntimeClasspath
// 可以看到`androidx.core:core:1.7.0`库在项目中被依赖的全部详细信息,这将有助于做库的版本升级。
如果只是为了查看 androidx .core: core,可以不区分版本号
1
./gradlew :app:dependencyInsight --configuration releaseRuntimeClassPath --dependency androidx.core:core
如果要查看依赖项的路径,可以添加选项 --singlepath(每个依赖项最多显示一个路径)
1
./gradlew :app:dependencyInsight --configuration releaseRuntimeClassPath --dependency androidx.core:core:1.7.0 --singlepath

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
> Task :app:dependencyInsight
androidx.core:core:1.7.0
   variant "releaseVariantReleaseRuntimePublication" [
      org.gradle.category                             = library (not requested)
      org.gradle.dependency.bundling                  = external (not requested)
      org.gradle.libraryelements                      = aar (not requested)
      org.gradle.usage                                = java-runtime
      org.gradle.status                               = release (not requested)

      Requested attributes not found in the selected variant:
         com.android.build.api.attributes.BuildTypeAttr  = release
         org.gradle.jvm.environment                      = android
         com.android.build.api.attributes.AgpVersionAttr = 7.2.0
         org.jetbrains.kotlin.platform.type              = androidJvm
   ]
   Selection reasons:
      - By conflict resolution : between versions 1.7.0, 1.5.0, 1.1.0, 1.2.0, 1.0.1, 1.3.0, 1.3.1 and 1.0.0

androidx.core:core:1.7.0
\--- androidx.appcompat:appcompat:1.4.1
     \--- releaseRuntimeClasspath

androidx.core:core:1.0.0 -> 1.7.0
\--- androidx.dynamicanimation:dynamicanimation:1.0.0
     \--- com.google.android.material:material:1.5.0
          \--- releaseRuntimeClasspath

androidx.core:core:1.0.1 -> 1.7.0
\--- androidx.appcompat:appcompat-resources:1.4.1
     \--- androidx.appcompat:appcompat:1.4.1
          \--- releaseRuntimeClasspath

androidx.core:core:1.1.0 -> 1.7.0
\--- androidx.activity:activity:1.2.4
     \--- androidx.appcompat:appcompat:1.4.1
          \--- releaseRuntimeClasspath

androidx.core:core:1.2.0 -> 1.7.0
\--- androidx.drawerlayout:drawerlayout:1.1.1
     \--- com.google.android.material:material:1.5.0
          \--- releaseRuntimeClasspath

androidx.core:core:1.3.0 -> 1.7.0
\--- androidx.customview:customview:1.1.0
     \--- androidx.drawerlayout:drawerlayout:1.1.1
          \--- com.google.android.material:material:1.5.0
               \--- releaseRuntimeClasspath

androidx.core:core:1.3.1 -> 1.7.0
\--- androidx.constraintlayout:constraintlayout:2.0.1
     \--- com.google.android.material:material:1.5.0
          \--- releaseRuntimeClasspath

androidx.core:core:1.5.0 -> 1.7.0
\--- com.google.android.material:material:1.5.0
     \--- releaseRuntimeClasspath
示例 2:查看 WorkManager 库被引用的情况
先查找 App 的 Configuration

见 [[#查看Configuration]]

执行命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# APP工程
./gradlew :app:dependencyInsight --configuration releaseRuntimeClassPath --dependency androidx.work:work-runtime:2.7.1

# xxx工程
./gradlew :xxx:dependencyInsight --configuration xxxGoogleLocalServerDebugCompileClasspath --dependency androidx.work:work-runtime:2.7.1

androidx.work:work-runtime:2.7.1
  Variant releaseVariantReleaseApiPublication:
    | Attribute Name                                             | Provided | Requested   |
    |------------------------------------------------------------|----------|-------------|
    | org.gradle.category                                        | library  |             |
    | org.gradle.dependency.bundling                             | external |             |
    | org.gradle.libraryelements                                 | aar      |             |
    | org.gradle.status                                          | release  |             |
    | org.gradle.usage                                           | java-api | java-api    |
    | com.android.build.api.attributes.AgpVersionAttr            |          | 7.4.0       |
    | com.android.build.api.attributes.BuildTypeAttr             |          | debug       |
    | com.android.build.api.attributes.ProductFlavor:app         |          | xxx       |
    | com.android.build.api.attributes.ProductFlavor:channel     |          | google      |
    | com.android.build.api.attributes.ProductFlavor:environment |          | localServer |
    | org.gradle.jvm.environment                                 |          | android     |
    | org.jetbrains.kotlin.platform.type                         |          | androidJvm  |
   Selection reasons:
      - By constraint: xxxGoogleLocalServerDebugRuntimeClasspath uses version 2.7.1
      - By ancestor

androidx.work:work-runtime:2.7.1
\--- project :basic_library
     \--- xxxGoogleLocalServerDebugCompileClasspath

androidx.work:work-runtime:{strictly 2.7.1} -> 2.7.1
\--- xxxGoogleLocalServerDebugCompileClasspath

androidx.work:work-runtime:2.7.1
+--- project :si_account
|    \--- xxxGoogleLocalServerDebugCompileClasspath
\--- androidx.work:work-runtime-ktx:2.7.1
     \--- xxxGoogleLocalServerDebugCompileClasspath

androidx.work:work-runtime:2.7.0 -> 2.7.1
\--- com.google.android.engage:engage-core:1.5.1
     \--- xxxGoogleLocalServerDebugCompileClasspath

(*) - dependencies omitted (listed previously)

示例 3

示例 2:查看 app 模块,库 androidx.annotation:annotationdebugCompileClasspath 引用了该库的依赖关系:

1
./gradlew :app:dependencyInsight --configuration debugCompileClasspath --dependency androidx.annotation:annotation
1
./gradlew :AndroidUI:dependencyInsight --configuration debugCompileClasspath --dependency com.github.razerdp:BasePopup-compat-support
![image.png550](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/1692688467385-41902dd7-306a-4831-86ef-33069e7218e6.png)
  • 可以看到 com.github.razerdp:BasePopup-compat-support 库是被 me.hacket.netcore:1.0.0 引入的

依赖对比 dependency-tree-diff

1
2
3
4
./gradlew :app:dependencies --configuration releaseRuntimeClasspath > old.txt
# Update a dependency...
./gradlew :app:dependencies --configuration releaseRuntimeClasspath > new.txt
./dependency-tree-diff.jar old.txt new.txt

GitHub - JakeWharton/dependency-tree-diff: An intelligent diff tool for the output of Gradle’s dependencies task

so 依赖信息

要分析项目 so 依赖信息,可以监听 Task :app:mergeDebugNativeLibs

使用 Gradle 命令了解项目构建信息 - 掘金

Gradle 依赖杂项

Gradle 去除重复依赖

单独给某个模块指定 exclude

Exclude 后的参数有 group 和 module,可以分别单独使用,会排除所有匹配项。

1
2
3
4
5
6
7
8
dependencies {
    implematation ('com.dou361.update:jjdxm-update:1.0.3'){
        exclude group: 'com.dou361.download', module:'jjdxm-download'
    }
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.1') {
        exclude group: 'org.hamcrest'
    }
}

Configurations

Duplicate 问题:

Duplicate class com. Google. Common. Util. Concurrent. ListenableFuture found in modules jetified-guava-23.5-jre (com. Google. Guava:guava: 23.5-jre) and jetified-listenablefuture-1.0 (com. Google. Guava:listenablefuture: 1.0) Duplicate class org. Jetbrains. Annotations. Contract found in modules jetified-annotations-13.0 (org. Jetbrains:annotations: 13.0) and jetified-kotlin-compiler-embeddable-1.2.71 (org. Jetbrains. Kotlin: kotlin-compiler-embeddable: 1.2.71)

全局排除依赖

  1. 使用 kotlin 语言配置 gradle 全局排除依赖
1
2
3
4
5
6
7
8
9
configurations.all {
    exclude group: 'com.android.support', module: 'support-v13'
    exclude group: 'com.nineoldandroids', module: 'library'
    exclude group: 'com.facebook.fresco', module: 'stetho'
    //    exclude group: 'com.google.guava', module: 'listenablefuture'
    //    exclude group: 'org.jetbrains', module: 'annotations'
    //    exclude group: 'org.jetbrains.kotlin', module: 'kotlin-compiler-embeddable'
    exclude group: "junit", module: "junit"
}

强制使用某个版本

强制使用某个统一的依赖版本,而不是排除他们,那么此时 force 就该登场了。Force 强制设置某个模块的版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
configurations.all {
    resolutionStrategy {
        force 'org.hamcrest:hamcrest-core:1.3'
    }
}
// 或
allprojects { p ->
    configurations.all {
        // 低版本报错:Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
        resolutionStrategy.force "androidx.work:work-runtime:${Config.work_version}"
        resolutionStrategy.force "androidx.work:work-runtime-ktx:${Config.work_version}"
        resolutionStrategy.force "androidx.work:work-rxjava2:${Config.work_version}"
        resolutionStrategy.force "androidx.work:work-gcm:${Config.work_version}"
        resolutionStrategy.force "androidx.work:work-multiprocess:${Config.work_version}"
    }
}

Substitute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
allprojects { p ->
    configurations.all {
        /*
        * 修复构建的依赖传递导致merge失败, 如:
        * qsbk.app.remix
        *   ↳ :live (local and changed) <-----------┒
        *   ↳ qsbk.app:feed:x.y.z (remote)          ┆
        *     ↳ qsbk.app:live:x.y.z (remote) <------┚
        */
        resolutionStrategy.dependencySubstitution {
            def libs = ['core', 'libcommon', 'libwidget']
            libs.each {
                if (libsInSource.toBoolean()) {
                    substitute module(Config.Maven.groupId + ":${it}:" + Config.moduleVersion[it]) with project(":${it}")
                }
            }
        }
    }
}

将一个 module 替换成另外一个 module:

1
2
3
4
5
6
7
8
9
10
11
configurations.all {
    resolutionStrategy.dependencySubstitution {
        val lancetDep = dependenciesList.firstOrNull { it.key == "lancet" }
        val siLancetDep = dependenciesList.firstOrNull { it.key == "si_lancet" }
        if (lancetDep != null && siLancetDep != null) {
            substitute(module(lancetDep.dependencyNotation.toString()))
                .using(module(siLancetDep.dependencyNotation.toString()))
                .because("si项目有该配置了,codelocator也会引入lancet")
        }
    }
}

DependencyHandler

动态切换依赖为 module 还是 project
Interface DependencyHandler
**Dependency add (String configurationName, Object dependencyNotation) **
add 方法参数介绍,

  • String configurationName 参数 , 是一个字符串 , 就是在 build.gradle dependencies 中配置的 “ implementation “ , “ testImplementation “ , “ compile “ , “ androidTestImplementation “ 等字符串 , 表示依赖类型。VariantDependencies 中有很多 configurationName 的一些常量,如 VariantDependencies. CONFIG_NAME_IMPLEMENTATION
  • Object dependencyNotation 参数 , 指的是要加入的依赖 , 如 androidx.appcompat:appcompat:1.2.0, “ 样式的字符串 , 该依赖一般发布在远程的 maven 仓库中 , 也可以是本地的依赖库 ;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
static void dynamicDependence(
        final Project project,
        final DependencyHandler handler,
        final String configurationName,
        final String moduleName,
        final String projectName,
        boolean remote) {
    try {
        def dependencyNotation = ""
        if (remote) {
            dependencyNotation = convertToModule(moduleName)
        } else {
            dependencyNotation = convertToProject(projectName, moduleName)
        }
        if (remote) {
            // module依赖
            handler.add(configurationName, dependencyNotation)
        } else {
            // project依赖
            def path = dependencyNotation
            Map<String, String> projectNotation = ImmutableMap.of("path", path)
            handler.add(configurationName, handler.project(projectNotation))
            // 需要传递什么参数,见ProjectDependencyFactory#parseMap
        }
    } catch (Exception e) {
        println "> aiuiassistant2.0-Build > handleDependence -> $moduleName error > ${e.getMessage()}"
        throw e
    }
}
private static String convertToModule(String v) {
    def temp = "com.iflytek.autofly" + ":${v}" + ":1.0.0"
    return temp
}

private static String convertToProject(final String projectName, String v) {
    if (projectName == null || projectName.length() == 0) {
        return ":$v";
    }
    return ":${projectName}:${v}"
}
  • [Avoid repetitive dependency declarations with Gradle Kotlin DSLby Wojciech DziemianczykProAndroidDev](https://proandroiddev.com/avoid-repetitive-dependency-declarations-with-gradle-kotlin-dsl-97c904704727)

Gradle 代码添加依赖

Project.GetDependencies (). Add (“debugImplementation”, “me. Hacket:debugtools: 1.0.0”)

Gradle 依赖统一管理

project.ext 不支持代码提示

不支持代码提示;不可跳转到源码

  1. 项目根目录通过 ext 定义 Gradle 的依赖版本

依赖越多,导致根目录的 build.gradle 越来越长

1
implementation rootProject.ext.dependencies["appcompat-v7"]
  1. 单独的 config.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义config.gradle
ext {
    // 注意key定义的时候前后不要有空格
    // 基本配置信息config
    google_lib = [ 
        "lifecycle" : "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version",
    ]
    // ...
}
// 在根build.gradle引入config.gradle
apply from: "$rootDir/config/config.gradle"

// 引用
implementation google_lib["lifecycle"]
  1. 循环添加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 1、定义config.gradle
ext{
	dependencies = [ "appcompat-v7": "com.android.support:appcompat-v7:${version["supportLibraryVersion"]}"]
    annotationProcessor = ["glide_compiler": "com.github.bumptech.glide:compiler:${version["glideVersion"]}"]
    apiFileDependencies = ["launchstarter":"libs/launchstarter-release-1.0.0.aar"]
    debugImplementationDependencies = ["MethodTraceMan":"com.github.zhengcx:MethodTraceMan:1.0.7" ]
    implementationExcludes = ["com.android.support.test.espresso:espresso-idling-resource:3.0.2" : [ 'com.android.support' : 'support-annotations'] ]
}
// 2、在各个module的build.gradle中配置如下:
apply from config.gradle
def implementationDependencies = project.ext.dependencies
def processors = project.ext.annotationProcesso
def implementationExcludes = project.ext.implementationExcludes
dependencies{
    // 处理所有的 xxximplementation 依赖
    implementationDependencies.each { k, v -> implementation v }   
    // 处理 annotationProcessor 依赖
    processors.each { k, v -> annotationProcessor v }
    // 处理所有包含 exclude 的依赖
    implementationExcludes.each { entry ->
        implementation(entry.key) {
            entry.value.each { childEntry ->
                exclude(group: childEntry)
            }
        }
    }
}
// 后续添加依赖不需要改动build.gradle,直接在config.gradle中添加即可;精简了build.gradle的长度

buildSrc 有代码提示

支持代码提示;可跳转到源码
定义 Config.groovy 即可
[译]Kotlin + buildSrc:更好的管理 Gadle 依赖
https://juejin.cn/post/6844903615346245646

includeBuild 有代码提示

可跳转到源码
【奇技淫巧】除了 buildSrc 还能这样统一配置依赖版本?巧用 includeBuild
https://juejin.cn/post/6844904169833234439

Platform bom

AGP Catelog 无代码提示

在根目录的 gradle 目录中找到了 libs.versions.toml

依赖相关问题案例

实战:查看某个版本的依赖是哪个库带进来的(常用于解决编译时版本冲突的问题)

问题 1:kotlin 多个版本共存报错

举个例子,我项目中依赖的 kotlin 版本是 1.6.21,但是发现最终运行时依赖的版本是 1.7.10,那肯定是我依赖的某个组件内部依赖的版本是 1.7.10,导致最终实际依赖的版本不是 1.6.21,而是 1.7.10,此时,我想确认一下是哪个组件内部依赖了 1.7.10 版本的 kt。

方式一:./gradlew :模块名:dependencies

首先第一步还是先把整体的依赖拉出来,注意这里需要使用运行时环境的依赖信息。
然后直接搜关键字例如 kotlin:kotlin-stdlib:1.7.10
找到后往上找他的父级是谁,根据蓝色的 ±–去判断父级就行
image.png
发现是 androidx.appcompat:appcompat:1.5.1 内部依赖了 org.jetbrains.kotlin:kotlin-stdlib:1.7.10
然后去 mvnrepository 仓库中搜一下指定库确认一下。
image.png

可以看到 androidx.appcompat:appcompat:1.5.1 中确实依赖了 1.7.10 版本的 kotlin-stdlib

方式二:':app:dependencyInsight --configuration someConf --dependency someDep

通过命令打印出指定版本的依赖存在于哪些库中

./gradlew 模块名:dependencyInsight > 文件名.txt –configuration 环境 –dependency 要查找的依赖组件名称 // 要查找的依赖组件名称不一定要完全匹配,部分匹配的名称也可以

示例:

./gradlew app:dependencyInsight > kotlin.txt –configuration prdReleaseRuntimeClasspath –dependency kotlin:kotlin-stdlib

结果如下,也能找到是 androidx.appcompat:appcompat:1.5.1 中依赖了 kotlin-stdlib:1.7.10:
image.png

Duplicate class 问题:BasePopup BuildConfig

问题:编译时报错了,BuildConfig 在两个包中存在
image.png
分析:搜索项目中,并没有引入相关的库,猜测可能是某个库引入 BasePopup 库导致的 Duplicate Class 问题,但是没有源码,不可能一个个库去 Github 看他们的依赖吧
解决:用命令查找出依赖了 BasePopup 的库

./gradlew :AndroidUI:dependencyInsight –configuration debugCompileClasspath –dependency com.github.razerdp:BasePopup-compat-support

image.png
我们可以很清晰地看到:

  • debugCompileClasspath 环境下 :core 依赖了 :libwidget
    • :libwidget 依赖了 :libcommon
      • :libcommondebugCompileClassPath 环境下依赖了 me.hacket.netcore:1.0.0
        • netcore 又依赖了 com.github.razerdp:BasePopup-compat-support:2.2.1,找到了依赖关系就好处理,版本统一下或去掉 BasePopup 库的依赖都可以

Duplicate class:protobuf AbstractMessageLite

错误:protobuf 冲突了
image.png
分析:发现是 com.google.protobuf:protobuf-javalitecom.google.protobuf:protobuf-lite 库冲突了,现在就是要找到哪些库引入了这 2 个库
实施:在 app module 执行下面命令找上面 2 个库的依赖引用,如果 app module 找不到就换个 module name

./gradlew Google:dependencyInsight –configuration debugCompileClasspath –dependency com.google.protobuf:protobuf-javalite

image.png

./gradlew AndroidDemos:dependencyInsight –configuration debugCompileClasspath –dependency com.google.protobuf:protobuf-lite

image.png
结论:从上面的两个库的引用来看,知道是 com.google.firebase:firebase-firestore-ktxcom.google.crypto.tink:tink-android 两个库冲突了,都引入了 protobuf 库
解决:去除重复依赖

1
2
3
implementation("com.google.firebase:firebase-firestore-ktx") {
    exclude group: 'com.google.protobuf', module: 'protobuf-javalite'
}

接入了某个库,导致编译不过

新接入的 si_security_adaptersdk 依赖了 basic 库,basic 库是旧代码
image.png
然后新的代码不一样,API 变了,编译不过
image.png
解决:

1
2
3
4
5
6
7
8
9
10
tasks.register("printFilesModule") {
    setGroup('hacket')
    doLast {
        configurations.each {
            if (!it.name.toLowerCase().contains('test') && !it.name.toLowerCase().contains('kapt') && !it.name.toLowerCase().contains('lint') && !it.name.toLowerCase().contains('androidtest')) {
                println("------------->>>> configurations=> ${it}")
            }
        }
    }
}

通过 task printFilesModule 获取主 module 的依赖关系,
再通过下面命令获取依赖关系,排查出问题

./gradlew xxx:dependencies > xxx_deps_xxx_module.txt –configuration xxxGoogleLocalServerDebugRuntimeClasspath

解决:si_verify_adapter 库用 compileOnly 方式依赖 basic 库

failOnVersionConflict 版本冲突报错

如果你想对版本冲突的依赖项做版本管理,但是又不知道当前项目中有哪些依赖是重复的,从 External Libraries 里面一个一个的看又太费劲。

开启版本冲突报错模式:

1
2
3
4
5
6
configurations.all {
    resolutionStrategy{
        failOnVersionConflict()
    }
    // ...
}

加上 failOnVersionConflict() 之后,编译解析的时候只要有重复的版本,也就是版本冲突的时候,就会直接报错,控制台会输出具体的依赖项和版本。

![image.png500](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian202403140051460.png)

其他问题

kotlin 作为依赖库 AAR 的时候看不到源码

![image.png500](https://raw.githubusercontent.com/hacket/ObsidianOSS/master/obsidian/20240312140940.png)

库包含本地 aar,找不到问题

项目库中需要打包一个 aar,这个 aar 含有第三方依赖。生成 aar 后,导入主项目,结果报错,第三方的类找不到(aar 生成时,包含有第三方依赖,比如第三方的 OkGo;aar 项目的 gradle:)

1
api ('com.lzy.net:okgo:3.0.4'){transitive=true}

解决办法:
需要上传 aar,不管上传本地还是 maven

  • 作为本地 module 依赖是没问题的,第三方依赖也是没问题的
1
api project(path: ':mylibrary2')
  • 上传到 maven 库,也是可以传递 aar 的第三方依赖

测试

project 引用

  1. implementation project
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// mylibrary1
dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'

    implementation "com.squareup.okhttp3:okhttp:3.12.10"
}

// mylibrary2
dependencies {
    implementation project(path: ':mylibrary2')
}

此时在 mylibrary1 引用不到 OkHttp 相关的类,mylibrary1 所有的 implementation 都引用不到。
如果 mylibrary1 改为 api 就可以引用

1
2
3
4
5
6
7
8
9
// mylibrary1
dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'

    api "com.squareup.okhttp3:okhttp:3.12.10"
}

aar 引用

本地 AAR 包引用第三方依赖无效

1
2
3
4
dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    api fileTree(include: ['*.aar'], dir: "${rootDir}/aar")
}

2 files found with path ‘META-INF/gradle/incremental.annotation.processors’ from inputs

1
2
3
4
5
6
7
8
Execution failed for task ':app:mergeyyyGoogleReleaseDevServerDebugJavaResource'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.MergeJavaResWorkAction
   > 2 files found with path 'META-INF/gradle/incremental.annotation.processors' from inputs:
      - /Users/xxx/.gradle/caches/modules-2/files-2.1/androidx.lifecycle/lifecycle-compiler/2.5.1/1d36fa846d1ffa7235a91fed54e3c5704c56eb00/lifecycle-compiler-2.5.1.jar
      - /Users/xxx/.gradle/caches/transforms-3/6945b46a4db0906e6e5ff30ca08f60ea/transformed/jetified-auto-service-1.0.jar
     Adding a packagingOptions block may help, please refer to
     https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
     for more information

原因:lifecycle-compilergoogle-auto-service 库都有 META-INF/gradle/incremental.annotation.processors
解决:选择其中一个就行

1
2
3
4
5
6
android {
    packagingOptions {
         // auto-service和lifecycle-compiler冲突
         pickFirst 'META-INF/gradle/incremental.annotation.processors'
     }
}

Ref

本文由作者按照 CC BY 4.0 进行授权