Gradle 介绍三

来源:互联网 发布:香港淘宝主播培训 编辑:程序博客网 时间:2024/05/08 15:08

六、Gradle 详解

现在正式进入 Gradle。Gradle 是一个工具,同时它也是一个编程框架。前
面也提到过,使用这个工具可以完成 app 的编译打包等工作。当然你也可以用
它干其他的事情。

6.1 Gradle 基本组件

Gradle 是一个框架,它定义一套自己的游戏规则。我们要玩转 Gradle,必
须要遵守它设计的规则。下面我们来讲讲 Gradle 的基本组件:
Gradle 中,每一个待编译的工程都叫一个 Project。每一个 Project 在构建
的时候都包含一系列的 Task。比如一个 Android APK 的编译可能包含:
Java 源码编译 Task、
资源编译 Task、
JNI 编译 Task、
lint 检查 Task、
打包生成APK 的 Task、
签名 Task 等。

一个 Project 到底包含多少个 Task,其实是由编译脚本指定的插件决定。插件是什么呢?插件就是用来定义 Task,并具体执行这些 Task 的东西。刚才说了,Gradle 是一个框架,作为框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。比如编译 Java 有 Java 插件,编译Groovy 有 Groovy 插件,编译 Android APP 有 Android APP 插件,编译Android Library 有 Android Library 插件。好了,到现在为止,你知道 Gradle中每一个待编译的工程都是一个 Project,一个具体的编译过程是由一个一个的Task 来定义和执行的。

6.2 一个重要的例子

下面我们来看一个实际的例子。这个例子非常有代表意义。
假设有一个名为 hello5.0 的目录。这个目录里包含 1 个 Android Library 工程,
3 个 Android APP 工程,结构如下:

1 个 Android Library:mylibrary
3 个 Android APP:animation, recyclerviewdemo, testmodel
这 3 个 App 和 SDK 有依赖关系。3 个 App 都依赖 mylibrary

请回答问题,在上面这个例子中,有多少个 Project?

答案是:每一个 Library 和每一个 App 都是单独的 Project(这里的 project 理
解为 module)。根据 Gradle 的要求,每一个 Project 在其根目录下都需要有
一个 build.gradle。build.gradle 文件就是该 Project 的编译脚本,类似于
Makefile。

看起来好像很简单,但是请注意:hello5.0 虽然包含 4 个独立的 Project,
但是要独立编译他们的话,得:
cd 某个 Project 的目录。比如 cd animation
然后执行 gradle xxxx(xxx 是任务的名字。对 Android 来说,assemble
这个 Task 会生成最终的产物,所以 gradle assemble)
这很麻烦啊, 10 个独立 Project,就得重复执行 10 次这样的命令。更有甚者,
所谓的独立 Project 其实有依赖关系的。比如我们这个例子。那么,我想在
hello5.0 目录下,直接执行 gradle assemble,是否能把这 4 个 Project 的东西
都编译出来呢?

答案自然是可以。在 Gradle 中,这叫 Multi-Projects Build。把 hello5.0
改造成支持 Gradle 的 Multi-Projects Build 很容易,需要:

(1)在 hello5.0 下也添加一个 build.gradle。
这个 build.gradle 一般干得活是:
配置其他子 Project 的。比如为子 Project 添加一些属性。这个 build.gradle 有
没有都无所属。

(2)在 hello5.0 下添加一个名为 settings.gradle。这个文件很重要,名字必
须是 settings.gradle。它里边用来告诉 Gradle,这个 multiprojects 包含多少
个子 Project。
来看 settings.gradle 的内容,最关键的内容就是告诉 Gradle 这个 multiprojects
包含哪些子 projects:

[settings.gradle]
//通过 include 函数,将子 Project 的名字(其文件夹名)包含进来
include ‘:animation’, ‘:recyclerviewdemo’, ‘:testmodel’, ‘:mylibrary’

强烈建议:
如果你确实只有一个 Project 需要编译,我也建议你在目录下添加一个
settings.gradle。把单个 Project 改成支持 Multiple-Project Build。改得方法
就是添加 settings.gradle,然后 include 对应的 project 名字。
另外,settings.gradle 除了可以 include 外,还可以设置一些函数。这些函数
会在 gradle 构建整个工程任务的时候执行,所以,可以在 settings 做一些初始
化的工作。如下 settings.gradle 的内容:

