Gradle详解(二)——Gradle

1 Gradle概述

参考

配置环境

  1. 去gradle官网下载gradle程序的压缩包,解压到硬盘。
  2. 在解压得到的目录中找到“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项目(一)

root­project
    |----build.gradle
    |----settings.gradle
    |----sub­project1
              |----build.gradle
    |----sub­project2
              |----build.gradle

要让sub­project1和sub­project2成为root­project的子Project,需要在root­project目录下创建一个名为settings.gradle的文件,并在其中加入代码:

//通过include函数,将子Project的名字(其文件夹名)包含进来
include ':sub­project1', ':sub­project2'

settings.gradle除了可以include之外,还可以做一些初始化工作。比如:

def initEnvironment(){
    //doSomething
}

//调用initEnvironment()函数进行初始化
initEnvironment()

//其实include也是一个函数
include ':sub­project1', ':sub­project2'

基本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项目:

root­project
    |----build.gradle
    |----settings.gradle
    |----sub­project1
              |----build.gradle
    |----sub­project2
              |----build.gradle

如果我们想要定义Task来显示每个Project各自的名称。我们可以在每个build.gradle中进行定义,但这是一种比较笨的方法,此时我们也完全没有享受到Gradle的多Project构建功能所带来的好处。在Gradle中,我们可以通过根Project的allprojects()方法将配置一次性地应用于所有的Project,当然也包括定义Task。比如,在root­project的build.gradle中,我们可以做以下定义:

allprojects {
	task getnames << {
		println project.name
	}
}

在root­project目录下执行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依赖的例子。比如sub­project1中有taskA和taskB,在sub­project2中有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 ':sub­project2:taskC'
taskB.dependsOn ':sub­project2:taskD'
//subproject2中的build.gradle

task taskC << {
	println 'this is taskC from project 2'
} 

task taskD << {
	println 'this is taskD from project 2'
}

此时执行gradle taskA,输出如下:

:sub­project2:taskD
this is taskD from project 2
:sub­project1:taskB
this is taskB from project 1
:sub­project2:taskC
this is taskC from project 2
:sub­project1: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 = "yyyy­MM­dd"
}

使用插件并进行配置:

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

发布了46 篇原创文章 · 获赞 38 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/al4fun/article/details/78386046