Gradle学习系列(四):Gradle依赖

8,438 阅读7分钟

概述

又开始了一个新的系列,这个系列学习Gradle,目标就是彻底理解Gradle,主要还是做下自己理解的笔记,防止忘记

Gradle系列(一):Groovy学习

Gradle学习系列(二):Gradle核心解密

Gradle学习系列(三):Gradle插件

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来理解下compileClasspathruntimeClasspath

implementation

会添加依赖到编译路径,并且会将依赖打包输出到aar/apk,但编译时不会把依赖暴露给其他moudle

比如:A implementation B B implementation C

  • 在B中,可以使用C中类库
  • 在A中,不能使用C只给的类库,但是可以使用B中类库
  • 这是因为implementation引入的依赖,会把C加入B的compileClasspathruntimeClasspath,会把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的compileClasspathruntimeClasspath,同时会把C加入A的compileClasspathruntimeClasspath,所以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 构建入门 ,感谢各位大佬的输出

image.png

pom文件中会有俩个dependency,也会分为runtimecompile,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

参考

Gradle 爬坑指南 -- 依赖管理

Gradle 与 Android 构建入门