//定义一个名为 initMinshengGradleEnvironment 的函数。
该函数内部完成一
//些初始化操作,比如创建特定的目录,设置特定的参数等
def initMinshengGradleEnvironment(){
println”initialize Minsheng Gradle Environment …..”
//干一些 special 的私活
println”initialize Minsheng Gradle Environment completes…”
}

//settings.gradle 加载的时候,会执行 initMinshengGradleEnvironment
initMinshengGradleEnvironment()
//include 也是一个函数:
include ‘:animation’, ‘:recyclerviewdemo’, ‘:testmodel’, ‘:mylibrary’

6.3 gradle 命令介绍

(1) gradle projects 查看工程信息
到目前为止,我们了解了 Gradle 什么呢?

1) 每一个 Project 都必须设置一个 build.gradle 文件。至于其内容,我们留到
后面再说。

2) 对于 multi-projects build,需要在根目录下也放一个 build.gradle,和一
个 settings.gradle。

3) 一个 Project 是由若干 tasks 来组成的,当 gradle xxx 的时候,实际上是要
求 gradle 执行 xxx 任务。这个任务就能完成具体的工作。

4) 当然,具体的工作和不同的插件有关系。编译 Java 要使用 Java 插件,编译
Android APP 需要使用 Android APP 插件。这些我们都留待后续讨论。

gradle 提供一些方便命令来查看和 Project,Task 相关的信息。比如在
hello5.0,我想看这个 multi projects 到底包含多少个子 Project:
执行 gradle projects,得到如下图:
这里写图片描述

你看,multi projects 的情况下,hello5.0 这个目录对应的 build.gradle 叫 Root
Project,它包含 4 个子 Project。
如果你修改 settings.gradle,去掉 animation app,则 gradle projects 的子
project 也会变少,如下图:
这里写图片描述

(2) gradle tasks 查看任务信息
查看了 Project 信息,这个还比较简单,直接看 settings.gradle 也知道。
那么 Project 包含哪些 Task 信息,怎么看呢?想看某个 Project 包含哪些 Task
信息,只要执行:gradle :tasks 就行,注意:project-path 是
目录名,后面必须跟冒号。

对于 Multi-project,在根目录中,需要指定你想看哪个 poject 的任务。 如
gradle testmodel:tasks,不过你要是已经 cd 到某个 Project 的目录了,则不需指定
Project-path。gradle testmodel:tasks 结果如下图:

这里写图片描述

cd testmodel
gradle tasks 得到同样的结果

Android Library 工程对应的插件定义了好多 Task。每种插件定义的 Task 都不
尽相同,这就是所谓的 Domain Specific,需要我们对相关领域有比较多的了解。

(3) gradle task-name 执行任务
上图中列出了好多任务,
这时候就可以通过 gradle 任务名来执行某个任务。这和 make xxx 很像。比如:
gradle clean 是执行清理任务,和 make clean 类似。
gradle properites 用来查看所有属性信息。
gradle tasks 会列出每个任务的描述,通过描述,我们大概能知道这些任务是干什么的。然后 gradle task-name 执行它就好。

强调一点:Task 和 Task 之间往往是有关系的,这就是所谓的依赖关系。比如,
assemble task 就依赖其他 task 先执行,assemble 才能完成最终的输出。
依赖关系对我们使用 gradle 有什么意义呢?

如果知道 Task 之间的依赖关系,那么开发者就可以添加一些定制化的 Task。
比如我为 assemble 添加一个 SpecialTest 任务,并指定 assemble 依赖于
SpecialTest。当 assemble 执行的时候,就会先处理完它依赖的 task。自然,
SpecialTest 就会得到执行了。

6.4 Gradle 工作流程

Gradle 的工作流程其实蛮简单,用一个图来表达:
这里写图片描述

上图告诉我们,Gradle 工作包含三个阶段:

1) 首先是初始化阶段。对我们前面的 multi-project build 而言,就是执行
settings.gradle

2) Initiliazation phase 的下一个阶段是 Configration 阶段。
Configration 阶段的目标是解析每个 project 中的 build.gradle。比如
multi-project build 例子中,解析每个子目录中的 build.gradle。在这两个阶
段之间,我们可以加一些定制化的 Hook。这当然是通过 API 来添加的。
Configuration 阶段完了后,整个 build 的 project 以及内部的 Task 关系就确
定了。前面说过,一个 Project 包含很多 Task,每个 Task 之间有依赖关系。
Configuration 会建立一个有向图来描述 Task 之间的依赖关系。所以,我们可
以添加一个 HOOK,即当 Task 关系图建立好后,执行一些操作。

