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

#.Gradle依赖配置

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

1.implementation

与Gradle2.0的compile对应,会添加依赖到编译路径,并且会将依赖打包到输出(aar或apk)。

但不同之处在于,编译时不会传递依赖包内部的依赖关系,即编译时只会访问依赖库的对外的公共接口,只有在运行时才能访问这个依赖库中的具体实现。举例,A依赖B,B依赖C,编译时A只能使用B中对外提供的类和接口,不能直接使用C中的类和接口。

使用这个配置,可以显著提升构建时间,因为B内部修改时,只要不修改B对外的接口,在打包编译时只需要重新编译B就行了,不需要重新编译依赖B的module。一般建议,配置依赖关系时尽可能使用implementation,而非api。

2.api

与Gradle2.0的compile对应,功能完全一样,会添加依赖到编译路径,并且会将依赖打包到输出(aar或apk).

与Gradle2.0的compile相同,编译时会传递依赖包内部的依赖关系,即编译时会访问依赖库的内部的具体实现,它在运行时也一样会访问这个依赖库中的具体实现。举例,A依赖B,B依赖C,如果使用api配置的话,A可以直接使用C中的公共类和接口(编译时和运行时),而如果是使用implementation配置的话,在编译时,A是无法访问C中的类的。

使用这个配置,因为内部的一切都是向上暴露的,代价就是,无论B还是C内部有修改,在项目打包时都需要重新编译打包A。这样需要重新编译构建的包就多了不少,如果整个项目中都是使用api来配置依赖关系,那么随便修改一点儿东西,在项目打包时所有依赖树上层的Module都需要重新编译打包一遍。

3.compileOnly

与Gradle2.0的provided对应,Gradle把依赖加到编译路径,编译时使用,不会打包到输出(aar或apk)。

这可以减少输出包的体积,在只有编译时需要,运行时可选的情况下,很有用。例如可通过网络动态下载所需依赖包时,依赖包事先可不必打包到输出包里,当判断需要用到相关模块时,可以再下载。

4.runtimeOnly

与apk对应,gradle添加依赖只打包到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)。

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

猜你喜欢

转载自blog.csdn.net/u013914309/article/details/124733064