目录
前言:
一、依赖配置
二、依赖类型
三、依赖加@aar与不加@aar的区别:
四、排除依赖透传
五、查看依赖树:
六、强制指定依赖
七、定义解析策略
八、sourceSets应用:
九、声明主DEX中必需的类:
十、gradle下载慢解决方案:
十一、针对Manifest.xml合并编译指令:
十二、依赖插件的不同方式:
十三、发布aar包到maven仓库:
十四、Gradle迁移指南:从Groovy到KTS:
前言:
大型APP项目开发中总是会遇到各种Gradle构建编译问题等,这里根据以往的踩坑经验做个总结,以方便大家日后能够更高效率的处理Gradle相关依赖的问题。
一、依赖配置
目前 Gradle 版本支持的依赖配置有:
implementation、api、compileOnly、runtimeOnly 和 annotationProcessor
【支持以上依赖配置需:Android Studio 3.0及以上版本,Gradle Version在4.1及以上版本,Android Plugin Version在 3.0及以上版本。具体Gradle Version与Android Plugin Version依赖对应关系可查看:Android 相关工具插件版本经验总结】
已经废弃的配置有:compile、provided、apk、providedCompile。
各配置项具体作用如下[括号后面为对应的已废弃的配置]:
- implementation (
compile):会添加依赖到编译路径,并且会将依赖打包到输出(aar或apk),但是在编译时不会将依赖的实现暴露给其他module,也就是只有在运行时其他module才能访问这个依赖中的实现。使用这个配置,可以显著提升构建时间,因为它可以减少重新编译的module的数量。建议,尽量使用这个依赖配置。 - api (
compile):与 compile功能完全一样,会添加依赖到编译路径,并且会将依赖打包到输出(aar 或a pk)。与 implementation 不同,这个依赖可以传递,其他 module 无论在编译时和运行时都可以访问这个依赖的实现,也就是会泄漏一些不应该不使用的实现。举个例子,A 依赖 B,B 依赖 C,如果都是使用 api 配置的话,A 可以直接使用 C 中的类(编译时和运行时)。而如果是使用 implementation 配置的话,在编译时,A 无法访问 C 中的类。 - compileOnly (
provided):Gradle 把依赖加到编译路径,编译时使用,不会打包到输出(aar 或 apk)。这可以减少输出的体积,在只在编译时需要,在运行时可选的情况,很有用。 - runtimeOnly (
apk):只在生成apk
的时候参与打包,编译时不会参与,很少用。 - annotationProcessor (
compile):用于注解处理器的依赖配置。
二、依赖类型
依赖类型主要有如下三种:
apply plugin: 'com.android.application'
android { ... }
dependencies {
// 【1、本地源码依赖】Dependency on a local library module
implementation project(":mylibrary")
// 【2、本地libs目录jar包等依赖】Dependency on local binaries
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 【3、远程Maven仓库等依赖】Dependency on a remote binary
implementation 'com.example.android:app-magic:12.3'
}
三、依赖加@aar与不加@aar的区别:
library可以通过多种格式上传到远程仓库,比如大部分情况下用到的.jar或.aar。当没有指定@后缀的话,依赖的时候将会下载它的默认格式(由上传方定义,如果没有定义则默认为.jar)的Library库。如果指定了@后缀,那么默认看不到它下面的依赖树,为了保证所有的依赖树可以下载下来,即看到此模块下面的依赖树,那么需要这样来定义:
compile ('com.android.support:appcompat-v7:22.1.1@aar') {
transitive = true
}
而如果没有指定@后缀,即:
compile ('com.android.support:appcompat-v7:22.1.1')
那么所有的依赖树均可以下载下来。
四、排除依赖透传
1、dependencies中单个依赖排除:
compile('com.hongri.android:accs-huawei:1.1.2@aar') {
//默认值为false
transitive = true
exclude group: 'com.taobao.android', module: 'accs_sdk_taobao'
}
2、全局配置排除:
configurations {
compile.exclude module: 'cglib'
//全局排除原有的tnet jar包与so包分离的配置,统一使用aar包中的内容
all*.exclude group: 'com.taobao.android', module: 'tnet-jni'
all*.exclude group: 'com.taobao.android', module: 'tnet-so'
}
3、禁止依赖传递:
compile('com.hongri.android:foundation:1.0') {
transitive = false
}
configurations.all {
transitive = false
}
4、还可以在单个依赖项中使用 @jar 标识符忽略传递依赖:
compile 'com.hongri.android:foundation:1.0.0@jar'
五、查看依赖树:
在Terminal 终端输入以下命令,即可看到项目的依赖关系解析树:
./gradlew app:dependencies
我们可以在上面的命令中添加一个标识来查看特定构建变体的配置。例如:
./gradlew app:dependencies --configuration releaseCompileClasspath
这将向我们展示 release
变体的依赖树。
但如果你的项目中有配置productFlavors多渠道打包,那指令就要稍作变化了,如:
productFlavors {
dev {dimension "channel"} // 测试
baidu {dimension "channel"} // 百度手机助手
yingyongbao {dimension "channel"} // 应用宝
...
}
就需要使用如下指令:
./gradlew app:dependencies --configuration devReleaseCompileClasspath
./gradlew app:dependencies --configuration baiduReleaseCompileClasspath
./gradlew app:dependencies --configuration yingyongbaoReleaseCompileClasspath
来查看展示各渠道包的release变体的依赖树。
另外:
./gradlew app:dependencies --configuration compile //输出模块中compile相关的依赖
./gradlew app:dependencies --configutation compileOnly //输出模块中compileOnly相关的依赖
./gradlew app:dependencies --configutation androidTestCompile //输出模块中androidTestCompile 相关的依赖
releaseCompileClasspath - Resolved configuration for compilation for variant: release
+--- com.android.databinding:library:1.3.1
| +--- com.android.support:support-v4:21.0.3
| | \--- com.android.support:support-annotations:21.0.3 -> 27.0.2
| \--- com.android.databinding:baseLibrary:2.3.0-dev -> 3.0.1
+--- com.android.databinding:baseLibrary:3.0.1
+--- com.android.databinding:adapters:1.3.1
| +--- com.android.databinding:library:1.3 -> 1.3.1 (*)
| \--- com.android.databinding:baseLibrary:2.3.0-dev -> 3.0.1
+--- com.android.support.constraint:constraint-layout:1.0.2
| \--- com.android.support.constraint:constraint-layout-solver:1.0.2
\--- com.android.support:appcompat-v7:27.0.2
+--- com.android.support:support-annotations:27.0.2
+--- com.android.support:support-core-utils:27.0.2
| +--- com.android.support:support-annotations:27.0.2
| \--- com.android.support:support-compat:27.0.2
| +--- com.android.support:support-annotations:27.0.2
| \--- android.arch.lifecycle:runtime:1.0.3
| +--- android.arch.lifecycle:common:1.0.3
| \--- android.arch.core:common:1.0.0
+--- com.android.support:support-fragment:27.0.2
| +--- com.android.support:support-compat:27.0.2 (*)
| +--- com.android.support:support-core-ui:27.0.2
| | +--- com.android.support:support-annotations:27.0.2
| | \--- com.android.support:support-compat:27.0.2 (*)
| +--- com.android.support:support-core-utils:27.0.2 (*)
| \--- com.android.support:support-annotations:27.0.2
+--- com.android.support:support-vector-drawable:27.0.2
| +--- com.android.support:support-annotations:27.0.2
| \--- com.android.support:support-compat:27.0.2 (*)
\--- com.android.support:animated-vector-drawable:27.0.2
+--- com.android.support:support-vector-drawable:27.0.2 (*)
\--- com.android.support:support-core-ui:27.0.2 (*)
在查找目的之前,理解 Gradle 依赖关系树的格式很重要。
先来谈谈以下三个符号,它们的目的仅用于格式化:
-
+- - -
是依赖分支库的开始。 -
|
标识还是在之前的依赖库中的依赖,显示它依赖的库。 -
\- - -
是依赖库的末尾。
星号(*)
最重要的标识是 ->
:
如果 Gradle 发现多个依赖库都依赖到同一个库但是不同版本,那么它必须做出选择。毕竟包含同一个库的不同版本是没有意义的。在这种情况下,Gradle 默认选择该库的最新版本。例如:
| + — — com.android.support:support-v4:21.0.3
| | \ — — com.android.support:support-annotations:21.0.3 -> 27.0.2
六、强制指定依赖
- 在dependencies中配置之后呢,一般情况下高的版本会覆盖旧的低的版本:
implementation 'com.android.support:appcompat-v7:27.0.2'
- 如果我们想一直使用某个固定版本,那么可以使用如下的强制依赖实现:
compile('com.hongri.android:foundation:1.0.0') {
force = true
}
//或者进行全局配置
configurations.all {
resolutionStrategy {
force 'com.hongri.android:foundation:1.0.0'
//或这种写法
forcedModules = ['com.hongri.android:foundation:1.0.0']
}
}
七、定义解析策略
1、dependencySubstitution:
dependencySubstitution接收一系列替换规则,允许你通过substitute函数为项目中的依赖替换为你希望的依赖项:
// add dependency substitution rules
dependencySubstitution {
//将该module所有的远程依赖替换成源码依赖
substitute module('org.gradle:api') with project(':api')
//将该module所有源码依赖替换成远程依赖
substitute project(':util') with module('org.gradle:util:3.0')
}
2、eachDependency:
eachDependency允许你在gradle解析配置时为每个依赖项添加一个替换规则,DependencyResolveDetails类型的参数可以让你获取一个requested和使用useVersion()、useTarget()两个函数指定依赖版本和目标依赖。request中存放了依赖项的groupid、module name以及version,你可以通过这些值来筛选你想要替换的依赖项,再通过useVersion或useTarget指定你想要的依赖。
- 将group是com.android.support且name不等于multidex的所有module版本指定为28.0.2:
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
def requested = details.requested
if (requested.group == 'com.android.support') {
if (!requested.name.startsWith("multidex")) {
details.useVersion '28.0.2'
}
}
}
}
- 将所有module为rxjava的依赖,全都指定使用如下版本:‘io.reactivex.rxjava3:rxjava:3.0.0-RC1’:
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.name == 'rxjava') {
//由于useVersion只能指定版本号,不适用于group不同的情况
details.useTarget group: 'io.reactivex.rxjava3', name: 'rxjava', version: '3.0.0-RC1'
}
}
}
八、sourceSets应用:
Java插件引入了一个概念叫做SourceSets,通过修改SourceSets中的属性,可以指定哪些源文件(或文件夹下的源文件)要被编译,哪些源文件要被排除。Gradle就是通过它实现Java项目的布局定义。
SourceSets目录结构是固定的Java的标准项目目录布局:
android {
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
}
}
1、我们可以在sourceSets中配置指定so文件夹:
so文件的默认加载路径是在: src/main/jniLibs, 如果想自定义加载路径,如src/main/libs,那么可以设置:
sourceSets.main {
jni.srcDirs = []//disable automatic ndk-build call
//一般设置libs文件夹路径就行,但某些情况下需要设置全局路径才能找到so文件。所以这里设置的全局路径
jniLibs.srcDirs = ['src/main/libs']
}
2、我们可以在sourceSets中配置指定的Manifest文件:
sourceSets {
main {
if (isDebug.toBoolean()) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/release/AndroidManifest.xml'
}
}
}
3.如果我想要加一些我需要的java文件,但我们还不想按照java标准的文件夹结构:
sourceSets {
main {
java {
srcDir 'src/myJavaPath'
}
}
}
4.如果我想在打包的时候, 不包含某些文件, 可以如下进行设置:
sourceSets {
main {
java {
exclude '/test/**' // 不想包含文件的路径
}
resources {
exclude '/resource/**' // 不想包含的资源文件路径
}
.....
}
}
九、声明主DEX中必需的类:
在构建多DEX时, 编译工具会执行复杂的决策来确定主DEX文件中需要的类,以便能够成功启动。如果主DEX文件中没有提供启动时需要的任何类,就会奔溃出现java.lang.NoClassDefFoundError错误。对于代码依赖复杂或者自检机制,就可能不会将这些类识别为主DEX文件中必需类。需要使用multiDexKeepFile 或者 multiDexKeepProguard
1、multiDexKeepFile:
创建一个名为multidex-config.txt文件,在文件中添加需要放在主DEX的类,每行包含一个类,格式如下:
com/example/MyClass.class
com/example/MyOtherClass.class
然后,您可以按以下方式针对构建类型声明该文件:
android {
buildTypes {
release {
multiDexKeepFile file('multidex-config.txt')
}
}
}
注意:Gradle 会读取相对于 build.gradle 文件的路径,因此如果 multidex-config.txt 与 build.gradle 文件在同一目录中,以上示例将有效。
2、multiDexKeepProguard:
multiDexKeepProguard 文件使用与 Proguard 相同的格式,并且支持整个 Proguard 语法。在multiDexKeepProguard 中指定的文件应该在任何有效的 ProGuard 语法中包含 -keep 选项,创建multidex-config.pro文件如下:
-keep class com.example.MyClass
-keep class com.example.MyClassToo
#或者指定包中所有的类
-keep class com.example.** { *; } // All classes in the com.example package
然后,可以按以下方式针对构建类型声明该文件:
android {
buildTypes {
release {
multiDexKeepProguard('multidex-config.pro')
...
}
}
}
同样需要注意文件路径问题。
十、gradle下载慢解决方案:
Gradle下载慢最好的方案就是:切换成国内的Gradle服务器地址,且不要下载all的版本(它会附带源码和文档)而是下载带bin的版本。
1、打开gradle-wrapper.properties 文件;
2、将distributionUrl那行替换为:
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.0-bin.zip
注意:gradle默认缓存地址:$User/.gradle/wrapper/dists
十一、针对Manifest.xml合并编译指令:
./gradlew processDebugManifest --stacktrace
打包时候经常会出现这个错误:Manifest merger failed with multiple errors, see logs。我们只知道Manifest文件merge失败,但没有给出更详细的说明。为了解决这个问题,我们可以使用上面红色指令,快速的只对Manifest文件进行编译,并会详细给出merge失败的原因,可以快速的排查解决问题。
十二、依赖插件的不同方式:
- 旧的方式:
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-serialization:1.9.10"
}
}
apply plugin: "org.jetbrains.kotlin.plugin.serialization"
- 新方式【DSL】:
plugins {
id "org.jetbrains.kotlin.plugin.serialization" version "1.9.10"
}
以上是groovy脚本举例说明,kotlin脚本可参考。
十三、发布aar包到maven仓库:
Gradle发布aar包到maven仓库
十四、Gradle迁移指南:从Groovy到KTS:
【官网】将构建逻辑从 Groovy 迁移到 Kotlin
Gradle迁移指南:从Groovy到KTS
将构建配置从 Groovy 迁移到 KTS
十五、不使用签名如何打出可用的release包:
在不使用签名的情况下(或者项目没有提供签名),想打出release包验证,如何操作呢,如果直接打包安装时,可能会出现如下错误:
Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed to collect certificates from /data/app/vmdl1895883708.tmp/base.apk: Attempt to get length of null array]
此时需要再你主项目app的builld.gradle的buildTypes -- release 中添加该行即可:
signingConfig signingConfigs.debug
buildTypes {
release {
minifyEnabled true
shrinkResources true
zipAlignEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//不使用签名的情况下,可添加该行打出可用的release包
signingConfig signingConfigs.debug
}
debug {
minifyEnabled false
shrinkResources false
zipAlignEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
参考:
Android脚本 - 不一样的Gradle多渠道配置总结
Android Studio 手动下载Gradle配置的方法
Gradle 下载地址
Gradle 使用指南 -- 基础配置
Gradle官网 ResolutionStrategy
配置构建变体
Gradle 的依赖关系处理不当,可能导致你编译异常
Gradle核心(五):Gradle高级自定义