Android—Gradle教程(三)

前言

在上一篇中,主要讲解了Gradle核心模型以及初步了解Gradle插件。在这一篇中,主要对Gradle依赖管理进行详解:

1、依赖管理

所谓的依赖管理:在大多数情况下,项目都要依赖lib形式的可重用功能,还有很多项目可能被切分成多个单独的子工程来构成模块系统。依赖管理是一种可以让项目自动化的定义、解析,及使用依赖的技术。

而Gradle提供了强大的依赖管理支持,并且也实现了现代软件项目的各种典型场景,如下图所示:

在这里插入图片描述
如图所示

Gradle在编译构建的时候,先是从对应(本地、远程)资源库里面拿到对应的资源,所以这里就要对Gradle资源库进行详解:

2、Gradle资源库

在Gradle中存储模块的地方就叫做资源库:

  • 定义了资源库之后,Gradle就知道怎么样查找和检索模块。资源库有两种方式:本地库和远程库
  • 在运行时,如果相应的任务需要,那么Gradle就需要定位依赖的声明,依赖可能需要从远程库下载,也可能从本地库检索或者是在多项目构建中的其他项目。这个过程就叫做依赖解析
  • 一旦解析,解析机制就会存储依赖的底层文件作为本地缓存,之后的构建会重用这些文件,而不用再到远程库下载
  • 模块还能提供一些其他的元数据,元数据是描述模块更详细信息的数据,比如在资源库中的坐标,项目的信息等。(group, name, version)

刚刚说到,资源的下载是根据开发者对应Gradle定位依赖声明而下载的,那么Gradle依赖添加有哪些方式呢?

3、Gradle依赖仓库

添加依赖仓库:

  • 在项目build.gradle中使用allprojects{}对所有的工程进行配置,使用repositories{}添加依赖仓库;
  • google():添加一个在 Google 的 Maven 存储库中查找依赖项的存储库;
  • mavenCenteral():添加一个在 Maven 中央存储库中查找依赖项的存储库;
  • jcenter()添加一个在 Bintray 的 JCenter 存储库中查找依赖项的存储库;
    • 注意jecnter即将关闭,改用mavenCentral。如果有自己发布在jcenter的依赖库,需要迁移到mavenCentral。AS高版本中也默认的将jcenter()改成了mavenCenteral()。
  • mavenLocal():添加一个在本地 Maven 缓存中查找依赖项的存储库;
  • maven{}:指定某个maven仓库的地址,使用url(path)方法来添加;
  • ivy{}:指定某个ivy仓库地址,使用url(path)方法来添加。

说了那么多,贴个代码看看:

// 对所有的工程进行配置
allprojects {
    
    
    // 为工程添加依赖仓库
    repositories {
    
    
        google() // google maven仓库
        mavenCentral() // maven 中央仓库
        //jcenter() // Warning: this repository is going to shut down soon
        mavenLocal() // 本地maven仓库
//        maven {
    
    
//            //url "http://xxxx" // 从xxx去获取library
//        }
//        ivy {
    
    
//            //url "http://xxxx" // 从xxx去获取library
//        }
    }
}

相信大多数读者对这段代码熟悉都不能在熟悉了,也不过多叙述,直接进入下个专题:Gradle依赖范围

4、Gradle依赖范围

  • 在Gradle构建脚本中开发者可以把依赖定义到不同的范围中,比如编译源码或者执行测试。在Gradle中依赖的范围叫依赖项配置
  • Gradle插件内置了几种方式的依赖项配置:
    • implementation、api、compileOnly、runtimeOnly、annotationProcessor、lintChecks、lintPublish …
    • apk/compile/provided已被废弃。
  • 在build.gradle中使用dependencies{}添加依赖项配置。

4.1 Gradle不同依赖方式的作用

这里太多了,我就直接截图官方的了:感兴趣的可以去看看官方文档

在这里插入图片描述

这些都是官方原生依赖配置,当然你也可以通过自定义依赖:

4.2 Gradle自定义依赖配置项

app下bulid.gradle

...略
configurations {
    
    
    abc {
    
    
    	//可在这实现依赖前后的逻辑
        println "abc"
    }
}

// 依赖项配置
dependencies {
    
    
    abc "org.jetbrains.kotlin:kotlin-stdlib:1.5.10"
    ...}

运行效果

Starting Gradle Daemon...
Gradle Daemon started in 2 s 539 ms

