您的位置:首页 > 移动开发 > Android开发

Android Gradle学习记录5 Gradle补充说明

2017-09-06 20:14 309 查看
前面的博客已经介绍了Groovy的基本用法及Gradle的工作流程。

通常情况下,我们只需要在当前的编译框架中,增添一些task及函数。

在具备Java基础的前提下,了解Groovy的基本语法后,

完成这部分工作应该还是比较轻松的。

至于解决更复杂的问题,可能就需要进一步学习一些使用实例,

并查询对应的API文档了。

考虑到Gradle整体的知识体系非常庞杂,我暂时也没有能力完全掌握和驾驭,

因此就在这篇文档中补充记录一下Gradle比较零碎的知识和使用实例了。

一、Gradle文件编译后生成的对象

通过前面的博客,我们知道Groovy脚本在编译时,会将脚本的内容转换成Java对象。

Gradle实际上是基于Groovy的,因此Gradle文件编译后也会根据文件的类型生成不同的Java对象。

其中,比较常见的是以下三种对象:

1、Gradle对象

当我们在终端中,利用gradle运行Task时,

gradle会从默认的配置脚本中构造出一个Gradle对象。

在脚本的整个执行过程中,只会有一个Gradle对象,它的数据类型就是Gradle。

一般情况下,我们很少会去定制这个默认的配置脚本。

关于Gradle对象的详细信息及其提供的接口,可以查看对应的文档:

https://docs.gradle.org/current/javadoc/org/gradle/api/invocation/Gradle.html

2、Project对象

在编译时,每一个build.gradle文件都会转化成一个Project对象。

Project对象对应的API文档如下:

https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html

对于Project对象,我们使用的比较多的是加载插件的接口apply。

根据API文档,我们可以看出Project的apply函数,继承自org.gradle.api.plugins.PluginAware。

对应的API文档如下:

https://docs.gradle.org/current/javadoc/org/gradle/api/plugins/PluginAware.html

对应的描述如下图所示:



我们来看看apply具体的接口定义:



结合上图,我们回忆一下apply通常的用法:

//以下apply调用的是上图中最后一个apply函数
//函数调用时通过 参数名1:参数值1, 参数名2:参数值2 的方式传递参数
//这些参数在传递时,实际上已经被转化为Map类型

apply plugin: 'com.android.library'

//除了加载gradle文件外,还能直接加载gradle文件
apply from: rootProject.getRootDir().getAbsolutePath() + '/SDKConfig/checkCode.gradle'


这里之所以着重介绍一下apply接口,主要是想借此来让大家感受下Gradle这个编程框架。

每一行代码,虽然看起来有点像简单的配置或引用,但实际上却是在调用一个实际的接口。

此外,通过这个例子,我们应该学会在必要的时候查询API文档。

Gradle大量的术语及类的介绍,都可以在以下地址找到:

https://docs.gradle.org/current/dsl/

https://docs.gradle.org/current/javadoc/allclasses-noframe.html

3、Settings对象

在编译时,每一个settings.gradle文件都会转化成一个Settings对象。

通常情况下,settings.gradle主要用于指定一个Project包含的子Project,

或定义一些函数,完成初始化时相关的工作。

Settings对象对应的文档地址如下:

https://docs.gradle.org/current/javadoc/org/gradle/api/initialization/Settings.html

4、其它

对于其它的gradle文件,除非定义了class,

否则与普通的Groovy脚本类似,会转换成一个实现了Script接口的对象。

关于整个编译过程中生成对象的顺序,可以参考如下链接:

https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html

其中,最主要的部分如下图所示:



可以看出,脚本编译时:

首先生成整个Project对应Settings对象,并按照setting.gradle的定义配置Settings对象;

然后,依据配置的Settings对象,创建出整个Project对象与其子对象的组织结构。

最后,根据settings.gradle中声明的project对应的build.gradle脚本,创建出对应的子Project对象。

由于每个子project都可能有自己的settings.gradle文件,因此还需要继续建立每个子Project的体系结构。

整体而言,这个创建体系结构的过程,是按照广度优先的顺序递归进行的。

二、Gradle中定义额外属性

1、使用方式

Gradle提供了一种名为extra property的方法。

extra property是额外属性的意思,

该属性定义的成员或函数可以被多个Project或Task使用。

在第一次定义该属性的时候,需要通过ext前缀来进行标识,

一旦定义好后,再次使用时就不需要ext前缀了。

Project和Gradle对象均可以设置ext属性。

例如,在一个multi project的工程中,在对应的build.gradle中分别配置子project的属性:

//subprojectA和subprojectB均为multi project的子project
..............
project(':subprojectA') {
//gradle指的是gradle对象,默认是Settings和Project对象的成员变量
//gradle可以直接通过ext前缀操作外置属性
//这里新建了一个version的外置属性
gradle.ext.version = '1.0'
}

