今天跟着几篇blog来学习Gradle的构件流程传送门1 传送门2
首先要理解什么是Gradle。
Gradle是项目构建工具,是Google官方推荐的Android项目编译工具。构建工具是可以让开发者以可执行和有序的任务来表达自动化的需求。就是将源代码生成可执行程序。
Gradle构建脚本是声明式的,可读的,并且能够大大表明他们的意图。用Groovy而不是xml写代码。
每一个Gradle的构建都是从一个脚本开始的,构建脚本的默认名字是build.gradle,当在shell中执行gradle命令时,Gradle就会去寻找这个build.gradle的文件。如果找不到就会显示一个帮助信息。
在app文件下的build.gradle文件中定义一个原子工作,在上图中 task在Gradle中就是构建一个任务,这个任务是输出helloworld,下面在Terminal执行gradle hello(gradle taskname
):
通过gradle -q hello可以加快执行速度(通过命令选项quiet,只需执行里面代码,省去了一些记录别的东西的操作)
task和里面的action是groovy这门语言的重要元素,doLast就是一个action,表示这个action最后执行,也可以用 符号<<来表示doLast:
下面展示Gradle更高级的特性:
代码中 dependsOn
来说明task之间的依赖。
Gradle会确保被依赖的task总会在定义该依赖的task之前执行。
dependsOn是task的一个方法,比如我们执行最后一行的groupThreapy,它里面依赖了mGradle2,所以会先执行mGradle2,但是mGradle2又依赖了mGradle1和mGradle0,所以在执行mGradle2前会先执行mG1和mG0,而mG0依赖了startTaskeone,所以最开始就会先执行startTaskone。
Gradle可以很好的和Ant(是一个java的构建工具)去集成,因为拥有对Groovy语言特性的完全访问权,所以用write()来打印信息。每个脚本都带有一个ant属性,赋予了可以直接访问Ant任务的能力。上面的例子中可以使用Ant的任务echo去打印"Gradle"。
另外Gradle提供的一个特性就是定义动态task,就是指向运行中指定任务的名字。在上述例子中,使用了Groovy的times方法创建了3个task。Groovy自动暴露一个隐式变量it来指定循环迭代的次数,可以使用这个计数器来构建task的名字,对于第一次循环的task叫做mGradle0.
下面运行 grade groupTherapy:
从上面结果可以看出执行过程:
gradle -q tasks
:列出项目中所有可用的task:gradle -q tasks --all
就可以列出项目内所有的task信息
任务名字缩写
gradle另外一个特性就是能以驼峰式的缩写在命令行上运行任务,例如在上述例子中,可以执行
gradle mG0 gT一样可以运行:
当然如果出现两个相同缩写的方法,比如写一个gotest和groupThreapy缩写相同,则执行命令行会报错。
执行时排除任务
有时候在构建运行时,需要排除一个指定任务,这时候可以通过-x
来实现,假设现在要排除mGradle0:
命令行选项:
gradle xxxx -is
或者 gradle xxxx -i-s
:打印出构建期间时的发生错误的堆栈追踪痕迹gradle xxxx --help
:打印出所有可用的命令行选项,描述信息gradle -b xxx.gradle
:可以执行一个特定的名字的构建脚本gradle xxxx --offline
:离线模式运行构建,仅仅在本地缓存检查依赖是否存在守护进程:
当每次和gradle打交道的时候,会发现我们每次都需要不断重复构建,每次初始化一个构建时,JVM都要重启一次,Gradle的依赖都要载入到类加载器中,而且还要建立项目对象模型,这个过程需要一段时间,这时候Gradle守护进程就是为了解决这个问题。
Gradle守护进程的出现就是以后台的方式运行Gradle,gradle命令就会在后续的构建之中重用之前的进程,避免了启动时造成的开销。
如何开启守护进程?我们在运行gradle命令时,加上--daemon
选项,一旦启动就可以看看系统进程表(Max OS X或者linux就在shell中运行命令 ps | grep gradle ,Windows中直接用任务管理器查看):
通过 gradle mG0 gT --daemon 实践:
这时候查看任务管理器:
守护进程会在3个小时空闲时间自动过期,任何时候你都可以选择构建不适用守护进程,加上–no–daemon即可,如果手动停止进程,可以执行 gradle --stop命令。
下面展示如何在构建apk中,在Andorid Manifest.xml添加meta-data,用Gradle脚本自动完成:
(1)第一种方法,在app的build.gradle中编写,提前导入 import groovy.xml.*
然后在Terminal中输入:
接下来就可以在Manifest.xml中看到:
(2)第二种方法
导入两个类 import com.android.build.gradle.api.ApplicationVariant
import groovy.xml.XmlUtil
同样也是可以的。
Gradle的项目构建分为三个阶段:初始化、配置、执行。
在这个阶段中,Gradle决定哪些项目加入到构建中(因为Gradle支持多项目构建),并为这些项目分别创建一个Project
实例。
Gradle支持多项目构建和单项目构建,如果是单项目构建,则初始化当前项目即可,如果是多项目构建,则需要决定哪些项目需要加入到构建中并初始化,那么Gradle是如何判断当前构建的是单项目还是多项目呢?如果是多项目,Gradle又是如何决定将哪些项目加入到构建中呢?
多项目还是单项目构建
这里的关键是一个名为settings.gradle
,Gradle构建初会去寻找这个文件,查找的规则如下:
settings.gradle
文件。master
目录查找多项目工程根目录必须存在 settings.gradle文件中,单项目工程可以不需要这个目录。
多项目构建的项目集如何确定?
多项目构建时,项目集由settings.gradle中定义,项目集可以用一个树型结构表示,每个节点表示一个项目,并且有对应的项目路径。
下面就是一个多项目的settings.gradle文件:
include 'project1', 'project2:child', 'project3:child1'
include
方法接收多个项目路径作为参数。
每个项目路径对应文件系统中的一个目录,通常情况下项目路径和文件目录一一对应,比如project2:child就是对应project2/child目录。
如果项目树的叶节点和它的父节点都需要包含在构建中,则只需指定子节点即可。
比如project2:child,将创建两个项目实例 project2和project2:child
扁平化布局:
includeFlat 'project3', 'project4'
includeFlat方法将目录作为参数,这些目录是根项目目录的兄弟项目,并作为多项目中根项目的子项目。
Settings语法
这里主要说一下include
方法的语法,其余的语法请对应:传送门
settings.gradle在构建时会创建代理对象
Settings
,其include方法对应着Settings.include(java.lang.String[])
。
示例:
// 引入两个项目, 'foo' and 'foo:bar'
// 对应的文件目录是 $rootDir/a 和 $rootDir/a/b
// directories are inferred by replacing ':' with '/'
include 'foo:bar'
// include one project whose project dir does not match the logical project path
// 引入‘baz’项目,对应的文件目录是foo/baz
include 'baz'
project(':baz').projectDir = file('foo/baz')
默认情况下,Gradle会配置settings.gradle里的所有项目,不论这些项目与最终执行的任务是否有关。
项目配置按照广度配置(breath-wise)顺序配置,如父项目先于子项目配置。
按需配置模式
一个多项目构建中,可能存在大量无需配置的项目,如果需要配置所有的项目后才能运行程序, 则会浪费大量的时间,在Gradle1.4开始引入了按需配置模式。
在这个模式下,Gradle只配置与最终任务相关联的项目,以缩短构建时间,这个模式以后会成为默认模式。
按需配置模式下,项目配置遵循如下规则:
进行构建时,可以在命令行加入gradle xxxx --config-on-deamon
来指定按需配置模式来进行构建。
在这个阶段之前,Gradle已经决定好了要构建的项目集,项目集由输入到命令行和当前目录决定的,然后Gradle会去执行任务集中的每个任务。
任务(Task)是由一系列活动(action)构成,当任务执行的时候,活动会依次执行。可以通过doFirst和doLast方法将活动添加到任务中。
那么在多项目构建时,Gradle是如何确定任务是哪个项目的?尤其是任务名称相同的情况下?
其次是Gradle如何确定多任务的执行顺序?
任务的位置
以命令行**$ gradle hello**为例,在执行阶段,Gradle会从构建的当前项目目录开始,依据项目树,往下查找名为 hello 的Task并执行,因此Gradle不会执行当前项目的父项目和兄弟项目的任务。
任务的顺序
如果没有额外的配置,Gradle将会以字母数字的顺序执行任务,比如“:consumer:action”将先于“:producer:action”执行
# 创建项目目录
$ mkdir single-project-build-lifecycle-demo
$ cd single-project-build-lifecycle-demo
# 创建settings.gradle文件
$ vi settings.gradle
在settings.gradle中:
rootProject.name = 'single-project-build-lifecycle-demo'
println 'settings.gradle -> this is executed during the initialization phase.'
创建build.gradle
# 创建build.gradle文件
$ vi build.gradle
println 'build.gradle -> global println -> This is executed during the configuration phase.'
task configured {
println 'build.gradle -> task configured -> This is also executed during the configuration phase.'
}
task test {
doLast {
println 'build.gradle -> task test -> doLast -> This is executed during the execution phase.'
}
}
task testBoth {
doFirst {
println 'build.gradle -> task testBoth -> doFirst -> This is executed first during the execution phase.'
}
doLast {
println 'build.gradle -> task testBoth -> doLast -> This is executed last during the execution phase.'
}
println 'build.gradle -> task testBoth -> This is executed during the configuration phase as well.'
}
开始构建,并执行testBoth任务 gradle testBoth,输出如下:
settings.gradle -> this is executed during the initialization phase.
> Configure project :
build.gradle -> global println -> This is executed during the configuration phase.
build.gradle -> task configured -> This is also executed during the configuration phase.
build.gradle -> task testBoth -> This is executed during the configuration phase as well.
> Task :testBoth
build.gradle -> task testBoth -> doFirst -> This is executed first during the execution phase.
build.gradle -> task testBoth -> doLast -> This is executed last during the execution phase.
BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed
构建过程如下:
模拟一个常见的Java项目,目录结构如下:
multi-project-build-lifecycle-demo/
settings.gradle
build.gradle
api/
src/main/java/
org/gradle/sample/
api/
Person.java
apiImpl/
PersonImpl.java
services/personService/
src/
main/java/
org/gradle/sample/services/
PersonService.java
test/java/
org/gradle/sample/services/
PersonServiceTest.java
shared/
src/main/java/
org/gradle/sample/shared/
Helper.java
根目录的settings.gradle
include 'api', 'shared', 'services:personService'
根目录的build.gradle
subprojects {
apply plugin: 'java'
group = 'org.gradle.sample'
version = '1.0'
repositories {
mavenCentral()
}
dependencies {
testCompile "junit:junit:4.12"
}
}
project(':shared') {
task hello {
doLast {
println "shared task hello -> doLast"
}
println "shared task hello"
}
}
project(':api') {
dependencies {
compile project(':shared')
}
task hello {
doLast {
println "api task hello -> doLast"
}
println "api task hello"
}
}
project(':services:personService') {
dependencies {
compile project(':shared'), project(':api')
}
task hello {
doLast {
println "personService task hello -> doLast"
}
println "personService task hello"
}
}
执行gradle -i :services:personService:hello,输出如下(-i就是打印啦):
Initialized native services in: /Users/zhangliang/.gradle/native
The client will now receive all logging from the daemon (pid: 27188). The daemon log file: /Users/zhangliang/.gradle/daemon/4.8.1/daemon-27188.out.log
Starting 4th build in daemon [uptime: 2 mins 55.761 secs, performance: 98%, no major garbage collections]
Using 8 worker leases.
Starting Build
Settings evaluated using settings file '/Users/zhangliang/Projects/tools/learn-gradle/multi-project-build-lifecycle-demo/settings.gradle'.
Projects loaded. Root project using build file '/Users/zhangliang/Projects/tools/learn-gradle/multi-project-build-lifecycle-demo/build.gradle'.
Included projects: [root project 'multi-project-build-lifecycle-demo', project ':api', project ':services', project ':shared', project ':services:personService']
> Configure project :
Evaluating root project 'multi-project-build-lifecycle-demo' using build file '/Users/zhangliang/Projects/tools/learn-gradle/multi-project-build-lifecycle-demo/build.gradle'.
shared task hello
api task hello
personService task hello
> Configure project :api
Evaluating project ':api' using build file '/Users/zhangliang/Projects/tools/learn-gradle/multi-project-build-lifecycle-demo/api/build.gradle'.
> Configure project :services
Evaluating project ':services' using build file '/Users/zhangliang/Projects/tools/learn-gradle/multi-project-build-lifecycle-demo/services/build.gradle'.
> Configure project :shared
Evaluating project ':shared' using build file '/Users/zhangliang/Projects/tools/learn-gradle/multi-project-build-lifecycle-demo/shared/build.gradle'.
> Configure project :services:personService
Evaluating project ':services:personService' using build file '/Users/zhangliang/Projects/tools/learn-gradle/multi-project-build-lifecycle-demo/services/personService/build.gradle'.
All projects evaluated.
Selected primary task ':services:personService:hello' from project :services:personService
Tasks to be executed: [task ':services:personService:hello']
:services:personService:hello (Thread[Task worker for ':',5,main]) started.
> Task :services:personService:hello
Task ':services:personService:hello' is not up-to-date because:
Task has not declared any outputs despite executing actions.
personService task hello -> doLast
:services:personService:hello (Thread[Task worker for ':',5,main]) completed. Took 0.001 secs.
BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed
从输出可以看出: