Android Gradle依赖配置与依赖冲突解决

#.Gradle依赖配置

##.Gradle依赖管理与两个重要的classpath相关,每个Module都有:

1.编译时路径:compileClasspath

编译时能使用的代码,当一个类参与编译时,Gradle会将其放在compileClasspath中;
编译过程会将源代码编译为.class文件。

2.运行时路径:runtimeClasspath

运行时使用的代码,当一个类参与打包时,Gradle就会将其放在runtimeClasspath中;
运行时会将编译好的.class文件在JVM上运行。
     各种不同依赖方式的关键区别就是:依赖树上的直接依赖包、间接依赖包的代码,是否允许加入编译时路径、运行时路径,或者是二者都允许加入。

Gradle3.0以后常用的关键字有implementation、api、compileOnly

1.implementation

    与Gradle2.0的compile对应,会将直接依赖包同时添加到编译时路径、运行时路径,依赖包会被打入输出包中(aar或apk)。 差别在于对间接依赖包的处理上,使用implementation时直接依赖包不会向上层传递自己内部的依赖关系。   
    举例,A依赖B,B依赖C。
    对于A而言:B会同时加入A的编译时路径、运行时路径;但C不会加入A的编译时路径,只会加入A的运行时路径。
    因此,A在编译时只能访问B对外暴露的类和接口,不能访问C对外暴露的类和接口。因为B、C加入A的的运行时路径,所以对A打包时,B、C会打到A的输出包中。
    因为A不能直接访问C中的代码,所以修改C中代码时,只会影响直接依赖它的B,不会影响到更上层。所以重新对A打包时,只需要对C、B重新编译,不需要对A重新编译。
    当有很多Module相互依赖,全部使用implementation时,修改一个Module,只有直接依赖它的Module受影响,绝大部分Module不需要重新编译,会显著提升构建时间。 一般建议配置依赖关系时尽可能使用implementation,而非api。

2.api

    与Gradle2.0的compile对应,功能完全一样,会将直接依赖包和间接依赖包同时添加到编译时路径、运行时路径,并且会将依赖包依赖包会被打入输出包中(aar或apk)。 与 Gradle2.0的compile相同 , 编译时直接依赖包会向上层传递自己内部的依赖关系。
    举例,A依赖B,B依赖C。
    对于A而言:B、C会同时加入A的编译时路径、运行时路径。
    因此,A在编译时既能访问B对外暴露的类和接口,也能访问C对外暴露的类和接口。因为B、C加入A的的运行时路径,所以对A打包时,B、C会打到A的输出包中。
    A能直接访问C中的代码,在编写代码调用时是方便了一些,但代价是修改C中对外暴露的代码时,不仅B受影响,A也受影响。所以重新对A打包时,C、B、A都需要重新编译。
    当有很多Module相互依赖,全部使用api时,修改一个Module对外暴露的代码时,所有直接依赖它和间接依赖它的Module都受影响。如果修改的是依赖树较低层位置的Module,可能绝大部分Module后继都需要重新编译,构建时间会大大增加。 一般建议配置依赖关系时尽可能使用implementation,而非api。

3.compileOnly

    与Gradle2.0的provided对应,依赖包只添加到编译时路径,不会添加到运行时路径,因此编译时可用,但不会打包到输出包中(aar或apk)。
    这可以减少输出包的体积,在只有编译时需要,运行时可选的情况下,很有用。例如可通过网络动态下载所需依赖包时,依赖包事先可不必打包到输出包里,当判断需要用到相关模块时,可以再下载。

4.runtimeOnly

    与apk对应,依赖包不添加到编译时路径,只会添加到运行时路径,因此编译时不可用,但最终会打包到输出包中(aar或apk)。(没用过)

Gradle新老版本关键字对应一览表:

Gradle3.x+版本配置

已弃用配置(2.0及以前)

api

compile

implement

compile

compileOnly

provided

runtimeOnly

apk

testImplementation

testCompile

androidTestImplementation

androidTestCompile

debugImplementation

debugCompile

releaseImplementation

releaseCompile

#.Gradle依赖冲突的出现与解决

1.依赖冲突出现的原因:

    Android项目中有多个有相互依赖关系的Module,每个Module都依赖多个库(既可以是第三方库,也可以是Module打包成的aar等)。

    Gradle会按照配置的依赖关系按照树形结构来做依赖解析,当发现依赖树中出现了同一个库的不同版本(意味着同一个库的不同实现代码),就会出现依赖冲突。

    (默认情况下Gradle会尝试帮我们解决依赖冲突,解决的方式是使用最新的版本;这里的最新不是判断版本号大小,应该是根据发布时间来决定!没细究过其具体解决依赖冲突的方式。)

    例:考虑如下场景:A 模块依赖 B1 和 C, D 模块依赖了 B2,其中 B1 和 B1 是同一个Module B的两个不同版本;同时,工程中我们同时依赖了 A 和 D。这种情况下,会存在针对Module B的依赖冲突!

2.依赖冲突的解决

2.1最好的方式,当然是依赖库不出现版本冲突。

    可以自己写一个gradle脚本,例如上文中的utils.gralde,统一管理项目中用到的所有库的版本。

所有用到这些库的地方,统一都使用脚本中定义好的版本。

2.2通过transitive、exclude等关键字手动配置,解决冲突

     1)transitive关键字:

            对某个依赖库设置transitive关键字后,Gradle在解析依赖树时不会对该库内部的依赖关系做解析。

    2)exclude关键字:    

            exclude关键字可以解除对依赖库内部的指定部分库的依赖解析。

    3)force关键字:    

            force关键字可以强制设置指定Module依赖某个库。

示例,针对上面的依赖冲突示例,避免B1与B2的冲突:

(1) 方案一:针对 A 或 D 配置 transitive。

    这里针对A配置,不解析A模块的传递依赖,因此当前Module的依赖关系树中不再包含 B1 和 C,这里需要手动添加依赖 C

dependencies {

    implementation A {

      transitive = false

    }

    implementation C

    implementation D {

      //transitive = false

    }

}

(2) 方案二:针对 A 或 D 配置 exclude规则,此处针对A配置,依赖关系树中不再包含B1

dependencies {

    implementation A {

      exclude  B1

    }

    implementation D {

      //exclude  B2

    }

}

(3) 方案三:使用force强制依赖某个版本,如强制依赖 B1 或者 B2

        以下是在顶层build.gradle中配置,强制所有module依赖B1

configurations.all {

    resolutionStrategy {

       force B1

       // force B2

    }

}

##.个人推荐的依赖配置方式

    可以自己写一个gradle脚本,例如上文中的utils.gralde,统一管理项目中用到的所有第三方库的版本。

所有用到第三方库的地方,统一都使用脚本中定义好的版本。

    至于自己写的Module,使用implementation方式,只对它直接用到的Module设置依赖关系(不要设置transitive=false,否则要把这些直接依赖包内部的包也统统设置到当前Module)。

    这些可以尽可能少的配置依赖关系,而且只要直接依赖包的对外类、接口不变,无论依赖树上子孙节点内部如何修改,都不用重新编译当前包。修改代码时,需要重新编译的包较少,提高编译效率。


版权声明:本文为u013914309原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。