3) 最后一个阶段就是执行任务了。当然,任务执行完后,我们还可以加 Hook。
好了,Hook 的代码怎么写,估计你很好奇,马上了!

最后,关于 Gradle 的工作流程,你只要记住:
Gradle 有一个初始化流程,这个时候 settings.gradle 会执行。
在配置阶段,每个 Project 都会被解析,其内部的任务也会被添加到一个有
向图里,用于解决执行过程中的依赖关系。

然后才是执行阶段。你在 gradle xxx 中指定什么任务,gradle 就会将这个
xxx 任务链上的所有任务全部按依赖顺序执行一遍!

下面来告诉你怎么写代码!
6.5 Gradle 编程模型及 API 实例详解

API 文档:https://docs.gradle.org/current/dsl/
Gradle 基于 Groovy,Groovy 又基于 Java。所以,Gradle 执行的时候和
Groovy 一样,会把脚本转换成 Java 对象。Gradle 主要有三种对象,这三种对
象和三种不同的脚本文件对应,在 gradle 执行的时候,会将脚本转换成对应的
对象:
1) Gradle 对象:当我们执行 gradle xxx 或者什么的时候,gradle 会从默认的
配置脚本中构造出一个 Gradle 对象。在整个执行过程中,只有这么一个对
象。Gradle 对象的数据类型就是 Gradle。我们一般很少去定制这个默认的
配置脚本。

2) Project 对象:每一个 build.gradle 会转换成一个 Project 对象。

3) Settings 对象:显然,每一个 settings.gradle 都会转换成一个 Settings 对
象。

对于其他 gradle 文件,除非定义了 class,否则会转换成一个实现了 Script 接
口的对象。

Project 生命周期:
当我们执行 gradle 的时候,gradle 首先是按顺序解析各个 gradle 文件。
这里边就有所谓的生命周期的问题,即先解析谁,后解析谁。下图是 Gradle 文
档中对生命周期的介绍:结合上一节的内容,相信大家都能看明白了。现在只需
要看红框里的内容:
这里写图片描述

6.5.1 Gradle 对象
我们先来看 Gradle 对象,它有哪些属性呢?如图所示:

这里写图片描述

我在 hello5.0 build.gradle 中和 settings.gradle 中分别加了如下输出:
//在 settings.gradle 中,则输出”In hello5.0 settings.gradle,gradle id is”
println “In hello5.0 build.gradle, gradle id is ” +gradle.hashCode()
println “Home Dir:” + gradle.gradleHomeDir
println “User Home Dir:” + gradle.gradleUserHomeDir
println “Parent: ” + gradle.parent
得到结果如下图所示:
这里写图片描述

你看, 在 settings.gradle 和 posdevice build.gradle 中,我们得到的 gradle实例对象的 hashCode 是一样的(都是 636959006)。
HomeDir: 是我在哪个目录存储的 gradle 可执行程序。
User Home Dir: 是 gradle 自己设置的目录,里边存储了一些配置文件,
以及编译过程中的缓存文件,生成的类文件,编译中依赖的插件等等。
Gradle 的函数接口在文档中也有。

6.5.2 Project 对象

每一个 build.gradle 文件都会转换成一个 Project 对象。 Gradle 术语中,
在Project 对象对应的是 BuildScript。Project 包含若干 Tasks。另外,由于 Project
对应具体的工程,所以需要为 Project 加载所需要的插件,比如为 Java 工程加
载 Java 插件。其实,一个 Project 包含多少 Task 往往是插件决定的。所以,在
Project 中,我们要:
a、加载插件。不同插件有不同的行话,即不同的配置。我们要在 Project 中配置好,这样插件就知道从哪里读取源文件等

b、设置属性。

(1)加载插件
Project 的 API 位于
https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html
加载插件是调用它的 apply 函数.apply 其实是 Project 实现的 PluginAware 接
口定义的:

这里写图片描述

