Gradle 作为一款优秀的构建工具,也作为是目前 Android 主流的构建工具,不管是通过命令行还是通过 GUI 方式构建,背后都是通过 Gradle 来实现的,所以学习 Gradle 非常重要。无论是组件化、插件化、热修复等技术都需要对 Gradle 比较了解,不懂 Gradle 将无法完成上述事情,所以 Gradle 必须要学习。这篇文章主要是针对组件化的 Android 项目,如何使用 Gradle 完成公共配置项的抽取,Gradle 如何定义 Task,工程测试环境与正式环境的自动配置等内容。
Gradle = Groovy/Kotlin + DSL
Gradle 可以看成 Groovy + Gradle DSL + Android DSL。DSL 的全称是 Domain Specific Language,即领域特定语言,其实就是这个语言不通用,只能用于特定的某个领域。因此 DSL 也是语言。Groovy 是一门 JVM 语言,和 Kotlin 一样最终是要编译成 class 文件然后在 JVM 上执行,所以 Java 语言的特性 Groovy 都支持,我们完全可以混写 Java 和 Groovy。
使用 Groovy 就像使用 JavaScript 一样简单,Groovy 提供了更加灵活简单的语法,大量的语法糖以及闭包特性可以让你用更少的代码来实现和 Java 同样的功能。比如解析 xml 文件,Groovy 就非常方便,只需要几行代码就能搞定,而如果用 Java 则需要几十行代码。关于 Groovy 的语法,我之前写过一篇博客 《Gradle 的使用和配置》,简单介绍了 Groovy 打印字符串,定义变量、方法、集合、闭包、List/Map 等。更多详细的语法内容可以参考官方文档:http://www.groovy-lang.org/api.html。
Gradle 的执行顺序
新建一个 Android 工程,有 settings.gradle、整个工程的 build.gradle、app module 的 build.gradle。
Gradle 脚本的执行分为三个过程:
1、初始化:分析有哪些 module 将要被构建,为每个 module 创建对应的 project 实例。这个时候 settings.gradle 文件会被解析。
2、配置:处理所有的模块的 build 脚本,处理依赖,属性等。这个时候每个模块的 build.gradle 文件会被解析并配置,这个时候会构建整个 task 的链表(这里的链表仅仅指存在依赖关系的 task 的集合,不是数据结构的链表)。
3、执行:根据 task 链表来执行某一个特定的 task,这个 task 所依赖的其他 task 都将会被提前执行。
分别在三个文件里打印一下执行顺序如下:

Gradle 的 Task
定义 Gradle 的 Task
Task 可以理解为 Gradle 的执行单元,Gradle 通过一个个 task 来完成具体的构建任务,下面我们来学习一下 Task 的定义:

通过上述方式定义的 task,括号内部的代码会在配置阶段执行,也就是说,只要我执行任何一个 task,那段代码都会执行,因为每个 task 执行之前都需要进行一遍完整的配置。但是很多时候我们并不需要写配置代码,我们想要括号内的代码仅仅在执行我们的 task 的时候才执行,这个时候可以通过 doFirst 或者 doLast 来完成。需要注意的是,一个 project 包含多个 Task,一个 Task 包含多个 Action,这里的 Action 就是完成一个 Task 需要的具体操作:

这样的话执行 aMyTask 会出现如下结果:
1 2 3 4 5 6 7 8 9
| ......
> Task :aMyTask before execute aMyTask2 before execute aMyTask1 after execute aMyTask1 after execute aMyTask2
BUILD SUCCESSFUL in 717ms
|
注意:println"run aMyTask"
这句代码并不是在队列的中间执行,这句代码是在配置阶段执行的,所有直接在 Task 里写的动作并不会添加到 Task 的 Action 列表中,只会当做 Task 的配置信息执行,所以声明周期一定要搞清楚。!