project(':subprojectB') {
//由于version是基于gradle对象的外置属性
//同时整个工程只有一个gradle对象
//因此subprojectB中也可以获取该对象的成员
//再次使用时,不需要再使用ext前缀了
println gradle.version
}
.............


我们在看看其它的用途,比如我们想定义一些工具函数,例如:

//在工具函数对应的文件,例如buildUtil.gradle中,
//将定义的函数添加到ext闭包中
//对应的函数就会成为额外属性的一部分
//当其他的Project利用apply from添加buildUtil.gradle时,
//就可以使用这些函数了
ext {
getVersionName = this.&getVersionName
}

def getVersionName() {
def xmlFile = project.file("AndroidManifest.xml")
................
}


此外,利用命令启动编译时,也可以指定额外属性,例如:

//这里为整个工程增加了额外属性packageName,对应的值为test
//这样就实现了将参数传递到编译脚本的工作
./gradlew clean buildSdk -PpackageName=test


2、工具函数的执行特点

至此,我们对extra property有了一定的了解,

其最重要的功能应该还是定义一些工具函数。

提到工具函数,就不得不提一下工具函数执行时的特点。

从上文可以看出工具函数getVersionName中,使用了project对象。

我们知道工具函数定义于buildUtil.gradle文件中,并不归属于任何Project,

那么它使用的project对象到底是什么?

为了搞清楚这个问题,就必须了解函数执行的过程了:

1、当一个 Project apply 一个 gradle 文件的时候, 这个 gradle 文件会转换成一个Script 对象。

2、Script 中有一个 delegate 对象(委托对象), delegate 默认指向加载它的

Project 对象(即调用apply from的Project)。

需要注意的是:

通过 apply to,可以把 delegate 对象指定为别的对象(详情参阅API文档)。

3、当在Script 中操作一些不是 Script 自己定义的变量或者函数时候,

gradle 会到Script的delegate对象去找,看看有没有定义这些变量或函数。

这有点像Android中利用handler处理msg,默认是发送msg的handler处理这个msg,

但设置了callback后,对应的msg将递交给callback处理。

根据上述执行过程,我们知道了:

当其它Project利用apply from加载buildUtil.gradle时,

getVersionName中的project就是加载该文件的Project。

上面的ext就是为对应的Project增加额外属性,

于是Project中的成员就可以使用getVersionName这个函数了。

三、Gradle中的Task

Task是Gradle中的一种数据类型,它代表了一些要执行工作,类似于任务队列中的任务对象。

Project引用不同的插件后,可以添加不同的Task。

每一个Task都需要和一个Project关联。

Project可以管理所有依附自己的Task,例如:。

//查找project中所有名称包含Debug的Task
//返回值保存到targetTasks容器中
def targetTasks = project.tasks.findAll{task ->
task.name.contains("Debug")
}

//对满足条件的task, 设置它为disable, 于是这个Task就不会被执行
targetTasks.each{
println "disable debug task: ${it.name}"
it.setEnabled false
}


Task的API文档如下:

https://docs.gradle.org/current/dsl/org.gradle.api.Task.html

1、创建Task的时候可以指定Type,指定新建的Task对象从哪个基类Task派生。

例如:

task copyClasses(type: Copy) {
.........
}


上述代码就是创建一个Copy Task的子类。

个人觉得,如果新建的Task需要执行的工作,

完全符合某个基类的定义时,才需要指定type,达到简化编码的目的。

即当新建的Task中仅执行copy操作时,才指定type为copy。

如果Task中还有其它的操作,例如调用其它函数,

那么直接继承时,可能由于父类的限制,出现不符合预期的情况。

2、一个Task可包含若干个Action。Action就是一个闭包,用于执行具体的操作。

//花括号是一个闭包。
task myTask {
xxxxxx
}


gradle创建该Task之后,返回给用户之前,

会先执行闭包中的内容。

此外,Task中有doFirst和doLast两个函数,

用于添加需要最先执行的Action和需要最后执行的Action。

整体来讲,task先执行花括号中的内容,

再执行doFirst中的内容,最后执行doLast中的内容。

例如:

task test1() {
doFirst {
println "test1 doFirst"
}

println "test1 closure"

doLast {
println "test1 doLast"
}
}

//test1执行完后,才执行test2
//不过花括号中的内容:println "test2 closure"
//在创建后,返回前就执行了
task test2(dependsOn: test1) {
doFirst {
println "test2 doFirst"
}

println "test2 closure"

doLast {
println "test2 doLast"
}
}


执行顺序如下:

test1 closure
test2 closure
test1 doFirst
test1 doLast
test2 doFirst
test2 doLast


需要注意的是,当使用下列方式时,<<符号代表的是doLast:

task myTask << {
xxx
}


此时,闭包将作为一个action加入到Task的队列中,最后执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: