在上一篇文章中 Gradle 之语言基础 Groovy 主要介绍了 Groovy 的基础语法(如果没有 Groovy 的基础,建议先看看上篇文章,如果可以动手敲一下里面的示例代码就更好不过了),也是为本篇文章打基础的。
本篇文章主要介绍 Gradle 在 Android 中的应用(Android DSL 和 Gradle DSL),也是通过一些示例来介绍和理解,主要分为以下一些内容,示例代码都在 GradleForAndroid
一. Gradle 构建生命周期
一个 Gradle 的构建通常有如下三个阶段
初始化:项目 Project 实例会在该阶段被创建。如果一个项目中包含有多个模块,并且每一个模块都有其对应的 build.gradle 文件,就会为每一个模块都创建一个对应的 Project 实例
配置:执行各个模块下的 build.gradle 脚本,为 Project实例创建和配置 Task,构造 Task 任务依赖关系图以便在执行阶段按照依赖关系执行 Task
执行:在这个阶段将会决定执行哪个 Task,哪个 Task 被执行取决于开始该次构建的参数配置和该 Gradle 文件的当前目录
在创建完成一个新的 Android 应用项目之后,一般情况下, .gradle 文件的目录结构如下所示:
GradleForAndroid
|---- build.gradle
|---- setting.gradle
\---- app
\---- build.gradle
其中,两个文件 build.gradle 和 setting.gradle 位于项目的根目录下,还有一个 build.gradle 位于 \app\ 目录下。\build.gradle 是顶层构建文件,\app\build.gradle 是模块构建文件。
我们以上面这个新创建的项目来学习 Gradle 的构建生命周期
1.1 初始化
在初始化阶段,会创建一个 Setting 对象,对应着 setting.gradle 文件,
Setting 对象的一个主要作用就是声明哪些模块将会参与到构建中去,Setting 文档(Gradle API 5.0)
在新建的项目中,setting.gradle 文件一般会默认包含一行内容,如下所示
include ':app'
上面这一行,其实是一行 groovy 代码的简写,对应的是 Setting#include(String[] projectPaths) 方法,表示 :app 模块将会参与到构建中去。
如果我们创建一个 library 库,setting.gradle 将会变为如下所示,表示 :app 和 :library 两个模块将会参与到构建中
include ':app', ':library'
setting.gradle 脚本文件可以中读取一些只可读的配置信息,这些配置信息的来源可以有如下三个:
可以在本工程的 gradle.properties 文件中定义配置信息
也可以在系统的 gradle.properties 文件中定义配置信息,系统的 gradle.properties 位于 user's .gradle 目录下
还可以通过 -P 命令参数指定配置信息,比如 ./gradlew clean -P cmd='Hello from commandLine' 便在执行 clean task 的时候,指定了 cmd='Hello from commandLine' 配置信息
上面讲到,一个 setting.gradle 文件对应着一个 Setting 对象,Setting 对象包含的方法如下图所示
Setting.png
例如有如下代码
include ':app', ':library'
println propertiesFile
println DEFAULT_SETTINGS_FILE
println getRootProject().name
println getRootProject().path
getGradle().addBuildListener(new BuildListener() {
@Override
void buildStarted(Gradle gradle) {
println 'buildStarted'
}
@Override
void settingsEvaluated(Settings settings) {
println "settingsEvaluated"
}
@Override
void projectsLoaded(Gradle gradle) {
println 'projectsLoaded'
}
@Override
void projectsEvaluated(Gradle gradle) {
println 'projectsEvaluated'
}
@Override
void buildFinished(BuildResult result) {
println 'buildFinished'
}
})
其输出是:
Hello from gradle.properties
settings.gradle
GradleForAndroid
:
settingsEvaluated
projectsLoaded
projectsEvaluated
BUILD SUCCESSFUL in 0s
3 actionable tasks: 3 executed
buildFinished
1.2 配置
在配置阶段,会执行所有的 build.gradle,包括项目根目录下的 build.gradle 和各个 module 下的 build.gradle
在执行 build.gradle 的时候,会为每个 build.gradle 创建一个对应的 Project 对象,Project 文档(Gradle API 5.0)
配置阶段会执行 build.gradle 里面的所有代码和 Task 里面的配置代码,比如下面的 printProperties Task,只执行了 doLast{} 之外的代码,doLast{} 之外的代码是 Task 的配置代码
配置执行完成之后,会根据各个 Task 的依赖关系生成一个有向无环图,可以通过Gradle对象的getTaskGraph方法访问,对应的类为TaskExecutionGraph
在执行所有的 Gradle Task 之前,都会执行 初始化阶段 和 配置阶段 的代码
比如有如下代码
// Top-level build file where you can add configuration options common to all sub-projects/modules.
println "/build.gradle 开始配置"
buildscript {
println "/build.gradle buildscript 开始配置"
ext.kotlin_version = '1.2.71'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
println "/build.gradle buildscript 结束配置"
}
allprojects {
println "/build.gradle allprojects 开始配置"
repositories {
google()
jcenter()
}
println "/build.gradle allprojects 结束配置"
}
task clean(type: Delete) {
delete rootProject.buildDir
}
ext {
local = 'Hello from build.gradle'
}
task printProperties {
println "/build.gradle task printProperties 开始配置"
println '/build.gradle task printProperties'
println "/build.gradle task printProperties 结束配置"
doLast {
println local
println propertiesFile
if (project.hasProperty('cmd')) {
println cmd
}
}
}
println "/build.gradle 结束配置"
在 Terminal 里面执行 ./gradlew clean 会有如下输出
Hello from gradle.properties
settings.gradle
GradleForAndroid
:
settingsEvaluated
projectsLoaded
// 配置阶段,执行 /build.gradle 里面的代码
> Configure project :
/build.gradle buildscript 开始配置
/build.gradle buildscript 结束配置
/build.gradle 开始配置
/build.gradle allprojects 开始配置
/build.gradle allprojects 结束配置
/build.gradle allprojects 开始配置
/build.gradle allprojects 结束配置
/build.gradle allprojects 开始配置
/build.gradle allprojects 结束配置
/build.gradle task printProperties 开始配置
/build.gradle task printProperties
/build.gradle task printProperties 结束配置
/build.gradle 结束配置
projectsEvaluated
BUILD SUCCESSFUL in 0s
3 actionable tasks: 3 executed
buildFinished
1.3 执行
执行阶段就是指执行某个具体的任务 Task。说道 Task,我想大家应该比较熟悉,在 Android 项目中依赖了 Android Gradle 插件以后,会有许多自带的 Task,比如常用的 clean、assemble 等,而且大家更应该掌握的是如何自定义 Task,关于 Task 会单独抽一节来讲述。
二. 自定义 Task
任务 Task 代表了在构建过程中的一个单原子性的动作,比如:编程生成 .class 文件或者生成 javadoc 等.
每一个 task 都是属于某一个 Project 对象的,每一个 task 都有自己的名字,在 project 中 task 的名字是唯一的,如果在整个项目 projects 范围内需要指定某个 task 的话,也需要指定 project 的名字,project 和 task 的名字中间使用 : 相连接,比如:./gradlew :app:clean
创建 Task 对象的方法有以下几种
// 通过 TaskContainer.create(String) 创建 `Task`
getTasks().create('helloTask') {
doLast {
println 'create Task by TaskContainer'
}
}
task helloTask1 {
doLast {
println 'create Task by task(String name)'
}
}
class HelloTask extends DefaultTask {
def message = 'create Task by extends DefaultTask'
@TaskAction
def hello() {
println message
}
}
// 通过 type 参数可以指定该 task 的父类,默认的父类是 DefaultTask
task helloTask2(type: HelloTask)
一个 Task 是由一系列的 Action 组成的,当一个 Task 执行的时候就是按照一定的顺序执行这些 Action,可以有以下两种方式向 Task 中添加 Action
通过闭包 Closure 的方式添加 Action
class HelloTask extends DefaultTask {
def message = 'create Task by extends DefaultTask'
@TaskAction
def hello() {
println message
}
}
task helloTask2(type: HelloTask) {
doFirst {
println 'helloTask2 doFirst'
}
doLast {
println 'helloTask2 doLast'
}
}
直接添加 Action 实例对象
def taskDef = task helloTask3(type: HelloTask)
taskDef.doFirst(new Action() {
@Override
void execute(Task task) {
println 'helloTask3 Action execute doFirst'
}
})
taskDef.doLast(new Action() {
@Override
void execute(Task task) {
println 'helloTask3 Action execute doLast'
}
})
Task 依赖关系 & Task 执行顺序
在 Task 中有两个很重要的概念 dependsOn 和 mustRunAfter。dependsOn 用于声明两个 Task 对象之间的依赖关系,mustRunAfter 用于声明一个 Task 必须在另一个 Task 之后运行,虽然感觉差不多,但是实际上还是有区别的
helloTaskB.dependsOn(helloTaskA) 中,可以单独运行 helloTaskA,但是运行 helloTaskB 的时候会触发 helloTaskA 的执行
helloTaskC.mustRunAfter(helloTaskA)中,helloTaskA 和 helloTaskC 都是可以单独运行的,但是当 helloTaskC 和 helloTaskA 同时运行时,helloTaskC 一定会在 helloTaskA 之后运行
task helloTaskA {
doFirst {
println 'helloTaskA doFirst'
}
}
task helloTaskB {
doFirst {
println 'helloTaskB doFirst'
}
}
helloTaskB.dependsOn(helloTaskA)
task helloTaskC {
doFirst {
println 'helloTaskC doFirst'
}
}
helloTaskC.mustRunAfter(helloTaskA)
Android 中使用自定义 Task
在 Gradle 构建的时候,需要将自定义的 Task 添加到构建过程中时,需要把握好添加自定义 Task 的时机与位置
下面一幅图清晰地展示了 Gradle 构建过程中一些关键的回调,可以在下面一些回调中添加自定义 Task
在 project 对象中,可以通过 gradle 对象得到 TaskExecutionGraph 的实例对象,也可以通过 TaskExecutionGraph 实例对象一些关键回调添加自定义的 Task。比如下面这个例子,就是在 TaskExecutionGraph 实例对象准备好之后,弹出一个 dialog 用于输入 storePass 和 keyPass,然后将 storePass 和 keyPass 设置到 android.signingConfigs 中
apply plugin: 'com.android.application'
import groovy.swing.SwingBuilder
//......
gradle.taskGraph.whenReady { taskGraph ->
if (taskGraph.hasTask(':app:assembleRelease')) {
def storePass = ''
def keyPass = ''
if (System.console() == null) {
System.setProperty('java.awt.headless', 'false')
new SwingBuilder().edt {
dialog(modal: true, title: 'Enter password', alwaysOnTop: true, resizable: false, locationRelativeTo: null, pack: true, show: true) {
vbox { // Put everything below each other
label(text: "Please enter store passphrase:")
def input1 = passwordField()
label(text: "Please enter key passphrase:")
def input2 = passwordField()
button(defaultButton: true, text: 'OK', actionPerformed: {
storePass = input1.password;
keyPass = input2.password;
dispose();
})
}
}
}
} else {
storePass = System.console().readPassword("\nPlease enter store passphrase: ")
keyPass = System.console().readPassword("\nPlease enter key passphrase: ")
}
if (storePass.size() <= 0 || keyPass.size() <= 0) {
throw new InvalidUserDataException("You must enter the passwords to proceed.")
}
storePass = new String(storePass)
keyPass = new String(keyPass)
android.signingConfigs.release.storePassword = storePass
android.signingConfigs.release.keyPassword = keyPass
}
}
如下图所示 TaskExecutionGraph 的方法结构如下图所示,都是非常实用方便的方法
TaskExecutionGraph.png
三. Android DSL & Gradle DSL
3.1 Android DSL
Android DSL 是 Gradle 的一个 Android 插件,其实在使用 Android Studio 开发的时候经常会和 Android DSL 打交道,比如下面 android{ } 闭包 里面的内容都是 Android DSL
apply plugin: 'com.android.application'
import groovy.swing.SwingBuilder
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.lijiankun24.gradleforandroid"
minSdkVersion 15
targetSdkVersion 27
// ......
}
buildTypes {
// ......
}
signingConfigs {
// ......
}
}
至于里面都有哪些 API,可以去 Android DSL 文档 查看,也可以去 GitHub 上面搜 android-gradle-dsl
3.2 Gradle DSL
Gradle DSL 在上面介绍 Gradle 生命周期和自定义 Task 的时候已经介绍过了,比如上面介绍的 Setting 和 TaskExecutionGraph 都是 Gradle DSL 中的类,其他的类和方法等 API 可以去 Gradle DSL 文档 查看,或者可以在 Android Studio 中像查看 Android SDK 源码一样去查看 Gradle DSL 的源码