不过通过 extends DefaultTask 可以在里面使用 @TaskAction 注解,这样等同于把注解的 Action 动作添加到了 Action 队列里,参考下面 Task 也可以继承的例子:
Gradle api 还给我们提供了其他的方式创建 Task:
1 2 3
| tasks.create ("aMyTask3"){ println "run aMyTask3 ..." }
|
Task 也可以继承:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class MyTask4 extends DefaultTask {
@TaskAction void action (){ println "run MyTask4 ..." } }
task aMyTask5 (type: MyTask4){ doLast { println "run MyTask5 ..." } }
|
输出为:
1 2 3 4 5 6 7 8
| ......
> Task :aMyTask5 run MyTask4 ... run MyTask5 ...
BUILD SUCCESSFUL in 686ms 1 actionable task: 1 executed
|
Task 的属性与方法
Task 的常见属性如下:
属性名 |
描述 |
actions |
该任务将要执行的一系列动作 |
dependsOn |
返回该任务依赖的任务 |
description |
任务的描述 |
enabled |
该任务是否开启 |
finalizedBy |
返回完成此任务之后的任务 |
group |
任务的分组 |
mustRunAfter |
返回该任务必须在哪个任务之后运行的任务 |
name |
任务的名字 |
path |
任务的路径 |
project |
任务所属的 Project |
Task 的常见方法如下:
方法名(不列出参数) |
描述 |
dependsOn |
给任务设置依赖任务 |
doFirst |
给 Task 添加一个任务动作开始执行之前的动作 |
doLast |
给 Task 添加一个任务动作执行结束之后的动作 |
finalizedBy |
给任务添加终结任务,即该任务结束后执行的任务 |
hasProperty |
判断该任务是否有指定属性 |
mustRunAfter |
声明该任务必须在某些任务之后执行 |
onlyIf |
给任务添加断言,只有满足条件才可以执行任务 |
property |
返回指定属性的值 |
setProperty |
修改指定属性的值 |
Gradle 声明周期
其实就是上面说到的 Gradle 的执行顺序,这里只不过是换个说法而已。
1、初始化阶段
会去读取根工程中 setting.gradle 中的 include 信息,决定有哪几个工程加入构建,创建 project 实例,比如下面有三个工程:include ‘:app’, ‘:lib1’, ':lib2'
。
2、配置阶段
会去执行所有工程的 build.gradle 脚本,配置 project 对象,一个对象由多个任务组成,此阶段也会去创建、配置 task 及相关信息。
3、运行阶段
根据 Gradle 命令传递过来的 Task 名称,执行相关依赖任务,Task 的 Action 会在这个阶段执行。
Task 依赖与顺序
一个 Project 拥有多个 Task,这些 Task 之间的关系由有向无环图维护。而有向无环图是在构建的配置过程中生成的,我们可以通过 gradle.taskGraph
来监听这个过程。
dependsOn 给某个任务设置依赖任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
task taskA { doLast { println 'TaskA run ...' } }
task taskB { dependsOn taskA doLast { println 'TaskB run ...' } }
task taskC (dependsOn: taskA) { dependsOn taskA doLast { println 'TaskC run ...' } }
task taskD { doLast { println 'TaskD run ...' } }
task taskE { doLast { println 'TaskE run ...' } } taskE.dependsOn taskA, taskD
|
finalizedBy 给某个任务设置终结任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| task taskA { doLast { println 'TaskA run ...' } }
task taskB { finalizedBy taskA doLast { println 'TaskB run ...' } }
task taskC { doLast { println 'TaskC run ...' } }
task taskD { doLast { println 'TaskD run ...' } }
taskD.finalizedBy taskA, taskC
|
对任务进行 finalizedBy 配置和 dependsOn 很类似,其作用和 dependsOn 恰好相反。在某任务执行完后,会执行其设置的终结任务。
mustRunAfter 如果 taskB.mustRunAfter (taskA) 则表示 taskB 必须在 taskA 执行之后再执行,这个规则比较严格。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| task taskA { doLast { println 'TaskA run ...' } }
task taskB { doLast { println 'TaskB run ...' } }
taskA.mustRunAfter taskB
|
运行命令 ./gradlew taskA taskB
,就会发现 taskB 会先执行。
如何跳过 Task
有时候某些任务需要禁止执行或者满足某个条件才能执行,Gradle 提供了多种方式来跳过任务。
方式一 :每个任务都有个 enabled 属性,可以启用和禁用任务,默认是 true,表示启用。如果设置为 false ,则会禁止该任务执行。
1 2 3 4 5 6 7 8
| task disableTask { enabled false doLast { println 'disableTask run ...' } } disableTask.enabled = false
|
方式二 :使用 onlyIf 判断方法,只有当 onlyIf 里返回 true 时该任务才可以执行。
1 2 3 4 5 6 7 8 9 10 11
| task onlyIfTestTask { doLast { println 'onlyIfTestTask run ...' } }
onlyIfTestTask.onlyIf { !project.hasProperty ('xx') }
|
方式三 :使用 StopExecutionException 。如果任务抛出这个异常,Gradle 会跳过该任务的执行,转而去执行下一个任务。
1 2 3 4 5 6 7 8 9 10 11 12 13
| task taskA { doLast { throw new StopExecutionException () } }
task taskB (dependsOn: taskA) { doLast { println 'taskB run ...' } }
|
方式四 :利用 Task 的 timeout 属性来限制任务的执行时间。一旦任务超时,它的执行就会被中断,任务将被标记失败。Gradle 中内置任务都能及时响应超时。
1 2 3 4 5 6 7 8 9 10 11 12 13
| task taskA { doLast { Thread.sleep (100000) } timeout = Duration.ofMillis (500) }
task taskB (dependsOn: taskA) { doLast { println 'taskB run ...' } }
|
其实常用的还是方法一和方法二。
上面已经说完了如何创建和使用 Task,Task 作为 Gradle 的主要执行骨架是非常重要的,我们可以通过 Task 的各种属性、方法来灵活地配置和调整任务的依赖、执行顺序以及运行规则。
抽取 Gradle 中的重复项
回到组件化配置相关的内容,此时再新建一个名为 mylibrary 的 Android Library module,mylibrary 的 build.gradle 和 app module 的 build.gradle 有很多重复项,那么如何抽取出这部分的重复项目呢?

