一、简介
Gradle是一个基于JVM的构建工具,是一款通用灵活的构建工具,支持maven, Ivy仓库,支持传递性依赖管理,而不需要远程仓库或者是pom.xml和ivy.xml配置文件,基于Groovy,build脚本使用Groovy编写。
Gradle不单单是一个配置脚本,它的背后是三门语言。
1)、Groovy Language
Groovy 也是一门语言,Groovy是一门jvm语言,它最终是要编译成class文件然后在jvm上执行,所以Java语言的特性Groovy都支持,我们完全可以混写Java和Groovy。
那Groovy的优势是什么呢?Groovy提供了更加灵活简单的语法,大量的语法糖以及闭包特性可以让你用更少的代码来实现和Java同样的功能。比如解析xml文件,Groovy就非常方便,只需要几行代码就能搞定,而如果用Java则需要几十行代码。
2)、Gradle DSL
DSL的全称是Domain Specific Language,即领域特定语言,或者直接翻译成“特定领域的语言”,算了,再直接点,其实就是这个语言不通用,只能用于特定的某个领域,俗称“小语言”。因此DSL也是语言。
如Android项目中Projrct的build.gradle中buildscript和allprojects都是DSL,DSL是基于Groovy实现,可以很方便通过代码控制这些DSL来达到你的构建目的。
3)、Android DSL
DSL的定义如上。module下build.gradle中的android中的就是Android DSL。针对Android平台特有的。Gradle也可以用来构建后台应用。
如何学习Gradle?
- 学习 Groovy(http://docs.groovy-lang.org/)
- 学习 Gradle DSL(https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html)
- 学习 Android DSL和Task(http://google.github.io/android-gradle-dsl/current/index.html)
二、Gradle相关文件
1、Setting文件
在Gradle中,定义了一个设置文件,用于初始化以及工程树的配置。设置文件的默认名字是setting.gradle,放在根工程目录下。主要作用是配置子工程。Gradle中多
工程是通过工程树表示。根工程相当于Android Studio中的Project,子工程相当于Module,一个根工程有很多个子工程。一个子工程只有在setting文件中配置了Gradle才会识别,才会在构建的时候包含进去。setting.gradle文件配置如下:
include ':app', ':libgroovytest'
2、Build文件
每个Project都会有一个Build文件,该文件是该Project构建的入口。可以在这里对该Project进行配置,如配置版本、需要哪些插件、依赖哪些库。
三、Gradle脚本的执行时序
Gradle脚本的执行分为三个过程:
1、初始化
分析有哪些module将要被构建,为每个module创建对应的 project实例。这个时候settings.gradle文件会被解析。settings.gradle代码如下:
include ':app', ':a', ':b'
Gradle 将会为它们三个分别创建一个 Project 对象实例。
2、配置
处理所有的模块的 build.gradle,处理依赖,属性等。这个时候每个模块的build.gradle文件会被解析并配置,这个时候会构建整个task的链表(这里的链表仅仅指存在依赖关系的task的集合,不是数据结构的链表)。(例如 assembleDebug task 的执行依赖于其他 tasks 先执行,而这个依赖关系的确定就是在 Configuration 阶段)。配置阶段task闭包中的代码会执行,doFirst和doLast闭包中的代码不会执行。
配置完以后有一个重要的回调project.afterEvaluate,表示build.gradle中所有的模块都已经配置完了,可以准备执行task了。在app的build.gradle中添加如下代码:
project.afterEvaluate {
println "app project.afterEvaluate>>"
}
打印日志如下:
> Configure project :app
app build.gradle start
app project.afterEvaluate>>
当app下的build.gradle配置阶段执行完后会调用project.afterEvaluate。
3、执行
根据task链表来执行某一个特定的task,这个task所依赖的其他task都将会被提前执行。首先执行task中 doFirst {} 闭包中的内容,最后执行 doLast {} 闭包中的内容。
下面有一个工程,lib1是一个库,app引用了这个库。项目的工程目录如下:
执行clean任务日志如下:
-------------------------
settings.gradle start
settings.gradle end
> Configure project :
-------------------------
Project build.gradle start
project gradle testTask
Project build.gradle stop
> Configure project :app
-------------------------
app build.gradle start
app build.gradle finish
app project.afterEvaluate>>
> Configure project :lib1
-------------------------
lib1 build.gradle start
lib1 gradle testTask
lib1 gradle testTask11
lib1 build.gradle finish
lib1 evaluate start
lib1 evaluate end
:clean
:app:clean
:lib1:clean
可以看到,Gradle执行的时候遵循如下顺序:
1. 首先解析settings.gradle来获取模块信息,这是初始化阶段;
2. 然后配置每个模块,配置的时候并不会执行task(不执行doFirst和doLast),会执行task闭包中的内容;
3. 配置完了以后,有一个重要的回调project.afterEvaluate,它表示所有的模块都已经配置完了,可以准备执行task了;
4. 执行指定的task(执行doFirst和doLast)。
四、Task
1、Project和Task的联系
一个Project就是在你的业务范围内,被你抽象出来的一个个独立的模块,你可以根据项目情况抽象归类,最后一个个Project组成了你的整个Gradle构建。
一个Project包含很多个Task,即每个Project是由多个Task组成的。Task就是一个操作,一个原子性的操作,比如打个jar包、复制一份文件、编译一次Java代码,这就是一个Task。
2、doFirst和doLast
doFirst和doLast中的代码,不执行这个任务时,是不会执行的,但是直接写在闭包中的,就也是在这两个函数外的代码,是在配置阶段就会执行的。运行任务时,doFirst中的代码最先执行,doLast中的代码最后执行。
切记大部分的内容是写在 doLast{} 或 doFirst{} 闭包中,因为写在如果写在 task 闭包中的话,会在 Configuration 阶段也被执行。当执行其他任务时会执行所有任务task闭包中的代码。当执行自己的任务,自己任务 doLast{} 和 doFirst{} 闭包中的代码才会执行
在app的build.gradle添加如下代码:
task testExecTask {
println '我会在 Configuration 阶段执行'
doFirst {
println '我仅会在 testExecTask 的 Execution 阶段执行'
}
doLast {
println '我仅会在 testExecTask 的 Execution 阶段执行'
}
}
写在 task 闭包中的内容是会在 Configuration 中就执行的,例如上面的 “ println ‘我会在 Configuration 阶段执行’”;而 doFirst {}、doLast {} 闭包中的内容是在 Execution 阶段才会执行到(doFirst {}、 doLast {} 实际上就是给当前 task 添加 Listener,这些 Listeners 只会在当前 task Execution 阶段才会执行)。
在AS中执行名字gradlew testExecTask运行testExecTask任务打印的日志如下:
> Configure project :app
testExecTask>>我会在 Configuration 阶段执行
> Task :app:testExecTask
doFirst>>我仅会在 testExecTask 的 Execution 阶段执行
doLast>>我仅会在 testExecTask 的 Execution 阶段执行
doLast也可以使用<<简便代替,<<后跟一个闭包。具体使用方式如下:
task testLeftShiftTask << {
println 'testLeftShiftTask>>doLast'
}
testLeftShiftTask.doFirst{
println 'testLeftShiftTask>>doFirst'
}
运行打印日志如下:
> Task :app:testLeftShiftTask
testLeftShiftTask>>doFirst
testLeftShiftTask>>doLast
五、创建Gradle插件
1、脚本插件
脚本插件就是一个普通的gradle构建脚本,通过在一个foo.gradle脚本中定义一系列的task,另一个构建脚本build.gradle通过apply from:'foo.gradle’即可引用这个脚本插件。
先新建文件customtask.gradle。在文件中定义一个任务如下:
project.task("customtask") {
doLast {
println("$project.name:customtask")
}
}
然后在需要引用该插件的Moudle的build.gradle中引用customtask.gradle。引用代码如下:
apply from: 'customtask.gradle' // 引用脚本插件
然后就可以通过命令运行这个任务了。
gradlew customtask
运行这个任务的日志如下:
> Task :app:customtask
app:customtask
2、对象插件
对象插件是指实现了org.gradle.api.Plugin接口的类。Plugin接口需要实现void apply(T target)这个方法。该方法中的泛型指的是此Plugin可以应用到的对象,而我们通常是将其应用到Project对象上。
新建一个Java Library Moudle,更改目录/main/java为/main/groovy。
在build.gradle中引用groovy插件,代码如下:
apply plugin: 'groovy'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation localGroovy()
}
新建CustomPluginInBuildSrc类,代码如下:
package com.demo.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
class CustomPluginInBuildSrc implements Plugin<Project> {
@Override
void apply(Project project) {
project.task('showCustomPluginInBuildSrc') {
doFirst {
println('task in CustomPluginInBuildSrc')
}
}
}
}
由于buildSrc目录是gradle默认的目录之一,该目录下的代码会在构建是自动编译打包,并被添加到buildScript中的classpath下,所以不需要任何额外的配置,就可以直接被其他模块的构建脚本所引用。
在app的build.gradle中引用该插件,代码如下:
apply plugin: com.demo.plugin.CustomPluginInBuildSrc
然后通过gradlew showCustomPluginInBuildSrc运行插件中的任务,日志如下:
> Task :app:showCustomPluginInBuildSrc
task in CustomPluginInBuildSrc
3、Extension
先举个例子感受下Extension的作用。在上面代码基础上。在CustomPluginInBuildSrc中增加如下代码:
class CustomPluginInBuildSrc implements Plugin<Project> {
@Override
void apply(Project project) {
def extension = project.extensions.create('customPluginExtension', CustomPluginExtension)
project.task('CustomPluginExtension') {
doLast {
println extension.message
}
}
}
}
通过project.extensions.create创建extension,create方法第一个参数是我们在build.gradle中使用时的名字,第二个参数是对应的Extension类名。然后创建任务CustomPluginExtension,在任务中打印的CustomPluginExtension的成员变量message。
CustomPluginExtension类的代码如下,其实就是定义一个JavaBean:
class CustomPluginExtension {
String message = 'Hello World'
}
然后在build.gradle中就可以使用customPluginExtension,通过闭包指定message 的值。代码如下:
customPluginExtension{
message 'hello gradle'
}
然后我们执行命令gradlew CustomPluginExtension,打印日志如下:
> Task :app:CustomPluginExtension
hello gradle
注意这里打印的“hello gradle”,就是我们在build.gradle闭包中指定的字符串。
这样有什么用处?我们可以在build.gradle中定义闭包设置一些参数,这些参数会传到我们定义的插件中,即在插件中我们可以获取build.gradle中定义的参数。
我们先来看一段 Android 应用的 Gradle 配置代码:
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.hm.iou.thinapk.demo"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
相信做 Android 应用开发的同学,对这段代码都快看吐了吧。记得当初刚从 eclipse 转到 Android Studio 的时候,看这些配置就像看天书一样,只知道按规定配置就可以了。但是为什么要这样配置?除此外还支持哪些配置?为什么一定要在 android 这个命名空间下配置呢?可以不可以定义自己的特殊配置呢?
上面这个 android 打包配置,就是 Gradle 的 Extension,翻译成中文意思就叫扩展。它的作用就是通过实现自定义的 Extension,可以在 Gradle 脚本中增加类似 android 这样命名空间的配置,Gradle 可以识别这种配置,并读取里面的配置内容。
在 app 的 build.gradle 里我们通常会采用插件 apply plugin: ‘com.android.application’ ,而在 library module 中则采用插件 apply plugin: ‘com.android.library’,先来看一张截图:
注意查看android gradle插件源码的方法是添加android gradle的依赖,添加依赖代码如下:
implementation 'com.android.tools.build:gradle:3.0.0'
图中类 AppPlugin 就是插件 com.android.application 的实现类,LibraryPlugin 则是插件 com.android.library 的实现类,接着再看看 AppPlugin 里是怎样创建 Extension 的:
public class AppPlugin extends BasePlugin implements Plugin<Project> {
@Inject
public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
super(instantiator, registry);
}
@NonNull
@Override
protected BaseExtension createExtension(
@NonNull Project project,
@NonNull ProjectOptions projectOptions,
@NonNull Instantiator instantiator,
@NonNull AndroidBuilder androidBuilder,
@NonNull SdkHandler sdkHandler,
@NonNull NamedDomainObjectContainer<BuildType> buildTypeContainer,
@NonNull NamedDomainObjectContainer<ProductFlavor> productFlavorContainer,
@NonNull NamedDomainObjectContainer<SigningConfig> signingConfigContainer,
@NonNull NamedDomainObjectContainer<BaseVariantOutput> buildOutputs,
@NonNull ExtraModelInfo extraModelInfo) {
return project.getExtensions()
.create(
"android",
AppExtension.class,
project,
projectOptions,
instantiator,
androidBuilder,
sdkHandler,
buildTypeContainer,
productFlavorContainer,
signingConfigContainer,
buildOutputs,
extraModelInfo);
}
...
}
在 createExtension() 方法中,可以看到创建了一个名为 android 的 Extension,该 Extension 的类型为 AppExtension,而 AppExtension 的继承结构为 AppExtension -> TestedExtension -> BaseExtension,所以它的实现逻辑大部分都是在 BaseExtension 里实现的。以后当我们不知道 android 里有哪些配置时,除了查看 API 文档以外,还可以直接翻看 BaseExtension 源码。