依赖冲突问题由来:
- Android开发中,我们使用第三方依赖库实在太正常不过了。
- 最基本的一些三方库,比如图片缓存的Glide、网络请求的Retrofix、流式API的RxJava等。
- 但是往往工程项目增大之后,我们还涉及到组件化的问题。当我们把Module拆分的足够精细的时候。三方依赖的问题就会显得十分突出。
- 因为往往同一个依赖库,可能在不同的Module之前被使用,而依赖库一旦版本不一致,就会出现编译错误的情况。
- 这种情况,统称为:依赖冲突。
工程依赖关系查看:
- 往往在依赖关系冲突问题中,有很多情况我们是摸不着头脑的。因为Module越多,Module间的依赖就越错综复杂。
- 一旦出现依赖关系冲突,就需要使用下面的方式来查看当前项目的依赖关系:
./gradlew Module名称:dependencies > 指定目录/dependencies.txt
- 上述命令,能够罗列出项目中Module的依赖关系,方便我们进行排查问题。
- 依赖关系会根据Debug、Beta、Release集中Build Variants进行输出,大致会如下:
+--- androidx.test.ext:junit:1.1.1
| +--- junit:junit:4.12
| | \--- org.hamcrest:hamcrest-core:1.3
| +--- androidx.test:core:1.2.0
| | +--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | +--- androidx.test:monitor:1.2.0
| | | \--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | \--- androidx.lifecycle:lifecycle-common:2.0.0 -> 2.1.0
| | \--- androidx.annotation:annotation:1.1.0
| +--- androidx.test:monitor:1.2.0 (*)
| \--- androidx.annotation:annotation:1.0.0 -> 1.1.0
+--- androidx.test.espresso:espresso-core:3.2.0
| +--- androidx.test:runner:1.2.0
| | +--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | +--- androidx.test:monitor:1.2.0 (*)
| | +--- junit:junit:4.12 (*)
| | \--- net.sf.kxml:kxml2:2.3.0
| +--- androidx.test.espresso:espresso-idling-resource:3.2.0
| +--- com.squareup:javawriter:2.1.1
| +--- javax.inject:javax.inject:1
| +--- org.hamcrest:hamcrest-library:1.3
| | \--- org.hamcrest:hamcrest-core:1.3
| +--- org.hamcrest:hamcrest-integration:1.3
| | \--- org.hamcrest:hamcrest-library:1.3 (*)
| \--- com.google.code.findbugs:jsr305:2.0.1
+--- androidx.test.ext:junit:{strictly 1.1.1} -> 1.1.1 (c)
+--- androidx.test.espresso:espresso-core:{strictly 3.2.0} -> 3.2.0 (c)
+--- androidx.appcompat:appcompat:1.1.0
| +--- androidx.annotation:annotation:1.1.0
| +--- androidx.core:core:1.1.0
| | +--- androidx.annotation:annotation:1.1.0
| | +--- androidx.lifecycle:lifecycle-runtime:2.0.0 -> 2.1.0
| | | +--- androidx.lifecycle:lifecycle-common:2.1.0 (*)
| | | +--- androidx.arch.core:core-common:2.1.0
| | | | \--- androidx.annotation:annotation:1.1.0
| | | \--- androidx.annotation:annotation:1.1.0
| | +--- androidx.versionedparcelable:versionedparcelable:1.1.0
| | | +--- androidx.annotation:annotation:1.1.0
| | | \--- androidx.collection:collection:1.0.0 -> 1.1.0
| | | \--- androidx.annotation:annotation:1.1.0
| | \--- androidx.collection:collection:1.0.0 -> 1.1.0 (*)
| +--- androidx.cursoradapter:cursoradapter:1.0.0
| | \--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| +--- androidx.fragment:fragment:1.1.0
| | +--- androidx.annotation:annotation:1.1.0
| | +--- androidx.core:core:1.1.0 (*)
| | +--- androidx.collection:collection:1.1.0 (*)
| | +--- androidx.viewpager:viewpager:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | | +--- androidx.core:core:1.0.0 -> 1.1.0 (*)
| | | \--- androidx.customview:customview:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | | \--- androidx.core:core:1.0.0 -> 1.1.0 (*)
| | +--- androidx.loader:loader:1.0.0
| | | +--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | | +--- androidx.core:core:1.0.0 -> 1.1.0 (*)
| | | +--- androidx.lifecycle:lifecycle-livedata:2.0.0
| | | | +--- androidx.arch.core:core-runtime:2.0.0
| | | | | +--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | | | | \--- androidx.arch.core:core-common:2.0.0 -> 2.1.0 (*)
| | | | +--- androidx.lifecycle:lifecycle-livedata-core:2.0.0
| | | | | +--- androidx.lifecycle:lifecycle-common:2.0.0 -> 2.1.0 (*)
| | | | | +--- androidx.arch.core:core-common:2.0.0 -> 2.1.0 (*)
| | | | | \--- androidx.arch.core:core-runtime:2.0.0 (*)
| | | | \--- androidx.arch.core:core-common:2.0.0 -> 2.1.0 (*)
| | | \--- androidx.lifecycle:lifecycle-viewmodel:2.0.0 -> 2.1.0
| | | \--- androidx.annotation:annotation:1.1.0
| | +--- androidx.activity:activity:1.0.0
| | | +--- androidx.annotation:annotation:1.1.0
| | | +--- androidx.core:core:1.1.0 (*)
| | | +--- androidx.lifecycle:lifecycle-runtime:2.1.0 (*)
| | | +--- androidx.lifecycle:lifecycle-viewmodel:2.1.0 (*)
| | | \--- androidx.savedstate:savedstate:1.0.0
| | | +--- androidx.annotation:annotation:1.1.0
| | | +--- androidx.arch.core:core-common:2.0.1 -> 2.1.0 (*)
| | | \--- androidx.lifecycle:lifecycle-common:2.0.0 -> 2.1.0 (*)
| | \--- androidx.lifecycle:lifecycle-viewmodel:2.0.0 -> 2.1.0 (*)
| +--- androidx.appcompat:appcompat-resources:1.1.0
| | +--- androidx.annotation:annotation:1.1.0
| | +--- androidx.core:core:1.0.1 -> 1.1.0 (*)
| | +--- androidx.vectordrawable:vectordrawable:1.1.0
| | | +--- androidx.annotation:annotation:1.1.0
| | | +--- androidx.core:core:1.1.0 (*)
| | | \--- androidx.collection:collection:1.1.0 (*)
| | +--- androidx.vectordrawable:vectordrawable-animated:1.1.0
| | | +--- androidx.vectordrawable:vectordrawable:1.1.0 (*)
| | | +--- androidx.interpolator:interpolator:1.0.0
| | | | \--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | | \--- androidx.collection:collection:1.1.0 (*)
| | \--- androidx.collection:collection:1.0.0 -> 1.1.0 (*)
| +--- androidx.drawerlayout:drawerlayout:1.0.0
| | +--- androidx.annotation:annotation:1.0.0 -> 1.1.0
| | +--- androidx.core:core:1.0.0 -> 1.1.0 (*)
| | \--- androidx.customview:customview:1.0.0 (*)
| \--- androidx.collection:collection:1.0.0 -> 1.1.0 (*)
+--- androidx.constraintlayout:constraintlayout:1.1.3
| \--- androidx.constraintlayout:constraintlayout-solver:1.1.3
+--- androidx.appcompat:appcompat:{strictly 1.1.0} -> 1.1.0 (c)
+--- androidx.constraintlayout:constraintlayout:{strictly 1.1.3} -> 1.1.3 (c)
+--- junit:junit:{strictly 4.12} -> 4.12 (c)
+--- androidx.test:core:{strictly 1.2.0} -> 1.2.0 (c)
+--- androidx.test:monitor:{strictly 1.2.0} -> 1.2.0 (c)
+--- androidx.annotation:annotation:{strictly 1.1.0} -> 1.1.0 (c)
+--- androidx.test:runner:{strictly 1.2.0} -> 1.2.0 (c)
+--- androidx.test.espresso:espresso-idling-resource:{strictly 3.2.0} -> 3.2.0 (c)
+--- com.squareup:javawriter:{strictly 2.1.1} -> 2.1.1 (c)
+--- javax.inject:javax.inject:{strictly 1} -> 1 (c)
+--- org.hamcrest:hamcrest-library:{strictly 1.3} -> 1.3 (c)
+--- org.hamcrest:hamcrest-integration:{strictly 1.3} -> 1.3 (c)
+--- com.google.code.findbugs:jsr305:{strictly 2.0.1} -> 2.0.1 (c)
+--- androidx.core:core:{strictly 1.1.0} -> 1.1.0 (c)
+--- androidx.cursoradapter:cursoradapter:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.fragment:fragment:{strictly 1.1.0} -> 1.1.0 (c)
+--- androidx.appcompat:appcompat-resources:{strictly 1.1.0} -> 1.1.0 (c)
+--- androidx.drawerlayout:drawerlayout:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.collection:collection:{strictly 1.1.0} -> 1.1.0 (c)
+--- androidx.constraintlayout:constraintlayout-solver:{strictly 1.1.3} -> 1.1.3 (c)
+--- org.hamcrest:hamcrest-core:{strictly 1.3} -> 1.3 (c)
+--- androidx.lifecycle:lifecycle-common:{strictly 2.1.0} -> 2.1.0 (c)
+--- net.sf.kxml:kxml2:{strictly 2.3.0} -> 2.3.0 (c)
+--- androidx.lifecycle:lifecycle-runtime:{strictly 2.1.0} -> 2.1.0 (c)
+--- androidx.versionedparcelable:versionedparcelable:{strictly 1.1.0} -> 1.1.0 (c)
+--- androidx.viewpager:viewpager:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.loader:loader:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.activity:activity:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.lifecycle:lifecycle-viewmodel:{strictly 2.1.0} -> 2.1.0 (c)
+--- androidx.vectordrawable:vectordrawable:{strictly 1.1.0} -> 1.1.0 (c)
+--- androidx.vectordrawable:vectordrawable-animated:{strictly 1.1.0} -> 1.1.0 (c)
+--- androidx.customview:customview:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.arch.core:core-common:{strictly 2.1.0} -> 2.1.0 (c)
+--- androidx.lifecycle:lifecycle-livedata:{strictly 2.0.0} -> 2.0.0 (c)
+--- androidx.savedstate:savedstate:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.interpolator:interpolator:{strictly 1.0.0} -> 1.0.0 (c)
+--- androidx.arch.core:core-runtime:{strictly 2.0.0} -> 2.0.0 (c)
\--- androidx.lifecycle:lifecycle-livedata-core:{strictly 2.0.0} -> 2.0.0 (c)
- 如上面的代码,详细罗列了每一个依赖库的具体版本
- 他们中末尾的版本号就是最终使用的依赖库版本。
统一
- 依赖关系的统一,可以使用下列方案,针对整个工程的依赖关系进行统一的管理。
仓库统一
- 在Android工程根目录的build.gradle文件中,我们通常是配置了google()、jcenter()等等这些maven仓库。
- 大部分的依赖库都存放在这些仓库里。当我们新增maven仓库的时候,都是在这里去实现的。
- 依赖关系既然要统一,那么最好代码仓库也能够统一管理起来。
统一目录
- 在根目录下,新建dependency目录:
- 随后新建一个名为repository.gradle的文件,统一作为仓库管理配置文件
- 代码如下:
def configureRepo(RepositoryHandler handler) {
println 'gg'
handler.maven {
url 'http://maven.aliyun.com/nexus/content/repositories/jcenter'
}
handler.maven {
url 'http://maven.aliyun.com/nexus/content/groups/public'
}
handler.mavenLocal()
handler.mavenCentral()
handler.google()
handler.jcenter()
handler.maven {
url "https://jitpack.io"
}
handler.flatDir {
dirs 'libs'
}
}
ext.configureRepo = this.&configureRepo
- RepositoryHandler是Gradle中对应的代码仓库管理的实现类。
- 调用handler对象的maven,就能声明一个代码仓库。
- 上述代码中,我们统一声明了开发中比较常用的代码仓库。同时优先使用阿里云镜像的jcenter等仓库
- 目前的网络环境,处处镜像,你们懂的。
替换仓库配置
- 仓库配置好需要替换目前的仓库管理项。
- 工程根目录下的build.gradle,修改成如下配置:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
apply from: "./dependency/repository.gradle"
configureRepo(repositories)
dependencies {
classpath 'com.android.tools.build:gradle:3.6.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
configureRepo(repositories)
}
task clean(type: Delete) {
delete rootProject.buildDir
}
- Sync一遍工程代码,整个工程的仓库依赖就统一了。后续如果需要增删仓库,只需要操作dependency目录下的factory.gradle即可。
classpath统一(这一步看喜好,其实可以不用做)
- 有时候我们新增一个依赖库,是需要增加作为plugin使用的,也就是通常我们在build.gradle中apply使用的插件
- 这时候是需要在根目录的build.gradle配置相应的classpath。
- 这个地方的改动也不太多,所以我们可以单拿出来一个文件对其进行管理。
- 同样还是在dependency目录中,新建classpath.gradle文件
ext {
classpath = [:]
}
ext.classpath.android_gradle = 'com.android.tools.build:gradle:3.6.2'
- 随后在根目录的build.gradle我们新增如下代码
buildscript {
apply from: "./dependency/classpath.gradle"//引用classpath.gradle,配置classpath全局变量
apply from: "./dependency/repository.gradle"
configureRepo(repositories)
dependencies {
//classpath 'com.android.tools.build:gradle:3.6.2' 修改classpath的引用方式
classpath rootProject.classpath.android_gradle
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
- 如此一来,classpath也纳入到了dependency目录中来管理,以后有任何依赖修改都比较统一位置。
- 这一步修改,其实并没有什么很意义程度上的优化,所以大家按照自己喜好来做就好。
dependencies统一
- 接下来是依赖关系的整合。
- 笔者自己写了一个gradle插件专门用来模式化管理工程的dependencies,下面来看下怎么引用。
声明
- 首先在classpath.gradle中声明插件
ext.classpath.easy_dep = 'com.ryanhuen.easy_dependencies:easydependencies:1.0.3'
添加依赖
- 插件已经上传jcenter,直接引用就好
- 随后在根目录build.gradle添加依赖:
buildscript {
apply from: "./dependency/classpath.gradle"
apply from: "./dependency/repository.gradle"
configureRepo(repositories)
dependencies {
classpath rootProject.classpath.android_gradle
classpath rootProject.classpath.easy_dep
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
使用
- dependency目录创建dependencies.gradle文件
- sync工程完毕后,在根目录build.gradle引用该插件,在build.gradle文件中头部插入以下代码
apply plugin: 'easy_dependencies' apply from: "./dependency/dependencies.gradle"
配置dependencies
- 此时插件已经引用上,接下来就是配置dependencies.gradle中的依赖了
easyDep {
compileSdkVersion 29
buildToolsVersion "29.0.2"
minSdkVersion 23
targetSdkVersion 29
groups {
androidx {
nodes {
appcompat {
alias 'androidx.appcompat:appcompat'
version '1.1.0'
}
constraintlayout {
alias 'androidx.constraintlayout:constraintlayout'
version '1.1.3'
}
}
}
unit_test {
nodes {
junit {
alias 'junit:junit'
version '4.12'
}
ext_junit {
alias 'androidx.test.ext:junit'
version '1.1.1'
}
espresso_core {
alias 'androidx.test.espresso:espresso-core'
version '3.2.0'
}
}
}
}
}
- 直接看代码,我在插件中声明了Module中常用的compileSdkVersion等版本,各位可以在此声明一次,全局进行调用。
- 同时,dependencies的声明,我拆分了alias和version,用于区分依赖声明和版本。因为version是经常被改动的,所以单独拆出来方便修改。
- 每一个dependencies的声明,就是一个node节点。这些节点自己可以作为一个group。比如androidx相关的依赖,就可以作为一个group进行管理。
引用
- 声明完毕就是引用了。
- Module中使用如下:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation easyDep.groups.androidx.nodes.appcompat.dep
implementation easyDep.groups.androidx.nodes.constraintlayout.dep
testImplementation easyDep.groups.unit_test.nodes.junit.dep
androidTestImplementation easyDep.groups.unit_test.nodes.ext_junit.dep
androidTestImplementation easyDep.groups.unit_test.nodes.espresso_core.dep
}