概述
又开始了一个新的系列,这个系列学习Gradle,目标就是彻底理解Gradle,主要还是做下自己理解的笔记,防止忘记
简介
在平时的使用中依赖是一个逃不过的坎,总是因为各种原因导致编译报错,今天我们就好好的理解下依赖,以及常见问题的解决
依赖类型
Gradle依赖分别为
直接依赖,项目依赖,本地jar arr依赖,传递依赖
,下面区分下这些依赖的意思
- 直接依赖:在项目中直接导入的依赖,就是直接依赖
dependencies {
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
}
- 传递依赖:A项目依赖了okhttp,而okhttp 依赖了 okio,最后A项目也就依赖了okio,也就是说依赖是有传递性的
本地Libary模块依赖
implementation project(':mylibary')
这种依赖方式是直接依赖本工程中的
libary module
,这个
libary module
需要在
setting.gradle
中配置
本地二进制的依赖
这个主要包括本地jar文件依赖,aar文件依赖和本地so文件依赖
本地jar文件依赖,一般包括这俩种形式
//直接依赖某文件
implementation files('libs/foo.jar', 'libs/bar.jar')
//配置某文件夹作为依赖项
implementation fileTree(dir: 'libs', include: ['*.jar'])
本地arr文件依赖,一般包括这俩种形式
//依赖单个arr文件
implementation(name: 'facesdk', ext: 'aar')
//依赖某个文件夹中的所有aar文件
implementation fileTree(include: ['*.aar'], dir: 'libs')
在使用之前需要设置arr文件夹的位置
// 声明本地 aar 文件地址
repositories {
flatDir {
dirs 'libs'
}
}
so文件依赖
.so文件和.java文件一样,会被Gradle看成本地的代码资源,只需要设置so的资源路径就可以拿到so文件
依赖so文件,一般有俩种形式
- 使用android插件默认的文件路径存放so文件,这样android会按照默认路径去寻找so,默认路径在/src/main/jniLibs
- 另一种是我们手动设置so文件路径位置
// 设置 .so 资源路径
android{
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
远程依赖
Gradle
没有自己的远程仓库,所以要添加远程依赖要声明使用哪个远程仓库,每个
Gradle
脚本都需要声明仓库,一版情况在在根目录的
build.gradle
配置子
project
的仓库
allprojects {
repositories {
jcenter()
maven {
url 'https://maven.google.com/'
name 'Google'
}
}
}
然后就可以远程依赖的配置了
implementation 'com.example.android:app-magic:12.3'
上面是简写法,完整版写法如下:
implementation group: 'com.example.android', name: 'app-magic', version: '12.3'
group
name
version
共同定位一个远程仓库,version最好写一个固定的版本号,以防构建出问题
依赖管理的核心
Gradle依赖管理是依靠俩个classpath:
compileClasspath、runtimeClasspath
-
compileClasspath:编译时能使用的代码和类,当一个组件参与编译时,Gradle会将其放在compileClasspath中
-
runtimeClasspath:运行时使用的代码和类,当一个组件参与打包时,Gradle就会将其放在runtimeClasspath中
-
编译时:代码还在编写阶段,只要还没有编译为class,就是编译时
-
运行时:当编译成class文件,在机器上运行的时候叫做运行时
-
compileClasspath中含有的代码和类库,是我们在编写代码的时候需要使用到的类库,如果这里面没有的类库,我们编写时是会找不到该类的
-
runtimeClasspath这个里面主要包括app运行时需要的类库,如果这个里面没有包含的库,那么运行时找不到类而crash
-
implementation、api 这些操作符只是对依赖库不同的操作方式,其核心的逻辑就是,从远程拉下来的库到底是放在
compileClasspath
中还是
runtimeClasspath
还是
俩个都放
依赖管理配置
目前Gradle版本支持的依赖配置
implementation、api、compileOnly、runtimeOnly 和 annotationProcessor
已废弃的是
compile、provided、apk、providedCompile
下面我们通过implementation和api来理解下
compileClasspath
和
runtimeClasspath
implementation
会添加依赖到编译路径,并且会将依赖打包输出到aar/apk,但编译时不会把依赖暴露给其他moudle
比如:A implementation B B implementation C
- 在B中,可以使用C中类库
- 在A中,不能使用C只给的类库,但是可以使用B中类库
-
这是因为
implementation
引入的依赖,会把C加入B的
compileClasspath
和
runtimeClasspath
,会把C加入A的
runtimeClasspath
- 因为C没有加入A的compileClasspath,所以A没有办法在编译时访问C中的类库,又是因为C加入A的runtimeClasspath,所以A可以在运行时访问C类库
api
会添加依赖到编译路径,并且把依赖打包输出到aar/apk,与implementation不同的是,这个依赖可以传递
例如:A implementation B B api C
- 在B中,可以使用C中类库
- 在A中,可以使用B中类库,也可以使用C中类库
-
这是因为api引入的依赖,会把C加入B的
compileClasspath
和
runtimeClasspath
,同时会把C加入A的
compileClasspath
和
runtimeClasspath
,所以A也可以在编译时访问C中类库
compileOnly
依赖只在编译时使用,不会打包到aar/apk运行时不能使用
例如:A implementation B B compileOnly C
- A访问不到C的代码,B可以访问C,且C不会打包到apk中
runtimeOnly
依赖编译时不能使用,只会打包到aar/apk运行时使用
例如:A implementation B B runtimeOnly C
- AB都不可以调用C中代码,但是C会打包到APK中
依赖远程库
其实每一个组件都有自己的
compileClasspath 和 runtimeClasspath
每一个组件参与编译时,Gradle就会将其放在compileClasspath中
每一个组件参与打包时,Gradle就会把他放入runtimeClasspath中
那如果依赖一个远程仓库,怎么判定放入
compileClasspath 还是 runtimeClasspath
中呢?
其实当maven上传组件时,不单单会上传二进制文件(如:aar),还会上传一个pom.xml文件,依赖信息就在这个文件中。
下面是一个pom文件,图片来自于
Gradle 与 Android 构建入门
,感谢各位大佬的输出
pom文件中会有俩个dependency,也会分为
runtime
和
compile
,runtime不会参与编译,会参与打包,compile会参与编译和打包
假设 A 依赖 B,B 依赖 C
BC 是远程仓库, B 的 POM 中对 C 的依赖是 runtime
在 Gradle 4.4 中,A 依然可以调用 C 的代码,这个问题在 Gradle 5.0 后被修复
依赖冲突
ABC都是本的Module,D是远程依赖,A依赖B,A依赖C,B依赖D 1.0版本,C依赖D 1.1版本,这个时候D就出现了依赖冲突,需要确定到底用D的1.0版本还是1.1版本
如何解决依赖冲突
- 在编译的时候,B编译时依赖的D的1.0版本,C编译时依赖D的1.1版本,但是最终打包到APK中的是D的1.1版本,这是因为版本号冲突,选择了最新版本
- Gradle也为我们提供了一系列的解决依赖冲突的方法如下:不允许依赖传递,exclude移除一个依赖,强制使用某个版本
强制使用某个依赖版本
isForce
isForce表示强制使用该版本的依赖
dependencies {
implementation('org.hibernate:hibernate:3.1') {
//在版本冲突的情况下优先使用3.1版本
isForce = true
}
}
strictly
strictly是一种强力版本约束,可以使用(!!)简写
dependencies {
implementation("io.reactivex.rxjava2:rxjava:2.2.0!!")
implementation("io.reactivex.rxjava2:rxjava") {
version {
strictly("2.2.0")
}
}
}
exclude移除一个依赖
Gradle中在implementation{…} 提供了了exclude设置,可以设置忽略指定的依赖,被忽略的依赖就被视为从来没有依赖,目前我测试的情况看来,这个只适用于远程依赖
dependencies {
implementation('org.hibernate:hibernate:3.1') {
//排除特定的依赖
exclude module: 'cglib' //by artifact name
exclude group: 'org.jmock' //by group
exclude group: 'org.unwanted', module: 'iAmBuggy' //by both name and group
}
}
我们可以根据group或者module,或者group和module一起作为排除的依据
下面就是group和module代表的意义
implementation("org.hibernate:hibernate:3.1")
group = org.hibernate
module = hibernate
version = 3.1
不允许依赖传递
dependencies {
implementation('org.hibernate:hibernate:3.1') {
//禁用依赖传递
// 传递依赖:A => B => C ,B 中使用到了 C 中的依赖,
// 且 A 依赖于 B,如果打开传递依赖,则 A 能使用到 B
// 中所使用的 C 中的依赖,
//默认都是打开,即 true
transitive = false
}
}
jar冲突
如果app引用一个aar,arr中有一个xx.jar,而app中又再次引用这个xx.jar,这个时候就需要让aar中引用jar的操作符用compileOnly代替implementation
参考