依赖冲突问题由来:

  • 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
}

参考代码:

EasyDependencies


 评论