来看代码:[apply 函数的用法]
apply 是一个函数,此处调用的是上图中第二个 apply 函数。Groovy 支持函数
调用的时候通过
参数名 1:参数值 2,参数名 2:参数值 2 的方式来传递参数。
apply plugin: ‘com.android.library’ <==如果是编译 Library,则加载此插件
apply plugin: ‘com.android.application’ <==如果是编译 Android APP,则
加载此插件。

除了加载二进制的插件(上面的插件其实都是下载了对应的 jar 包,这也是
通常意义上我们所理解的插件),还可以加载一个 gradle 文件。为什么要加载
gradle 文件呢?

其实这和代码的模块划分有关。一般而言,
我会把一些通用的函数放到一个
名叫 utils.gradle(utils.gradle 是我封装的一个 gradle 脚本,里边定义了一些
方便函数,比如读取 AndroidManifest.xml 中的 versionName,或者是 copy
jar 包/APK 包到指定的目录)文件里。然后在其他工程的 build.gradle 来加载
这个 utils.gradle。这样,通过一些处理,我就可以调用 utils.gradle 中定义的
函数了。

//加载 utils.gradle 插件的代码如下:
apply from: rootProject.getRootDir().getAbsolutePath() + “/utils.gradle”
也是使用 apply 的第二个函数。我这里不遗余力的列出 API,
就是希望大家在写
脚本的时候,碰到不会的,一定要去查看 API 文档!

(2) 设置属性
如果是单个脚本,则不需要考虑属性的跨脚本传播,但是 Gradle 往往包含
不止一个 build.gradle 文件,比如我设置的 utils.gradle,settings.gradle。如
何在多个脚本中设置属性呢?
Gradle 提供了一种名为 extra property 的方法。extra property 是额外属
性的意思,在第一次定义该属性的时候需要通过 ext 前缀来标示它是一个额外的
属性。定义好之后,后面的存取就不需要 ext 前缀了。ext 属性支持 Project 和
Gradle 对象。即 Project 和 Gradle 对象都可以设置 ext 属性。

举个例子:
我在 settings.gradle 中想为 Gradle 对象设置一些外置属性,所以在
initMinshengGradleEnvironment 函数中
def initMinshengGradleEnvironment(){
//属性值从 local.properites 中读取
Properties properties = new Properties()
File propertyFile = new File(rootDir.getAbsolutePath()
+”/local.properties”)
properties.load(propertyFile.newDataInputStream())
//gradle 就是 gradle 对象。它默认是 Settings 和 Project 的成员变量。可

直接获取
//ext 前缀,表明操作的是外置属性。api 是一个新的属性名。前面说过,
只在第一次定义或者设置它的时候需要 ext 前缀
gradle.ext.api =properties.getProperty(‘sdk.api’)
println gradle.api //再次存取 api 的时候,就不需要 ext 前缀了
……
}

再来一个例子强化一下:我在 utils.gradle 中定义了一些函数,然后想在其他
build.gradle 中调用这些函数。那该怎么做呢?

utils.gradle 中定义了一个获取 AndroidManifests.xmlversionName 的函数:
def getVersionNameAdvanced(){
//下面这行代码中的 project 是谁?
def xmlFile = project.file(“AndroidManifest.xml”)
def rootManifest = new XmlSlurper().parse(xmlFile)
return rootManifest[‘@android:versionName’]
}
//现在,想把这个 API 输出到各个 Project。由于这个 utils.gradle 会被每一个
Project Apply,所以我可以把 getVersionNameAdvanced 定义成一个
closure,然后赋值到一个外部属性
//下面的 ext 是谁的 ext?
ext{ //此段花括号中代码是闭包
getVersionNameAdvanced = this.&getVersionNameAdvanced
}
上面代码中有两个问题:
a、project 是谁?
b、ext 是谁的 ext?

问题 1: project 就是加载 utils.gradle 的 project。由于 hello5.0 有 4 个
project,所以 utils.gradle 会分别加载到 4 个 project 中。
getVersionNameAdvanced 才不用区分到底是哪个 project。
反正一个 project
有一个 utils.gradle 对应的 Script。

问 题 2:
ext 自 然就 是 Project 对应的 ext 了。此处 为 Project 添加了一 些
closure。那么,在 Project 中就可以调用 getVersionNameAdvanced 函数了
通过这种方式,我将一些常用的函数放到 utils.gradle 中,然后为加载它的
Project 设置 ext 属性。最后,Project 中就可以调用这种赋值函数了!

