书接上文:
- Gradle 爬坑指南 -- 导论
- Gradle 爬坑指南 -- 概念初解、Grovvy 语法、常见 API
- Gradle 爬坑指南 -- 理解 Plugin、Task、构建过程
- Gradle 爬坑指南 -- Gradle 核心模型、Hook 函数、ext 扩展属性、Project API
前面的内容算是把 Gradle 基本的概念讲清楚了,要想用好 Gradle 这个工具、框架,单靠这些内容远远不够,有些内容还需要大家继续深入研究、学习。这部分内容才是实际项目中 莫名其妙、怎么搞不懂 的真正所在。本文对这部分内容也是尽量深入一点,剩下的还是要靠大家自己,多找找资料丰富自己
依赖分析工具
先介绍几个命令行分析工具,我觉得很有必要从这里开始
1. Profile 命令
gradle assembleDebug --profile
命令可以分析项目构建过程,可以看性能、可以看耗时、构建每个阶段、方法耗时清清楚楚,还可以看依赖版本统计。虽然 AS 在构建完毕之后有蓝字提示,点击后可以看 AS 对构建性能的分析,但是哪有这个工具来的清晰
Profile 工具会在 根目录/build/reports/profile
中生成 HTML 格式的分析文件的分析文件
,内容清晰大方、方便查看、方便截图、截取内容做文档用
上面几个 tab 就不说了,大家都看得懂,Task Execution 会统计 Task 总的耗时。这里要注意,Gradle 在 Task 执行阶段是 多线程并行模式,最多8个线程 ,所以统计的总时间是每个线程耗时之和
assembleDebug
是变体,你换个变体也行,不写变体可以这样写 gradle build --profile
Gradle 为了加快构建速度,现在带 Task 缓存了,所以要分析构建性能还得在 AS 启动后第一次构建时进行
还可以更精准的分析构建性能
gradlew --profile --recompile-scripts --offline --rerun-tasks assembleDebug
有几个参数可配:
profile
--> 开启性能检测recompile-scripts
--> 不使用缓存,直接重新编译脚本offline
--> 启用离线编译模式return-task
--> 运行所有 gradle task 并忽略所有优化
2. dependencies 命令
1. gradlew dependencies
2. gradlew app:dependencies
3. gradlew app:dependencies --configuration implementation
dependencies 命令是用来分析依赖的,我们一般都用第3个命令,加上 module 和 implementation 限定,要不出来的信息太多,在命令行里看的太费劲,加上限定之后好多了,加上限定就是分析 module 中我们自己添加的依赖
不过说实话,信息太多,用命令行看真是蛋疼,所以用官方出了一个叫 Scan
的工具来帮助我们查看结果
3. Scan 工具
Scan
是 Google 官方推出的,用于诊断应用构建过程的性能检测工具,可以分析构建性能,能详细的看到单个 Task 的耗时,在整个构建过程中的时间点位置,也能分析依赖版本号,这点尤其有用,是我们分析依赖冲突的有力武器
在项目根目录位置下运行 gradle build --scan
即可,然后会生成 HTML 格式的分析文件的分析文件 ,中间会卡住询问 yes/on,输入 yes,有时候这个询问看不到,会一直卡在那,这时输入 yes 就行,我这就经常这样
分析文件会直接上传到 Scan 官网,命令行最后会给出远程地址
第一次跑会让你在 Scan 官网注册一下,邮件确认后就能看了,这里我们的目标是分析依赖决策(就是依赖版本号),scan 工具是按照依赖变体挨个分类的,debugCompileClassPath
就是 dedug 打包中的依赖包了
4. 分析具体依赖
分析某个具体依赖很有必要,有时候你不知道决定依赖版本的最终是哪个依赖、在哪里,用 Scan 和 命令行 都可以
我们以 rxandroid 为例,rxandroid 里依赖了 rxjava,后面我们再手动引入一个低版本的 rxjava,来看看怎么做依赖分析
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "io.reactivex.rxjava2:rxjava:2.0.0"
1) 命令行
gradle :app:dependencyInsight --configuration debugCompileClasspath --dependency rxjava
- 上半部分先是声明 rxjava 最终决议使用的版本是 2.2.6
- 然后 2.2.6 这个版本的依赖来自于 rxandroid 2.1.1
2) Scan
Scan 里面看依赖分析,每一条依赖右上角都有一个小标记,单开就能看具体的分析过程了
依赖管理
在早期,Gradle 每次编译总是 迷一样,编译全靠运气 ,编译失败大部分原因都是依赖出问题了。当然现在这种情况少很多了,但是对于 依赖 我们必须搞明白,Gradle 太多的地方都有依赖的存在,不搞清楚关于依赖的部分,你发布 aar 到 Maven 都做不好,出了问题很难解决
本文解决的就是大家 一头雾水,出了问题一点头绪没有 的这种窘境。另外依赖管理是自动化构建的一大核心点,所以这块请大家务必掌握
依赖分类
Gradle 依赖分:直接依赖,项目依赖,本地 jar、aar 依赖,传递依赖
,这几个总规要分清楚的,多少还是有些差别的
- 直接依赖 --> 在项目中直接导入的依赖,就是直接依赖
implementation 'androidx.core:core-ktx:1.3.2'
- 项目依赖 --> 这个好理解,app module 依赖 libs module,libs 就是项目依赖
- 传递依赖 --> 依赖里面的依赖,好比 rxandroid 里面还依赖了 rxjava,rxjava 对于外层来说就是传递依赖。传递依赖就是 Gradle 做的烂的一个地方,哪个项目中传递依赖不是一层套一层的,最后套了太多层,Gradle 自己分析起来都很耗时,还容易出现问题造成编译失败,有人管这叫: 依赖地狱
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
--> 内部依赖了 implementation "io.reactivex.rxjava2:rxjava:x.x.x"
版本号法则
1.3, 1.3.0-beta3
--> 固定版本号[1.0.0, 1.3.0)
--> >= 1.0.0 < 1.3.0 ,[ ) 符号都可以写- [ 含 =
- ) 不含 =
1.+, [1.0,)
--> >= 1.0 版latest.integration、latest.release
--> 最新版本
因为 Gradle 没有自己的远程仓库,而是使用 Maven、jcenter、jvy 这些远程仓库,所以在添加远程依赖时,Gradle 首先按照 Maven POM 规则查找远程依赖,版本号这块大家看 Maven POM--文档 这部分就行了,写的更详细
添加依赖
注意每种资源添加依赖的区别,看着乱,但还是有规律的。Gradle 中依赖的设置都是通过 Configuration
对象完成的,可以通过代码操作,目前没看到有什么实际应用场景,应该是我段位低的缘故
1) jar 文件
module 对于依赖的 jar 包,会直接把 jar 中的代码添加到自己的项目里。举个例子,子项目 module 中有依赖 jar 文件,然后 module 打 aar 上传到 Maven,我们来看看 jar 在 aar 中如何储存的
很明显,jar 包中的类库会直接添加到构建最终产物 aar 中
正是因为这样,jar 包依赖容易出现冲突。大家想2个 aar 中依赖了相同的 jar 包,那么对于项目来说,就有2个 包名+类名 完全一样的类库,必然会冲突,解决冲突的办法下面会说
添加依赖的方式一般就是这2种了,基本上大家都是直接放到 libs 里的,一个个写太费事
implementation files('hibernate.jar', 'libs/spring.jar')
implementation fileTree(dir: 'libs', include: ['*.jar'])
对于 Gradle Android Plugin 来说,会把 jar 看成一种本地代码资源,implementation fileTree() 是声明本地 java 资源路径。其实这和依赖一个 module 项目本质上是一样的,区别是资源类型的不同,一个是 file,一个是 project
大家对比下看看
implementation project(':pickerview')
implementation files('hibernate.jar', 'libs/spring.jar')
2) so 文件
.so
文件和 .java
一样,会被 Gradle 看成一种本地代码资源,只要设置 .so
资源的路径即可
一般有2种方式:
- 一个是按照 android 插件默认的文件路径放置 .so 文件,这样 android 插件会按照默认的路径查找,引入
so
文件 - 一个是我们手动设置 .so 资源路径
// 设置 .so 资源路径
android{
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
需要注意的是,so 文件需要放到具体的 ABI 目录下,不能直接放 libs 目录下,一般都是这样的
x86/x86_64/armeabi-v7a/arm64-v8a
这些叫 ABI,每一种都对应一种 CPU 指令集 ,在 defaultConfig{...} 中设置使用哪些 ABI
android{
defaultConfig {
ndk {
abiFilters "armeabi", "armeabi-v7a", "x86", "mips"
}
}
}
3) ABI 说明
Android 平台 CPU 有三大类:ARM、x86、MIPS,其中 x86、MIPS 已经被彻底淘汰了,大家集成 .so
只要考虑 ARM 平台架构就行了
ARM 平台架构主要有3种,下面说的代对应的是架构:
armeabi:
第5代、第6代 ARM 处理器,早期 android 手机用,A5/7/8/9 核心用的都是这个架构armeabiv-v7a:
第7代、32位 ARM 处理器架构,带浮点数计算能力。A15/17 核心用的都是这个架构,一般现在也少见了,也都淘汰了arm64-v8a:
第8代、64位 ARM 处理器架构,A32/35/53/57/72/73 核心用的都是这个架构,一般现在的 ARM 处理器都在这个范围内
这个大家都能看到手机 CPU 是哪家的,哪个型号的,都会宣传采用了 AXX 的 ARM 核心
一般来说现在选择集成 V7、V8 的 ABI,你要是还想兼容特别老的手机可以加上 armeabi,armeabi 的缺点就是执行速度慢,2020年开始 google paly 已经开支强制提供 64位 so 了,来自官方版的说法是很可能未来很快新的 CPU 就都是 64位并且执行支持 64 指令集的设计了
可能干说大家还是每个清晰的认识,那么结合 ARM 架构发展历程来看看吧
ARM 架构发展历程:
左侧的是架构,右侧的是处理器,也可以叫核
- V1 架构 1985
- V2 架构 1986
- V3 架构 1990
- V4 架构 1993
- V5 架构 1998
- V6 架构 2001
- V7 架构 2004
- V8 架构 2011
4) 远程依赖
Gradle 没有自己的远程仓库,用的是 Maven,Jcenter,Jvy 这些库,所以添加远程依赖,要先声明使用哪个远程仓库
每个 Gradle 脚本都要声明使用的远程仓库,所以根目录脚本才会用 allprojects{...} 给每个子项目都声明远程仓库地址
buildscript {
ext.kotlin_version = "1.4.10"
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
allprojects {
repositories {
google()
jcenter()
}
}
下面这么写可能会好看一些 (○` 3′○)
dependencies {
androidTestImplementation([
'androidx.test.ext:junit:1.1.2',
'androidx.test.espresso:espresso-core:3.3.0'
])
implementation([
'androidx.appcompat:appcompat:1.2.0',
"io.reactivex.rxjava2:rxandroid:2.1.1"
])
}
或者有人在 ext{...} 里面写 implementation,然后在脚本里 each 遍历添加的,不上代码了,我觉得依赖没这么多,没必要这么写,不利于查看依赖项
5) aar 文件
Gradle 把 aar 文件一样视为远程依赖,所以同样要在 repositories{...} 里声明 aar 文件仓库地址,不过这个仓库的设置就有些麻烦、复杂了
若是 app module 依赖本地 aar,这么写就行了
android{
...
}
// 声明本地 aar 文件地址
repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
// 添加本地 aar 文件依赖
implementation(name: 'easeui', ext: 'aar')
但要是 lib 这样的,给别的 module 提供依赖的 module,依赖 aar 文件的话,必须在整个依赖链条上所有的 module 都写上这个 aar 文件所在的仓库地址,一般这种情况,我们都是在根目录脚本 allprojects{...} 里添加 aar 仓库,注意这里 aar 仓库的地址淂写 aar 文件所在 module 这一层级的地址
allprojects {
repositories {
google()
jcenter()
flatDir {
dirs '../animal/libs'
}
}
}
implementation(name: 'easeui', ext: 'aar')
图示如下:app 依赖 lib module --> lib module 依赖 animal module --> animal module 中有 aar 依赖 easeui.arr
或者呢你要是嫌麻烦,可以在项目根目录下建一个 libs 文件夹,所有的 aar 都放到里面,然后在跟脚本统一设置下
依赖管理的核心是什么
implementation
导入远程依赖大家熟悉吧,那么大家知道依赖导进来是怎么管理的吗?
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "io.reactivex.rxjava2:rxjava:2.0.0"
Gradle 管理依赖是依靠2个 Classpath:compileClasspath、runtimeClasspath
compileClasspath
--> 编译时能使用的代码、类。当一个组件参与编译时,Gradle 就会将其放在 compileClasspath 中runtimeClasspath
--> 运行时能使用的代码、类。当一个组件参与打包时,Gradle 就会将其放在 runtimeClasspath 中
-
编译时 --> 大家熟悉吗,不熟悉的这里我说下。什么是编译时,就是我们写完远程依赖,AS 把代码下来来,我们写代码时能代用到类库时、我们自己写代码时,代码还在编写阶段,只要没编译成
.class
都叫 编译时 -
运行时 --> 大家熟悉吗,不熟悉的这里我说下。什么是运行时,代码我们写完了,也编译成
.class
了,在机器上安装完,跑起来,这个时候叫运行时 -
compileClasspath --> 中包含的代码、类库,是我们在编写代码时能使用到的类库。这个 path 里要是没有我们想要调的类库,即便我们把远程依赖代码下下来也没用
-
runtimeClasspath --> 中包含的代码、类库,是 app 跑起来之后能找到的类库。即便我们把远程依赖代码下下来并写到 compileClasspath 里了,我们写代码时可以调用对应的类库,但只要没写到 runtimeClasspath 里面去,app 跑起来一样还是会找不到类库,会 crash 的
-
implementation --> 这些只是对于依赖库不同的管理方式,核心逻辑就是决定远程拉下来的代码是放在 compileClasspath 里还是 compileClasspath 里,还是2个都放,还是只放一个,另一个不放
结合 implementation、api 理解 compileClasspath、runtimeClasspath
可能有的同学 api
不怎么熟悉,但是没关系,往后看就行
举例:A依赖B、B依赖C,A、B、C 都是项目 module
- 若 A implementation B,B implementation C,那么
- 在 B 中 C 的类库可以随便用
- 在 A 中就不能使用 C 的类库,IED 提示找不到对应的类
- 这是因为 implementation 引入的依赖,会把 C 加入 B 的 compileClasspath 和 runtimeClasspath,会把 C 加入 A 的 runtimeClasspath
- 因为没有加入 A 的 compileClasspath,所以在 A 项目中使用不了对应的代码,但是因为加入了 A 的 runtimeClasspath,则会把 C 的类库打包进 APK 中
- 若 A implementation B,B api C,那么
- 在 B 中 C 的类库可以随便用
- 在 A 中 C 的类库一样可以随便用
- 这是因为 api 引入的依赖,会把 C 加入 B 的 compileClasspath 和 runtimeClasspath,同样会把 C 加入 A 的 compileClasspath 和 runtimeClasspath
- 因为 C 即加入了 A 的 compileClasspath ,也加入了 A 的 runtimeClasspath,所以在 A 项目中既能使用 C 的类库,也会把 C 的类库打包进 APK 中
- 若 B compileOnly C,则 A 无法调用 C 的代码,且 C 的代码不会被打包到 APK 中
- 若 B runtimeOnly C,则 A、B 都无法调用 C 的代码,但 C 的代码会被打包到 APK 中
关于这点大家可以自己去试试
深入理解 compileClasspath、runtimeClasspath
compileClasspath、runtimeClasspath
这2个 path 是每个依赖级别都有的,比如上面的例子,ABC 3个都是 module 项目,每一级 module 都有自己的 compileClasspath、runtimeClasspath
来管理自己这个级别使用到的依赖,远程依赖同样如此,比如 rxandroid 里面就依赖了远程的 rxjava,对于 rxandroid 这个远程依赖来说,同样也有 compileClasspath、runtimeClasspath
这2个 path 来管理自己的依赖。最后在构建阶段,Gradle 会逐级合并 compileClasspath、runtimeClasspath
,决定哪些依赖可以在代码编写时使用,哪些依赖会打包进 APK
对于 module 项目这种本地代码依赖来说比较好理解,对于远程 aar 依赖来说就有点费解了,感谢百度团队的文章做出了详细解释:Gradle 与 Android 构建入门
当使用 Maven 规范上传 aar
时,不单单会上传 aar 二进制为念,还会上传一个 pom.xml 文件,aar 中使用的远程依赖信息就记录在这个文件里,pom 是个 xml 文件,我们看一下:
很明显,我们上传 Maven 的 aar 文件中不包含我们在编写 aar 时依赖的远程依赖代码,这些我们依赖的远程依赖以 xml 文件的形式表明,上图中的 pom 文件有2个远程依赖,一个用于 complie 编译阶段,一个用于 runtime 运行时阶段,我们在 AS 中添加这个 Maven 上的 aar 后,AS 会自动解析该 aar 中的 pom 文件,去下载相关远程依赖的代码
这下大家清楚了吧,不管是项目依赖,还是远程依赖,对于子依赖的管理都是一个规则,搞清楚2个 path 的作用,我们就能清晰的知道代码是否可以 隔着项目使用了
举个例子:rxandroid 中有依赖 rxjava,我们添加 rxandroid 的依赖后,rxjava 的代码能不能用,试了下可以用,为啥呢,我去看了 rxandroid 的源码,原来 rxandroid 中是 api
的 rxjava
Gradle 的 Scope
Gradle 这部分其实就是照着 Maven 的 Scope 来的,Scope 就是 implementation、api 这些,操作的是依赖在什么时候可以用,用 path 解释就是依赖放在哪个 path 里面,是编译时可以用,还是运行时可以用
Gradle 的 Scope 基于2个维度设计:
- 一个就是编译时还是运行时,影响依赖添加到 path 的结果,这种 Scope 常见的就是:
implementation、api
这俩货了 - 另一个就是结合 buildType 构建类型了,这点大家看自动提示就明白了
compile 已经废弃,用 implementation 代替
其他的 Scope 还有,这几个也常见:
compileOnly
仅编译期有效, 不会出现在最终产物中runtimeOnly
仅运行期有效, 会出现在编译产物中annotationProcessor
注解处理器依赖
Gradle 的 Scope 不光可以可以影响子项目资深的 compilePath、runtimePath,还可以记住依赖传递来影响上级项目的 compilePath、runtimePath,大家再结合下 implementation、api 体会下。Gradle 的 Scope 操作的都是 path,可以是项目自己的,也可以是上级项目的,区别是范围不同。然后结合 Gradle 的依赖传递,影响最终 APK 打包时都有哪些文件
POM 文件中的 Scope
上面提到过 pom.xml 文件,子项目打包 aar 上传 Maven,子项目中依赖的远程依赖不会把代码打包到最终的 aar 文件中,而是会生成一个描述 aar 使用远程依赖状况的描述文件 pom.xml,其中每个依赖都有一个 scope 属性,该属性和 implementation、api 一样,影响的也是依赖在 path 中的位置,具体下个小节会提到,这里先看看 scope 属性有哪几种类型:
此元素引用手头任务的类路径(编译和运行时,测试等),以及如何限制依赖项的可传递性
有五个作用域:
compile
- 这是默认范围,如果未指定则使用。编译依赖项在所有类路径中均可用。此外,这些依赖项会传播到相关项目provided
- 这很像编译,但是表明您希望JDK或容器在运行时提供它。它仅在编译和测试类路径上可用,并且不可传递runtime
- 此作用域指示依赖关系不是编译所必需的,而是执行所必需的。它在运行时和测试类路径中,而不在编译类路径中test
- 此范围表明该依赖关系对于正常使用该应用程序不是必需的,并且仅在测试编译和执行阶段可用。它不是可传递的system
- 此范围类似于,provided除了必须提供显式包含它的JAR之外。该工件始终可用,并且不会在存储库中查找
子项目打包上传 aar 时 implementation、api 对 pom.xml scope 的影响
还是上面 rxandroid、rxjava 的例子,还是用上面的那种图说事
Gradle scope 和 Maven 对照,核心内容都一样,就是写法有些变化,testCompile 对应的就是 Maven 里面的 Scope
简单来说:
- 子项目中 rxandroid 要是 implementation rxjava,那么这个 scope 就是 runtime
- 子项目中 rxandroid 要是 api rxjava,那么这个 scope 就是 compile
假如子项目中 rxandroid implementation rxjava,然后打包 aar 上传 Maven,我们再依赖 rxandroid 这个远程依赖库,大家猜猜会怎样,会报错,提示找不到 rxjava 的类
为啥么,大家想此时的 pom.xml 文件中,rxjava 的 scope 是 runtime 的,也就是打包 APK 时代码才会生效,我们编译、写代码时是找不到 rxjava 类库的,那特定会报错的,除非我们自己在项目中再添加 rxjava 的远程依赖才行
所以 rxandroid 基于实用性上考虑,最终还是 api 的 rxjava,这样我们引入 rxandroid 的远程依赖就不会报找不到类了,要不真的很糟心,开发人员最讨厌这种麻烦事了
依赖决议
依赖决议是指 "在编译过程中, 如果存在某个依赖的多个版本, 构建系统应该选择哪个进行构建的问题"
依赖决议处理的是远程依赖版本冲突的问题,来看下典型案例:
A、B、C 都是本地子项目 module,D 是远程依赖
- 编译时: B 用 1.0 版本的 D,C 用 1.1 版本的 D,B 和 C 之间没有冲突
- 打包时: 只能有一个版本的代码最终打包进 APK,对于 Gradle 来说这里就有冲突了
不过 Gradle 自有其处理方法,不考虑其他强制设置版本号的手段,就说默认。默认下,Gradle 会使用最新版本的远程依赖,使用 Scan 工具分析依赖时大家能看到
系统处理的是死的,这里我碰到过坑,就是前后版本不兼容的问题。老版本有带参数的构造函数,新版本这个带参数的构造函数直接被删除了。Gradle 默认的版本决议解决不了这种问题,这只能靠大家来协调了,谁改下,另外也是提醒我们,公司里写功能组件时一定要前后版本兼容,要不很容易出那些 莫名其妙 问题来,这种问题及其难以定位,发觉,很多时候是自己给自己埋的大坑
强制依赖版本
1) isFoce
isFoce 标记会强制使用该标记版本的依赖
dependencies {
implementation("io.reactivex.rxjava2:rxjava:2.2.6") {
isForce = true
}
implementation("io.reactivex.rxjava2:rxjava:2.2.10")
}
// 依赖决议使用 2.2.6 版本
注意:
- 同一个 module 中,对同一个依赖多次书写 isForce,只有第一个会生效,后面的都没用
- isForce 只作用于当前模块, 不同模块中的 isForce 相互独立
- isForce 跨模块,这些模块相互依赖,那么以 app 的 isForce 为准,因为 app 的 脚本先执行
- isForce 的版本并不会反映到 POM 文件中, 这个必须清楚,要使用 isForce 一定要谨慎
isForce 出现的非常早,实际使用起来问题很多,因书写不当很容易造成构建失败,现在建议不要再使用了
isForce 最大的问题是这个:
force 的版本不能比 app 主模块的版本低,要不就报错。比如 app 模块依赖于 lib, 如果 app 中引入了 rxjava:2.2.10, lib 却 force 了 rxjava:2.2.6 则会发生编译错误
遇到这个问题:
- 去掉 lib 中的 force
- 可以在 app 中再次 force 该依赖即可,无论高低版本
2) strictly
strictly 是一种强力依赖版本约束,官方现在推荐使用这个,可以用 !!
简写
dependencies {
implementation("io.reactivex.rxjava2:rxjava:2.2.0!!")
implementation("io.reactivex.rxjava2:rxjava") {
version {
strictly("2.2.0")
}
}
}
这样就会强制使用 2.2.0 版本的依赖了
3) constraints
constraints 是 strictly 新版本的替代,东西和 strictly 是一样的
dependencies {
implementation("io.reactivex.rxjava2:rxjava:2.2.6!!")
constraints {
implementation("io.reactivex.rxjava2:rxjava:2.2.6!!")
}
}
但是同样也有问题,constraints 的版本不能比其他的低,要不也会报错,下面的代码就是有问题的,会报错
dependencies {
implementation("io.reactivex.rxjava2:rxjava:2.2.4")
constraints {
implementation("io.reactivex.rxjava2:rxjava:2.2.0!!")
}
}
4) 吐槽
这几个强制版本依赖都有问题,不当设置都会报错,说实话我觉得还是按系统默认的走就好了,选最新的版本号就好了,搞得这么复杂说不好就给自己挖坑
jar 包冲突
上面说过 .jar 文件会以代码的方式添加到最终构建产物中,aar 会直接包含 .jar 中的代码。要是本地项目和远程依赖都引入了同一个 .jar 的话会冲突的,会提示有相同包名+类名冲突的资源,这里的话可以在方便的位置用 compileOnly 代替 implementation
远程依赖冲突
Gradle 在 implementation{...} 中提供了 exclude 设置,可以忽略指定的依赖,被忽略的依赖将被视为从来没有依赖过
先熟悉下 group、module、version
指的都是哪部分
implementation("io.reactivex.rxjava2:rxandroid:2.1.1")
group = io.reactivex.rxjava2
module = rxandroid
version = 2.1.1
一般我们可以这么写
implementation('org.hibernate:hibernate:3.1') {
exclude module: 'cglib'
exclude group: 'org.jmock'
exclude group: 'org.unwanted', module: 'iAmBuggy'
}
远程依赖冲突一般我们通过 exclude、force、transitive
解决冲突,其中 force 已经不推荐使用了,下面提供一些范例:
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
def requested = details.requested
if (requested.group == 'com.android.support') {
if (!requested.name.startsWith("multidex")) {
details.useVersion '26.1.0'
}
}
}
}
依赖传递
前文我用 implementation、api 来讲解 compileClasspath、runtimeClasspath,A依赖B、B依赖C,C作为依赖会传递给A。Gradle 就是这么一层层、逐级的合并 path,才能最终知道哪些代码应该打进 APK 文件中,即便这会带来依赖冲突、性能损耗的问题,但是正是这种机制,才能使得代码齐全,不会缺少
我们 implementation 一个远程依赖,依赖传递默认是启动的。当然依赖传递也有操作余地,系统提供了 transitive
让我们自己选择是不是要把依赖传递下去
导入远程依赖,transitive 默认是 true 的,就像下面一样
implementation("io.reactivex.rxjava2:rxandroid:2.1.1"){
transitive(true)
}
现在我们把 transitive 写成 false 看看
例:app implementation libs、libs api rxandroid,这样我们在 app 中也能够使用 rxandroid 的 API,我们给 libs api rxandroid 设置 transitive(false) 后,app 中就找不到 rxandroid 的类库了,因为依赖无法被传递了
api("io.reactivex.rxjava2:rxandroid:2.1.1"){
transitive(false)
}
我不知道 transitive 有什么应用场景,想来应该可以用在开源库中,比如 Google 官方组件这样大家都会用的设置 transitive 就挺合适,设置 transitive 之后,依赖是不会打入 APK 中的(除非是 app module)