首先在项目根目录下新建一个 app_config.gradle,在里面抽取出公共的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
ext { username = "changlin"
app_android = [ compileSdkVersion : 30, buildToolsVersion : "30.0.3", minSdkVersion : 23, targetSdkVersion : 30, versionCode : 1, versionName : "1.0", testInstrumentationRunner : "androidx.test.runner.AndroidJUnitRunner" ]
app_impl = [ "appcompat": 'androidx.appcompat:appcompat:1.2.0', "material": 'com.google.android.material:material:1.3.0', "junit": 'junit:junit:4.13.2', "androidx_junit": 'androidx.test.ext:junit:1.1.2', "androidx_espresso": 'androidx.test.espresso:espresso-core:3.3.0' ]
app_compile = [ sourceCompatibility: JavaVersion.VERSION_1_8, targetCompatibility: JavaVersion.VERSION_1_8, ] }
|
在整个项目的 build.gradle 中引入 app_config.gradle:
1 2 3 4 5 6 7 8 9 10 11 12
| println 'build.gradle run ...'
apply from : 'app_config.gradle'
buildscript { ... }
allprojects { ... }
|
接下来在 app module 与 mylibrary module 中使用定义好的配置即可,app module 的 build.gradle:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| plugins { id 'com.android.application' }
println 'app -> build.gradle run ...'
def my_name = this.rootProject.ext.username println my_name
println "rootProject.name = ${rootProject.name}"
android { compileSdkVersion app_android.compileSdkVersion buildToolsVersion app_android.buildToolsVersion
defaultConfig { applicationId "com.tal.learn_gradle" minSdkVersion app_android.minSdkVersion targetSdkVersion app_android.targetSdkVersion versionCode app_android.versionCode versionName app_android.versionName
testInstrumentationRunner app_android.testInstrumentationRunner }
buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility app_compile.sourceCompatibility targetCompatibility app_compile.targetCompatibility } }
dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
app_impl.each { k, v -> implementation v println " 引入 > ${k}" }
}
|
mylibrary module 的 build.gradle:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| plugins { id 'com.android.library' }
def app_android = this.rootProject.ext.app_android
android { compileSdkVersion app_android.compileSdkVersion buildToolsVersion app_android.buildToolsVersion
defaultConfig { minSdkVersion app_android.minSdkVersion targetSdkVersion app_android.targetSdkVersion versionCode app_android.versionCode versionName app_android.versionName
testInstrumentationRunner app_android.testInstrumentationRunner consumerProguardFiles "consumer-rules.pro" }
buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro' } }
compileOptions { sourceCompatibility app_compile.sourceCompatibility targetCompatibility app_compile.targetCompatibility } }
dependencies { implementation app_impl.appcompat implementation app_impl.material testImplementation app_impl.junit androidTestImplementation app_impl.androidx_junit androidTestImplementation app_impl.androidx_espresso }
|
提取这么多重复项目,来看看结果吧,依旧是成功编译:

环境参数自动变更
比如在 app_config.gradle 里面定义两个 URL:
1 2 3 4 5
| app_server_url = [ "debug": "http://test.xxx.com/xxx", "release": "http://product.xxx.com/xxx" ]
|
现在只需要在 app module 的 build.gradle 里面如下写法:
1 2 3 4 5 6 7 8 9 10 11
| buildTypes { debug { buildConfigField ("String", "SERVER_URL", "\"${app_server_url.debug}\"") } release { buildConfigField ("String", "SERVER_URL", "\"${app_server_url.release}\"") ... } }
|
Build 一下工程,便会在 BuildConfig 中自动生成 URL 常量:

另外,如果想在代码中使用自己在 app_config.gradle 定义的 isRelease
字段可以这样使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| android { compileSdkVersion app_android.compileSdkVersion buildToolsVersion app_android.buildToolsVersion
defaultConfig { ... targetSdkVersion 30 versionCode 1 versionName "1.0" ... buildConfigField ("boolean", "isRelease", String.valueOf (isRelease)); } ... }
|

参考资料
Gradle 官方使用手册 https://docs.gradle.org/current/userguide/userguide.html
Groovy 语言官方 API http://www.groovy-lang.org/api.html