注意:此处我研究的还不是很深,而且我个人感觉:
1 在 Java 和 Groovy 中:我们会把常用的函数放到一个辅助类和公共类中,然
后在别的地方 import 并调用它们。

2 在 Gradle 中,更正规的方法是在 xxx.gradle 中定义插件。然后通过添加 Task
的方式来完成工作,gradle 的 user guide 有详细介绍如何实现自己的插件。

(3) Task 介绍
Task 的 API 文档位于
https://docs.gradle.org/current/dsl/org.gradle.api.Task.html
Task 是 Gradle 中的一种数据类型,它代表了一些要执行或者要干的工作。不同的插件可以添加不同的 Task。每一个 Task 都需要和一个 Project 关联。关于 Task,我这里简单介绍下 build.gradle 中怎么写它,以及 Task 中一些常见的类型。

关于 Task。来看下面的例子:
[build.gradle]
//Task 是和 Project 关联的,所以,我们要利用 Project 的 task 函数来创建一个 Task
task myTask <==myTask 是新建 Task 的名字
task myTask { configure closure }
task myType << { task action } <==注意,<<符号是 doLast 的缩写
task myTask(type: SomeType)
task myTask(type: SomeType) { configure closure }

上述代码中都用了 Project 的一个函数,名为 task,注意:
(1)一个 Task 包含若干 Action。所以,Task 有 doFirst 和 doLast 两个函数,
用于添加需要最先执行的 Action 和需要和需要最后执行的 Action。Action
就是一个闭包。

(2)Task 创建的时候可以指定 Type,通过 type:名字表达。这是什么意思呢?
其实就是告诉 Gradle,这个新建的 Task 对象会从哪个基类 Task 派生。比
如,Gradle 本身提供了一些通用的 Task,最常见的有 Copy 任务。Copy
是 Gradle 中的一个类。当我们:task myTask(type:Copy)的时候,创建的
Task 就是一个 Copy Task。

(3)当我们使用 taskmyTask{ xxx}的时候。花括号是一个 closure。这会导致
gradle 在创建这个 Task 之后,返回给用户之前,会先执行 closure 的内容。

(4)当我们使用 taskmyTask << {xxx}的时候,我们创建了一个 Task 对象,同
时把 closure 做为一个 action 加到这个 Task 的 action 队列中,并且告诉它
“最后才执行这个 closure”(注意,<<符号是 doLast 的代表)。

下图是 Project 中关于 task 函数说明:
这里写图片描述

陆陆续续讲了好多了。是得,Gradle 用一整本书来讲都嫌不够呢。尽管到目前为止,介绍的都是一些比较基础的东西,但该涉及到的知识点都有了。要学到更多的东西,大家可以去看看官方文档。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 肝郁气滞的体质怎么办 手指甲长在肉里怎么办 甲床与指甲脱离怎么办 指甲往肉里面长怎么办 指甲和肉分离是怎么办 大脚趾指甲空了怎么办 脚趾甲长在肉里怎么办 脚趾甲又厚又硬怎么办 小孩子咳嗽有痰怎么办%3f 支气扩张咳血怎么办小 背部第8块脊椎疼怎么办 坐时间长了背疼怎么办 新生儿总哭怎么办吐奶 婴儿吃饱了还哭怎么办 宝宝喉咙哭哑了怎么办 婴儿哭哑了嗓子怎么办 婴儿胖子哭哑了怎么办 孩子声音哭哑了怎么办 喝咖啡手抖心慌怎么办 累了就心慌发抖怎么办 经常头晕心慌胸闷乏力怎么办 在末地迷路了怎么办 在森林里迷路了怎么办 电热宝充电不热怎么办 保温壶按钮坏了怎么办 热水壶盖子坏了怎么办 充电暖宝宝不热怎么办 刚怀孕孕酮低该怎么办 小三怀孕了该怎么办 15学生怀孕了该怎么办 学东西慢悟性差怎么办 欠债不还怎么办有欠条 欠了几百万我该怎么办 玄凤鹦鹉感冒了怎么办 多肉爆出小崽怎么办 多肉红宝石爆崽怎么办 黑尾蜡嘴太怕人怎么办 凤眼菩提盘黑了怎么办 凤眼菩提盘花了怎么办 小金刚菩提反碱怎么办 小金刚盘花了怎么办