前言
Gradle是Android工程师的进阶必备,最近我也开始慢慢了解Gradle。
本文不会花太多的篇幅介绍Groovy语法,主要是介绍Gradle的基本概念,和一些常见的用法。
先给大家安利一下官方文档吧Gradle User Manual,如果英语不错的话我感觉看官方文档可能更好,因为本文的主要内容也来自官方文档。
Groovy
基本概念
Groovy是一种动态语言。这种语言比较有特点,它和Java一样,也运行于Java虚拟机中。你可以认为Groovy扩展了Java语言。
比如,Groovy对自己的定义就是:Groovy是在 java平台上的、 具有像Python, Ruby 和 Smalltalk 语言特性的灵活动态语言, Groovy保证了这些特性像 Java语法一样被 Java开发者使用。
实际上,由于Groovy Code在真正执行的时候已经变成了Java字节码,所以JVM根本不知道自己运行的是Groovy代码。
闭包
Groovy的基本语法就不介绍了,可以参看任玉刚的Gradle从入门到实战 - Groovy基础,这里简单聊一下闭包。
官方定义:
A closure in Groovy is an open, anonymous, block of code that can take arguments, return a value and be assigned to a variable.
Groovy中的闭包是一段开放的、匿名的代码块,并且可以带参数,返回值,也可以赋值给一个变量。
简而言之,闭包就是一段代码块。
看如下代码:
task hello {
doLast {
println 'Hello, My First task!
}
}
以上代码是简写,省略了圆括号,其实应该是:
task hello {
doLast ({
println 'Hello, My First task!
})
}
这里的闭包就是;
//闭包是一段代码,所以需要用大括号括起来
{
println 'Hello, My First task!
}
如上代码的意思是定义了一种task叫hello(task是什么,下文会讲到),doLast就是把一个闭包对象传递给了这个task,在task的最后阶段会执行这个闭包中的代码println 'Hello, My First task!
。
有了圆括号,你会知道doLast只是把一个Closure对象传了进去。很明显,它不代表这段脚本解析到doLast的时候就会调用println 'Hello, My First task!
。但是把圆括号去掉后,就感觉好像println 'Hello, My First task!
立即就会被调用一样!
Gradle
Gradle简介
Gradle是一个基于JVM的构建工具,支持传递性依赖管理,是一款通用灵活的构建工具。面向Java应用为主,当然也支持Groovy、Kotlin、Scala等等。
Project
Gradle中,每一个待编译的工程都叫一个Project。
先来看我的一个Android工程结构:
我的Android工程叫AndroidStudy,它是一个Project。我的项目中还有两个module,app和buildsrc,每个module下都有一个build.gradle文件。这里的每一个module对于Gradle来说也是一个Project,而build.gradle文件就是该Project的编译脚本。
所以这里有一个主Project–AndroidStudy和两个子Project,app和buildsrc。
在命令行中输入:
gradle projects
查看工程中的Project,得到如下结果:
Root project ‘AndroidStudy’
+— Project ‘:app’
— Project ‘:buildsrc’
我们在主工程下,都有一个setting.gradle文件,它的作用是什么呢,它就是告诉Gradle构建系统,这个主工程一共有多少个子工程需要编译。例如,AndroidStudy项目下的setting.gradle内容是这样的:
include ':app', ':buildsrc'
Task
每一个Project在构建的时候都包含一系列的Task。比如一个Android APK的编译可能包含:Java源码编译Task、资源编译Task、JNI编译Task、lint检查Task、打包生成APK的Task、签名Task等。
我们可以在命令行输入:
gradle tasks
去查看项目下有多少Task,得到的结果就像这样:
Android tasks
-------------
androidDependencies - Displays the Android dependencies of the project.
signingReport - Displays the signing info for each variant.
sourceSets - Prints out all the source sets defined in this project.
Build tasks
-----------
assemble - Assembles all variants of all applications and secondary packages.
assembleAndroidTest - Assembles all the Test applications.
assembleDebug - Assembles all Debug builds.
assembleRelease - Assembles all Release builds.
build - Assembles and tests this project.
buildDependents - Assembles and tests this project and all projects that depend on it.
buildNeeded - Assembles and tests this project and all projects it depends on.
classes - Assembles main classes.
clean - Deletes the build directory.
cleanBuildCache - Deletes the build cache directory.
compileDebugAndroidTestSources
compileDebugSources
compileDebugUnitTestSources
compileReleaseSources
compileReleaseUnitTestSources
jar - Assembles a jar archive containing the main classes.
mockableAndroidJar - Creates a version of android.jar that's suitable for unit tests.
testClasses - Assembles test classes.
Build Setup tasks
-----------------
init - Initializes a new Gradle build.
wrapper - Generates Gradle wrapper files.
CreateActivity tasks
--------------------
createActivity
……
显然Task还是有分组的。你当然也可以点击AS右上角的按钮来查看Task:
我们常用的Task有哪些呢,比如clean,build。我们执行对应Task,其实只要在命令中进行输入:
gradle clean
gradle build
和你点击AS上对应的按钮是一样的效果。
自定义Task
例1:
task originalInputs {
doLast {
file('inputs').mkdir()
file('inputs/1.txt').text = "Content for file 1."
file('inputs/2.txt').text = "Content for file 2."
file('inputs/3.txt').text = "Content for file 3."
}
}
这里是一个自定义Task,叫originalInputs,在这个Task执行的最后,会执行一些文件创建操作。
- 一个Task包含若干Action。所以,Task有doFirst和doLast两个函数,用于添加需要最先执行的Action和需要和需要最后执行的Action。Action就是一个闭包。
例2:
task copyTaskWithPatterns(type: Copy) {
from 'A'
into 'B'
include '**/*.html'
include '**/*.jsp'
exclude { details ->
details.file.name.endsWith('.html') &&
details.file.text.contains('staging')
}
}
这里的自定义Task叫copyTaskWithPatterns,我们可以理解成创建了一个Copy Task的对象,并指定了一些参数。这里的操作就是,从A文件拷贝一些东西到B文件,并且对拷贝的内容进行了过滤。
- Task创建的时候可以指定Type,通过
type:名字
的形式。这是什么意思呢?其实就是告诉Gradle,这个新建的Task对象会从哪个基类Task派生。比如,Gradle本身提供了一些通用的Task,最常见的有Copy Task。Copy是Gradle中的一个类。当执行task copyTaskWithPatterns(type: Copy)
的时候,创建的Task就是一个Copy Task。
例3:
class HelloTask extends DefaultTask {
String nameOfPerson = "David"
@TaskAction
void hello() {
println "Hello, $nameOfPerson !"
}
}
如上,我们也可以在.java或者.groovy文件中声明一个Task,但是我们并不能直接调用这个Task。还是要在build.gradle中创建它的一个对象:
task helloMike(type: com.example.tsnt.task.HelloTask) {
nameOfPerson = "Mike"
}
最后执行helloMike这个Task,控制台会输出:
Hello, Mike !
Plugin
一个Project到底包含多少个Task,其实是由编译脚本指定的插件决定。插件是什么呢?插件就是用来定义Task,并具体执行这些Task的东西。
Gradle是一个框架,作为框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。比如编译Java有Java插件,编译Groovy有Groovy插件,编译Android APP有Android APP插件,编译Android Library有Android Library插件。
加载插件就像这样:
apply plugin: 'com.android.application'
或者:
apply plugin: 'groovy'
再或者:
apply plugin: 'java'
自定义Plugin
首先需要实现Plugin接口,然后重写apply()方法,如下:
class FirstPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
System.out.println("====================");
System.out.println("Hello, FirstPlugin!");
System.out.println("====================");
project.tasks.create('ballTask') {
println("ballTask in configuration!")
doLast {
println 'ballTask in execution!'
}
}
}
}
apply()会在我们加载插件的时候被调用,传入的参数project就代表加载该插件的Project。
这里我自定义了一个叫FirstPlugin的插件,我在apply()里做的操作很简单,打印了3行文字,然后动态地创建了一个Task叫ballTask。
我刚才提到传入的参数project就代表加载该插件的Project,那我们就能拿到这个Project对象中的属性。
再举个例子:
// 首先我创建了一个类GreetingPluginExtension,定义了两个属性。
class GreetingPluginExtension {
String message
String greeter
}
// 然后我创建了一个插件GreetingPlugin
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
// 我拿到当前Project中的greeting,创建一个GreetingPluginExtension对象extension,用来保存greeting中的值
def extension = project.extensions.create('greeting', GreetingPluginExtension)
// 然后我动态地去创建一个叫hello的Task
project.task('hello') {
doLast {
// 在Task执行的最后阶段,去打印extension中保存的greeting中取得的值
println "${extension.message} from ${extension.greeter}"
}
}
}
}
// 最后来看一下我在build.gradle中定义的greeting
greeting {
message = 'Hi'
greeter = 'Gradle'
}
然后执行hello这个Task,会发现控制台输出:
Hi from Gradle
应该和你脑海中的结果一样把。
Gradle的生命周期
一共有三个阶段:
初始化阶段:Gradle构建系统会根据setting.gradle中的内容,知道有哪些Project会需要编译,并且为每个Project创建一个Project对象。
配置阶段:在这个阶段,Gradle构建系统会执行每个加入编译的Project下的build.gradle脚本。
执行阶段:根据你所要执行的Task的依赖和参数,Gradle构建系统就会将这个Task链上的所有Task全部按依赖顺序执行一遍。
如下是我的一个gradle脚本中的部分内容,帮助你理解Gradle的生命周期:
println 'This is executed during the configuration phase.'
task taskInConfiguration {
println 'This is also executed during the configuration phase.'
}
task taskInExecution {
doLast {
println 'This is executed during the execution phase.'
}
}
task taskInBoth {
// 在运行阶段, 最先执行
doFirst {
println 'This is executed first during the execution phase.'
}
// 在运行阶段, 最后执行
doLast {
println 'This is executed last during the execution phase.'
}
// 配置阶段执行
println 'This is executed during the configuration phase as well.'
}
我去执行taskInBoth,最后执行结果如下:
Configure project :buildsrc
This is executed during the configuration phase.
This is also executed during the configuration phase.
This is executed during the configuration phase as well.
Task :buildsrc:taskInBoth
This is executed first during the execution phase.
This is executed last during the execution phase.
提示:
可以去gradle目录下的samples目录下查看官方给出的Task和Plugin的例子。
定义Plugin或者Task可以选择直接用Java,也可以选择用Groovy,记得Groovy是Java的扩展。
文中所举的例子,可以在AndroidStudy中看到。
参考: