1 Gradle概述
参考
- Gradle官方文档:https://docs.gradle.org/current/release-notes (UserGuide、DSLReference、Javadoc)
- 《Gradle快速入门》:http://www.cnblogs.com/CloudTeng/p/3417762.html
- 《深入理解Android之Gradle》:http://blog.csdn.net/innost/article/details/48228651
配置环境
- 去gradle官网下载gradle程序的压缩包,解压到硬盘。
- 在解压得到的目录中找到“gradle.bat”文件,将其所在路径(如“E:\gradle-4.2.1\bin”)添加到Windows系统的PATH环境变量中。
基本概念
Gradle是基于Groovy的一种DSL(领域特定语言),也是一个编程框架,它定义了一套自己的游戏规则。我们要玩转Gradle,必须要遵守它定义的规则。
Project:每一个待构建的工程都叫一个Project,具体来说,一个Project的标志就是一个build.gradle文件,也就是说,如果一个目录直接包含一个build.gradle文件,则该目录就是一个Project。
Task:一个Task表示一个逻辑上较为独立的执行过程,比如编译Java源代码,拷贝文件,打包Jar文件等。每一个Project中都包含多个Task,比如一个Android App的构建Project可能包含:Java源码编译Task、资源编译Task、JNI编译Task、lint检查Task、打包生成APK的Task、签名Task等。
Plugin:即插件,用来向Project中添加一系列Task(任务)和Property(属性),以完成特定的构建任务。构建Java工程有Java插件,构建Groovy工程有Groovy插件,构建Android App工程有Android App插件,构建Android Library工程有Android Library插件。
多Project的Gradle项目(一)
rootproject
|----build.gradle
|----settings.gradle
|----subproject1
|----build.gradle
|----subproject2
|----build.gradle
要让subproject1和subproject2成为rootproject的子Project,需要在rootproject目录下创建一个名为settings.gradle的文件,并在其中加入代码:
//通过include函数,将子Project的名字(其文件夹名)包含进来
include ':subproject1', ':subproject2'
settings.gradle除了可以include之外,还可以做一些初始化工作。比如:
def initEnvironment(){
//doSomething
}
//调用initEnvironment()函数进行初始化
initEnvironment()
//其实include也是一个函数
include ':subproject1', ':subproject2'
基本gradle命令
- 执行当前Project及其所有子Project中的名为task_name的Task:
gradle task_name
- 列出当前Project及其所有子Project:
gradle projects
- 列出当前Project及其所有子Project中的全部Task:
gradle tasks
- 列出当前Project及其所有子Project中的全部Property:
gradle properties
projects、tasks、properties其实都是Gradle系统提供的Task。以gradle projects
为例,这条命令其实就是在执行名为projects的Task。
2 Gradle详解
Gradle对象
当我们执行gradle xxx
或其他什么的时候,会自动创建一个Gradle对象。在整个执行过程中,只有这么一个对象。
在settings.gradle、build.gradle中都可以直接获取Gradle对象的引用,就像这样:println "gradle id is " +gradle.hashCode()
Project对象
每一个build.gradle文件就代表一个Project。每个Project中都包含一系列Property(属性)和Task(任务)。
Project的API位于:https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html
加载插件
加载插件调用的是Project实现的PluginAware接口中的apply函数:
//此处调用的是上图中最后一个 apply 函数
apply plugin: 'com.android.library' //如果是编译 Android Library,则加载此插件
apply plugin: 'com.android.application' //如果是编译 Android APP,则加载此插件
除了加载二进制的插件(上面的插件其实都是下载了对应的 jar 包,这也是通常意义上我们所理解的插件),还可以加载一个gradle文件。
为什么要加载gradle文件呢?一般而言,我们会把一些通用的函数放到一个名叫utils.gradle的文件中。然后在各个Project中加载这个utils.gradle。这样,再经过一些处理(后面会说),就可以在各个Project中调用utils.gradle中定义的函数了。
加载utils.gradle的代码如下:
apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
apply方法的详细说明:
Property
Gradle对象、Project对象、Task对象都有属性(Property),而日常使用中我们经常接触的是Project的Property。很多Plugin都会向Project中加入Property,在使用这些Plugin时,我们通常需要对这些Property进行赋值。
Gradle在默认情况下已经为Project定义了很多Property,其中比较常用的有:
project:Project本身
name:Project的名字
path:Project的绝对路径
description:Project的描述信息
buildDir:Project构建结果存放目录
version:Project的版本号
举个例子,我们首先设置Project的version和description属性,再定义一个Task来打印这些属性。具体来说就是在build.gradle中加入如下代码:
version = 'this is the project version'
description = 'this is the project description'
task showProjectProperties << {
println version
println project.description
}
然后就可以在命令行窗口中通过gradle showProjectProperties
来执行这个Task了。
需要注意的是,在打印description时,我们使用了project.description,而不是直接使用description。原因在于Project和Task中都有description属性,如果在Task中使用description而不加前缀,那么会被认为是Task的description属性。
额外属性
可以给Gradle对象、Project对象、Task对象设置额外属性(extra property)。定义额外属性需要使用ext关键字,定义好之后,后面再存取额外属性就不需要ext关键字了。
以Project为例,我们为其定义一个名为prop的额外属性,只需在build.gradle中加入如下代码:
ext.prop = "this is property1"
也可以通过闭包的方式:
ext {
prop = "this is property2"
}
在定义了Property之后,使用时就不需要写ext了:
task showProperties << {
println prop
}
额外属性有什么用呢?举个例子:我们在settings.gradle中为Gradle对象设置一些额外属性:
include 'subproject1','subproject2'
//设置一个名为hello的额外属性
gradle.ext.hello = 'hello world'
因为在每个Project中都可以获取到Gradle对象,因此,我们完全可以把Gradle对象的额外属性当做一个全局变量来使用。例如我们在subproject1的build.gradle中定义了这样一个Task:
task sayHello << {
println gradle.hello
}
在命令行执行gradle sayHello
,可以发现正确打印出了hello world。
再来一个例子强化一下。我们在utils.gradle中定义了一些函数,然后想在各个Project中调用这些函数。那该怎么做呢?
//utils.gradle中定义了一个获取AndroidManifests.xml中versionName的函数
def getVersionNameAdvanced(){
//project指的是加载此utils.gradle的Project对象
def xmlFile = project.file("AndroidManifest.xml")
def rootManifest = new XmlSlurper().parse(xmlFile)
return rootManifest['@android:versionName']
}
//我们可以把getVersionNameAdvanced函数赋值给一个外部属性
//然后在想要调用getVersionNameAdvanced函数的Project中apply此utils.gradle
//此ext是谁的ext?——是加载此utils.gradle的Project对象的ext
ext{
//除了 ext.xxx=value 这种写法之外,还可以使用 ext{} 这种写法
getVersionNameAdvanced = this.&getVersionNameAdvanced
}
Task
Task 的 API 文档位于:https://docs.gradle.org/current/dsl/org.gradle.api.Task.html
定义Task
我们可以通过多种方式为Project定义Task,所有的Task都会被存放在Project的TaskContainer中。
(1)调用Project的task()方法创建Task
这是创建Task最常见的方式:
task hello1 << {
println 'hello1'
}
这里的“<<”表示追加的意思,即向hello1中加入执行过程。我们还可以使用doLast来达到同样的效果:
task hello2 {
doLast {
println 'hello2'
}
}
另外,如果需要向Task的最前面加入执行过程,我们可以使用doFirst:
task hello3 {
doFirst {
println 'hello3'
}
}
以上我们自定义的3个Task都位于TaskContainer中,Project中的tasks属性即表示该TaskContainer。
(2)通过TaskContainer的create()方法创建Task
tasks.create(name: 'hello4') << {
println 'hello4'
}
(3)创建Task时指定Task的类型
在创建Task时,我们可以声明该Task的类型。如果不声明其类型,那么它的类型就是DefaultTask。
task copyFile(type: Copy) {
from 'xml'
into 'destination'
}
以上copyFile将xml文件夹中的所有内容拷贝到destination文件夹中,其中xml文件夹与destination文件夹与当前Project(即build.gradle文件)位于同一目录下。
注意:对于我们创建的每一个Task,Gradle都会在Project中创建一个同名的Property,所以我们可以将该Task当作Property来访问。
声明Task间的依赖关系
Task之间可以存在依赖关系。如果TaskA依赖TaskB,那么在执行TaskA时,Gradle会先执行TaskB,再执行TaskA。
可以在定义一个Task的同时声明它的依赖关系:
task hello5(dependsOn:hello4) << {
println 'hello5'
}
也可以在定义Task之后再单独声明依赖:
task hello6 << {
println 'hello6'
}
hello6.dependsOn hello5
配置Task
一个Task除了执行操作之外,还可以包含多个Property,其中有Gradle为每个Task默认定义的Property,比如description,logger等。另外,每一个Task类型通常还含有自己的特有Property,比如Copy的from和to等。此外,我们还可以动态地向Task中添加额外Property。
在执行一个Task之前,我们通常需要先设定其Property值。Gradle提供了多种方法设置Task的Property值。
(1)在定义Task的时候配置其Property:
task hello7 << {
description = "this is hello7"
println description
}
(2)通过同名属性来配置Task的Property:
前面已经说过,当我们创建一个Task的时候,Gradle会自动在Project中创建一个同名的Property。
hello7.description = "this is hello7"
(3)通过闭包的方式来配置一个已有的Task:
当我们创建一个Task的时候,Gradle除了会创建一个同名的Property,还会创建一个同名的方法,我们可以通过调用这个方法并传入一个闭包来对Task的Property进行配置:
task hello8 << {
println description
}
hello8 {
description = "this is hello8"
}
需要注意的是,Gradle在执行Task时分为两个阶段,首先是配置阶段,然后才是实际执行阶段。也就是说在执行hello8之前,Gradle会扫描整个build.gradle文档,将hello8的description设置为“this is hello8”,然后执行hello8。
(4)通过Task的configure()方法设置Property:
task hello9 << {
println description
}
hello9.configure {
description = "this is hello9"
}
多Project的Gradle项目(二)
对于上面所提到的多Project的Gradle项目:
rootproject
|----build.gradle
|----settings.gradle
|----subproject1
|----build.gradle
|----subproject2
|----build.gradle
如果我们想要定义Task来显示每个Project各自的名称。我们可以在每个build.gradle中进行定义,但这是一种比较笨的方法,此时我们也完全没有享受到Gradle的多Project构建功能所带来的好处。在Gradle中,我们可以通过根Project的allprojects()方法将配置一次性地应用于所有的Project,当然也包括定义Task。比如,在rootproject的build.gradle中,我们可以做以下定义:
allprojects {
task getnames << {
println project.name
}
}
在rootproject目录下执行gradle getnames
,输出如下:
> Task :getnames
rootproject
> Task :subproject1:getnames
subproject1
> Task :subproject2:getnames
subproject2
可见rootproject、subproject1、subproject2中都有了一个名为getnames的Task。
除了allprojects()之外,Project还提供了subprojects()方法用于配置所有的子Project(不包含根Project)。
一旦有了多个Project,他们之间便会存在着依赖关系。Gradle的Project之间的依赖关系是基于Task的,而不是整个Project的。
现在,让我们来看一个Project依赖的例子。比如subproject1中有taskA和taskB,在subproject2中有taskC和taskD,taskA依赖taskB、taskC,taskB依赖taskD:
//subproject1中的build.gradle
task taskA << {
println 'this is taskA from project 1'
}
task taskB << {
println 'this is taskB from project 1'
}
taskA.dependsOn taskB
taskA.dependsOn ':subproject2:taskC'
taskB.dependsOn ':subproject2:taskD'
//subproject2中的build.gradle
task taskC << {
println 'this is taskC from project 2'
}
task taskD << {
println 'this is taskD from project 2'
}
此时执行gradle taskA
,输出如下:
:subproject2:taskD
this is taskD from project 2
:subproject1:taskB
this is taskB from project 1
:subproject2:taskC
this is taskC from project 2
:subproject1:taskA
this is taskA from project 1
Gradle工作流程
当我们执行一个gradle xxx
命令时,会经历以下阶段:
3 Gradle高级
自定义Task类型
我们以定义一个简单的HelloWorldTask为例,讲解如何自定义一个Task类型,并且如何对其进行配置。
(一)在build.gradle文件中直接定义
我们知道,Gradle其实就是groovy代码,所以在build.gradle文件中,我们便可以定义Task类:
//定义HelloWorldTask
class HelloWorldTask extends DefaultTask {
@Optional
String message = 'I am davenkin'
@TaskAction
def hello(){
println "hello world $message"
}
}
//使用HelloWorldTask类型来定义Task
task hello(type:HelloWorldTask)
task hello1(type:HelloWorldTask){
message ="I am a programmer"
}
在上例中,我们定义了一个名为HelloWorldTask的Task,它需要继承自DefaultTask,它的作用是向命令行输出一个字符串。@TaskAction表示该Task要执行的动作,即在调用该Task时,hello()方法将被执行。另外,message被标记为@Optional,表示在配置该Task时,message是可选的。在定义好HelloWorldTask后,我们创建了两个Task实例,第一个hello使用了默认的message值,而第二个hello1在创建时重新设置了message的值。
执行hello时,输出hello world I am davenkin
;执行hello1时,输出hello world I am a programmer
。
(二)在当前工程中定义Task类型
(三)在单独的项目中定义Task类型
自定义插件
在Plugin中,我们可以向Project中加入新的Task和Property等。
举个例子,创建一个DateAndTimePlugin,该Plugin定义了2个Task,分别用于输出系统当前的日期和时间,另外,我们可以配置日期和时间的输出格式。
(一)在build.gradle文件中直接定义Plugin
和在build.gradle文件中定义Task类型一样,我们可以将对Plugin的定义直接写在build.gradle中:
定义插件:
class DateAndTimePlugin implements Plugin<Project> {
void apply(Project project) {
//向project添加extension
project.extensions.create("dateAndTime", DateAndTimePluginExtension)
//向project添加Task
project.task('showTime') << {
println "Current time is " + new Date().format(project.dateAndTime.timeFormat)
}
//向project添加Task
project.tasks.create('showDate') << {
println "Current date is " + new Date().format(project.dateAndTime.dateFormat)
}
}
}
class DateAndTimePluginExtension {
String timeFormat = "MM/dd/yyyyHH:mm:ss.SSS"
String dateFormat = "yyyyMMdd"
}
使用插件并进行配置:
apply plugin: DateAndTimePlugin
dateAndTime {
timeFormat = 'HH:mm:ss.SSS'
dateFormat = 'MM/dd/yyyy'
}
然后就可以使用showTime和showDate这两个Task了。
每一个自定义的Plugin都需要实现Plugin接口。该接口定义了一个apply()方法,在该方法中,我们可以操作Project,比如向其中加入Task,定义额外的Property等。
在上例中,我们在DateAndTimePlugin中向Project添加了2个Task,一个名为showTime,一个名为showDate。
每个Gradle的Project都维护了一个ExtenionContainer,我们可以通过project.extentions进行访问,比如读取额外的Property和定义额外的Property等。在DateAndTimePlugin中,我们向Project中定义了一个名为dateAndTime的extension,并向其中加入了2个Property,分别为timeFormat和dateFormat。它们分别用于showTime和showDate。在使用该Plugin时,我们可以通过以下方式对这两个Property进行重新配置:
dateAndTime {
timeFormat = 'HH:mm:ss.SSS'
dateFormat = 'MM/dd/yyyy'
}
(二)在当前工程中定义Plugin
(三)在单独的项目中创建Plugin