> Configure project :app
abc

BUILD SUCCESSFUL in 34m 9s

这个很少用,除特殊情况外几乎用不到,直接简单过一下。

直接到本篇重点,依赖传递:

5、Gradle依赖传递

依赖传递:就是通过不同的依赖方式有着不一样的传递关系。那么怎么来查看对应的依赖项?

5.1 查看模块依赖项

要想查看整个项目的依赖传递关系,使用命令:

  • gradlew app:dependencies --configuration releaseRuntimeClasspath
  • x.x.x (*) 该依赖已经有了,将不再重复依赖
  • x.x.x -> x.x.x 该依赖的版本被箭头所指的版本代替
  • x.x.x -> x.x.x(*) 该依赖的版本被箭头所指的版本代替,并且该依赖已经有了,不再重复依赖

现在来撸码验证下:

dependencies {
    
    

    abc "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    //implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    
    implementation 'androidx.core:core-ktx:1.3.1'
    //这种方式和下面那种相同,androidx.core:core-ktx:1.3.1 分为 
    // group: 'androidx.core', name: 'core-ktx', version: '1.3.1'
    implementation group: 'androidx.core', name: 'core-ktx', version: '1.3.1'
    
    implementation'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.appcompat:appcompat:1.+'

...}

注意这里,我特意将 androidx.appcompat:appcompat依赖了不同版本,其中1.+表示最新,执行以下上面那个命令看看效果:


+--- androidx.appcompat:appcompat:1.2.0 -> 1.4.0-rc01
|    +--- androidx.annotation:annotation:1.3.0-rc01
|    +--- androidx.core:core:1.7.0 (*)
|    +--- androidx.cursoradapter:cursoradapter:1.0.0
|    ....|	 ....|         \--- androidx.annotation:annotation:1.1.0 -> 1.3.0-rc01
+--- androidx.appcompat:appcompat:1.1.0 -> 1.4.0-rc01 (*)
+--- androidx.appcompat:appcompat:1.+ -> 1.4.0-rc01 (*)

从这个运行效果可知:

  • Gradle依赖管理进行了优化
    • 如果项目存在同一个依赖库的多个版本,默认选择最高版本
    • Gradle会自动排除重复的依赖
    • Gradle默认支持依赖传递

既然Gradle依赖给我们做了优化,那为什么我们还是会遇到依赖冲突的问题?

  • 当引入一个第三方库,该库中也依赖了Android支持库,且支持库的版本和当前版本不一致。这种情况下,Gradle会自动选择最高的版本,导致不兼容的问题。
  • 如果同时依赖一个group下的全量库,又依赖了这个group其中一个分库,如果这两个库版本不统一,就导致了依赖冲突问题。最典型的就是support-v4的24.2.0版本开始的分库导致的问题
  • androidx包和support包的冲突问题

这个问题在之前经常出现,当你依赖A,A又依赖B,此时你依赖了C,而C没有依赖A而是直接依赖了B,当双方的B版本不同时,就会出现依赖冲突问题。

笔者用了以前最经典的support-v7、support-v4包,来举例,可惜的冲突问题复现不出来了,一看依赖树,gradle已经帮我们全转成androidx了。

+--- com.android.support:appcompat-v7:20.0.0 -> androidx.appcompat:appcompat:1.2.0 (*)
+--- com.android.support.constraint:constraint-layout:1.1.3 -> androidx.constraintlayout:constraintlayout:2.0.1 (*)
+--- com.android.support:support-v4:23.0.0 -> androidx.legacy:legacy-support-v4:1.0.0

虽然系统资源包gradle已经帮我们解决好了,但为了防止以后因为非系统资源包而出现的依赖冲突问题,仔细斟酌了下,还是将解决方案说一下,毕竟多会一点也不是坏事,还是要做到未雨绸缪。

5.2 解决依赖冲突的方式

5.2.1 排除传递依赖项

就以implementation ("androidx.room:room-runtime:2.3.0") 这个举例,我们通过依赖树看看这个依赖子下有哪些:

