在上一篇文章中 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
对象包含的方法如下图所示
例如有如下代码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
之前,都会执行初始化阶段
和配置阶段
的代码 - 比如有如下代码
在 Terminal 里面执行// 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 结束配置"
./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<Task>() { @Override void execute(Task task) { println 'helloTask3 Action execute doFirst' } }) taskDef.doLast(new Action<Task>() { @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
的方法结构如下图所示,都是非常实用方便的方法
三. 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 的源码