现在的Android工程都是采用 gradle 来构建的,从早期的单一工程架构(一个项目只有一个主 module),到现在的组件化架构(一个项目包含有多个module),项目结构越来越复杂,在这里陆续记录一些挺有用的小技巧。
1. resolutionStrategy 统一全局第三方库版本
resolutionStrategy 顾名思义“解析策略”,也就是说可以在 gradle 解析各种依赖库时,配置一些解析策略。
1.1 resolutionStrategy.force
我们在开发的过程当中,会依赖使用各种第三方库,比如说最常见的 appcompat-v7
库,不仅我们自己的代码里会依赖该库,而且有可能我们依赖的第三方库也会依赖该库,但是不同的地方依赖该库的版本号都有可能不一致,这样在编译的时候,整个项目当中会出现各种不同版本号的 appcompat-v7
库,编译的时候可能会报错。我们不可能修改第三方库里对 appcompat-v7
库依赖的版本号,那么我们可以通过 resolutionStrategy.force
来强制编译时统一库的版本号。
android {
configurations.all {
resolutionStrategy {
force 'com.android.support:appcompat-v7:28.0.0'
force 'com.android.support:support-v4:28.0.0'
}
}
}
1.2 resolutionStrategy.dependencySubstitution
还有一种这样的情形,假设一个第三方工具库叫 org.gradle:util:3.0
,除了我们自己的项目以及部分第三方库也有依赖它,但是在实际使用的过程当中,发现该库有些功能不满足或者有bug,那这个时候该怎么办呢?一是提 issue 让该库的作者去修改,但是时间上来不及;二是源码拿过来本地进行修改,直接本地集成,但是要修改依赖方式,那么这个时候可以这样做:
假设我们本地 module 名称叫 :util
android {
configurations.all {
resolutionStrategy {
//远程依赖替换成本地依赖
substitute module('org.gradle:util:3.0') with project(':util')
//也可以将远程依赖换成另外的远程依赖,假设我们修改过的代码发布到自己的 maven 中央仓库后叫:com.xxx.xxx:util:3.0
//substitute module('org.gradle:util:3.0') with module('com.xxx.xxx:util:3.0')
}
}
}
2. gradle.settingsEvaluated 在编译之前做一些初始化工作
之前接手过一个项目,是采用 react-native 框架进行开发的,熟悉前端以及 React 的同学都知道,项目 build 的第一步是先进行 npm install
或者 yarn
操作,这样所有依赖的第三方库都会下载到 node_modules
目录下面。在 react-native 当中,Android 工程会依赖 node_modules
目录下的原生 Android 代码,但是因为库的版本关系,每次我编译的时候,都会发现某个 module 中的 build.gradle 配置与主工程冲突,需要手动修改才能编译。需要注意的是,node_modules
目录是动态下载的第三方库,并不会被提交,也就是说每次执行 yarn
操作之后再编译 Android,都需要手动更改下这个目录下的库,非常繁琐。那么有没方法来自动帮我们解决这个问题,不用每次都要手动修改呢?答案是可以。gradle 有个生命周期钩子方法 gradle.settingsEvaluated
,在 gradle configure 阶段之前让我们做一些处理。
在我们项目的 settings.gradle
文件的最开头加上该方法的监听,注意该方法必须加在 settings.gradle 文件中,在 build.gradle 里没用。以下是个范例:
gradle.settingsEvaluated {
println "-------------settingsEvaluated start------------"
//假设我们要修改的是 react-native-fs 这个库当中的 build.gradle 文件
//先删除该文件
delete("${rootDir}/../node_modules/react-native-fs/android/build.gradle")
//我们将修改好的文件放到 ./replace-files/react-native-fs 这个目录当中
//前面已经删除了目标文件,现在只需将我们需要的文件复制过去即可
copy {
from("./replace-files/react-native-fs/build.gradle")
into("${rootDir}/../node_modules/react-native-fs/android")
}
println "-------------settingsEvaluated end------------"
}
执行打包命令 ./gradlew assembleRelease
等的时候,一开始就会执行以上代码,自动帮我们做好这些事情。
除此之外,还可以做很多事情,比如打包前先删除某个缓存目录的文件,每次打包时自动递增修改 versionCode
、versionName
,检查 local.properties 配置文件是否存在已经配置是否正确等等。
3. 修改包的输出路径
android {
applicationVariants.all { variant ->
variant.outputs.each { output ->
def date = releaseTime()
//这里只改变 release 包的输出路径
if (variant.buildType.name.contains('release')) {
//我们定义包的输出路径为 android_outputs/Android_APK
//这里可以根据情况定义任意路径
variant.packageApplicationProvider.get().outputDirectory = new File(project.rootDir.absolutePath + File.separator + "android_outputs"+ File.separator + "Android_APK")
//包名范例:Android-pro-release-v2.6.6-c266-d202108181428.apk
def fileName = "Android-${variant.productFlavors[0].name}-${buildType.name}-v" + versionName + "-c" + versionCode + "-d" + date + ".apk"
output.outputFileName = fileName
}
//拓展一下,也可以根据 productFlavors 来做配置
/*
if (variant.getName() == "qaRelease") {
//这里还可以动态修改 productFlavors 里的东西,例如:
variant.buildConfigField "String", "APP_CHANNEL", '"xiaomi"'
} else if (variant.getName() == "proRelease") {
}
*/
}
}
}
def releaseTime() {
return new Date().format("yyyyMMddHHmm")
}
4. 找到版本冲突的库
当项目越来越大,所使用的第三方库越来越多的时候,最让人头疼的是针对同一个库引入了多个不同的版本,编译的时候导致库冲突直接编译失败,问题找起来也很麻烦。还是利用resolutionStrategy
,我们可以快速找到冲突的库以及各版本号,配置如下:
configurations.all {
resolutionStrategy {
failOnVersionConflict()
}
}
下面是一个范例,在我的项目当中,om.android.support:multidex
这个库引入了2个不同的版本:1.0.3 和 1.0.2
Conflict(s) found for the following module(s):
- com.android.support:multidex between versions 1.0.3 and 1.0.2
找到冲突的库及版本后,我们可以用前面的方法,强制指定统一的版本号以解决冲突。
持续更新中......
系列文章
Android Gradle学习(一):Gradle基础入门
Android Gradle学习(二):如何创建Task
Android Gradle学习(三):Task进阶学习
Android Gradle学习(四):Project详解
Android Gradle学习(五):Extension详解
Android Gradle学习(六):NamedDomainObjectContainer详解
Android Gradle学习(七):Gradle构建生命周期
Android Gradle学习(八):统计Task执行时长
Android Gradle学习(九):一些有用的小技巧