+--- androidx.room:room-runtime:2.3.0
|    +--- androidx.room:room-common:2.3.0
|    |    \--- androidx.annotation:annotation:1.1.0 -> 1.3.0-rc01
|    +--- androidx.sqlite:sqlite-framework:2.1.0
|    |    +--- androidx.annotation:annotation:1.0.0 -> 1.3.0-rc01
|    |    \--- androidx.sqlite:sqlite:2.1.0
|    |         \--- androidx.annotation:annotation:1.0.0 -> 1.3.0-rc01
|    +--- androidx.sqlite:sqlite:2.1.0 (*)
|    +--- androidx.arch.core:core-runtime:2.0.1 -> 2.1.0 (*)
|    \--- androidx.annotation:annotation-experimental:1.1.0

我们可以看到androidx.room:room-runtime 这个依赖树里面结构为:

  • androidx.room:room-common:2.3.0
    • androidx.annotation:annotation:1.1.0
  • androidx.sqlite:sqlite-framework:2.1.0
    • androidx.annotation:annotation:1.0.0
    • androidx.sqlite:sqlite:2.1.0
      • androidx.annotation:annotation:1.0.0
  • androidx.sqlite:sqlite:2.1.0 (*)
  • androidx.arch.core:core-runtime:2.0.1

通过这个结构能清楚的认识到,每一个依赖具体依赖了哪些子依赖,子依赖的下一层又依赖了哪些子依赖,甚至还会有多重子依赖。

这里我们看到不同的子依赖依赖了不同版本的annotation:annotation依赖,我们先假设annotation:annotation依赖并没有转成1.3.0-rc01,那么编译肯定会出现依赖冲突的问题,那么可以如下方式解决该问题。

    implementation ("androidx.room:room-runtime:2.3.0"){
    
    
        exclude group:'androidx.annotation', module: 'annotation'
    }
//    implementation group: 'androidx.annotation', name: 'annotation', version: '1.0.0' 等同于
    implementation("androidx.annotation:annotation:1.0.0")

在Groovy代码里面,额外创建了一个闭包,在闭包里面通过exclude关键字指定了要删除room-runtime里面对应的annotation依赖。随后在外部将annotation进行单独依赖,再次看看依赖树:

+--- androidx.room:room-runtime:2.3.0
|    +--- androidx.room:room-common:2.3.0
|    +--- androidx.sqlite:sqlite-framework:2.1.0
|    |    \--- androidx.sqlite:sqlite:2.1.0
|    +--- androidx.sqlite:sqlite:2.1.0
|    +--- androidx.arch.core:core-runtime:2.0.1 -> 2.1.0 (*)
|    \--- androidx.annotation:annotation-experimental:1.1.0
+--- androidx.annotation:annotation:1.0.0 -> 1.3.0-rc01

我们发现annotation:annotation有且仅有一个了。

那么如果出现多个依赖库都依赖了annotation:annotation 并且版本都不一样该怎么办?

5.2.2 Froce强制指定

// 强制指定版本
configurations.all {
    
    
    resolutionStrategy {
    
    
        force 'androidx.annotation:annotation:1.0.0'
    }
}
// 依赖项配置
dependencies {
    
    
...略
    implementation ("androidx.room:room-runtime:2.3.0")//{
    
    
//        exclude group:'androidx.annotation', module: 'annotation'
//    }
//    implementation group: 'androidx.annotation', name: 'annotation', version: '1.0.0' 等同于
//    implementation("androidx.annotation:annotation:1.0.0")
}

这里我们看到,在dependencies 使用了 force 强制将annotation:annotation 指定为 1.0.0,现在来看看效果:

+--- androidx.room:room-runtime:2.3.0
|    +--- androidx.room:room-common:2.3.0
|    |    \--- androidx.annotation:annotation:1.1.0 -> 1.0.0
|    +--- androidx.sqlite:sqlite-framework:2.1.0
|    |    +--- androidx.annotation:annotation:1.0.0
|    |    \--- androidx.sqlite:sqlite:2.1.0
|    |         \--- androidx.annotation:annotation:1.0.0
|    +--- androidx.sqlite:sqlite:2.1.0 (*)
|    +--- androidx.arch.core:core-runtime:2.0.1 -> 2.1.0 (*)
|    \--- androidx.annotation:annotation-experimental:1.1.0

我们通过这句代码annotation:annotation:1.1.0 -> 1.0.0 可以看到就算依赖了高版本的 1.1.0 也会被强制变成为 1.0.0。

6、总结

这一篇文字很多,但是内容比较少,相信读者看完本篇后还是会有所收获。

猜你喜欢

转载自blog.csdn.net/qq_30382601/article/details/121078993