关于Gradle的构建流程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rikkatheworld/article/details/88747752

今天跟着几篇blog来学习Gradle的构件流程传送门1 传送门2

首先要理解什么是Gradle。

1、Gradle的概念

Gradle是项目构建工具,是Google官方推荐的Android项目编译工具。构建工具是可以让开发者以可执行和有序的任务来表达自动化的需求。就是将源代码生成可执行程序。

2、Gradle的优点

Gradle构建脚本是声明式的,可读的,并且能够大大表明他们的意图。用Groovy而不是xml写代码。

  1. 灵活
  2. 完全控制
  3. 目标链
  4. 依赖管理
  5. 约定优于配置
  6. 多模块项目
  7. 插件扩展

3、Gradle的使用

每一个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:
在这里插入图片描述
从上面结果可以看出执行过程:
在这里插入图片描述

4、使用Gradle命令行

  1. 通过执行gradle -q tasks:列出项目中所有可用的task:
    在这里插入图片描述
    这个时候可能会产生:构建脚本中定义的其他task去哪了?
  2. 通过执行 gradle -q tasks --all 就可以列出项目内所有的task信息
    在Other task中,就有这些定义的task啦(group Threapy、mGradle0…):

在这里插入图片描述
任务名字缩写
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的项目构建分为三个阶段:初始化、配置、执行。

5、Gradle初始化

在这个阶段中,Gradle决定哪些项目加入到构建中(因为Gradle支持多项目构建),并为这些项目分别创建一个Project实例。

Gradle支持多项目构建和单项目构建,如果是单项目构建,则初始化当前项目即可,如果是多项目构建,则需要决定哪些项目需要加入到构建中并初始化,那么Gradle是如何判断当前构建的是单项目还是多项目呢?如果是多项目,Gradle又是如何决定将哪些项目加入到构建中呢?

多项目还是单项目构建
这里的关键是一个名为settings.gradle,Gradle构建初会去寻找这个文件,查找的规则如下:

  1. 查找当前构建目录下的settings.gradle文件。
  2. 如果没有找到, 则去与当前目录有着相同嵌套级别的master目录查找
  3. 如果没有找到则去父目录中找
  4. 如果没有找到,则进行单项目构建
  5. 如果找到了,Gradle就检查这个文件中是否有定义,如果没有定义多项目,则进行单项目构建,否则进行多项目构建。

多项目工程根目录必须存在 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[])

  1. include方法接收项目路径数组作为参数,并将对应的项目添加到构建中
  2. 参数中支持的项目路径分隔符为“:”而不是“/”
  3. 路径中最后一个节点是项目名称
  4. 项目路径是根项目 目录下的相对路径
    可以使用ProjectDescription.setProjectDir(java.io.File)更改

示例:

// 引入两个项目, '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')

6、配置

默认情况下,Gradle会配置settings.gradle里的所有项目,不论这些项目与最终执行的任务是否有关。

项目配置按照广度配置(breath-wise)顺序配置,如父项目先于子项目配置。

按需配置模式
一个多项目构建中,可能存在大量无需配置的项目,如果需要配置所有的项目后才能运行程序, 则会浪费大量的时间,在Gradle1.4开始引入了按需配置模式。
在这个模式下,Gradle只配置与最终任务相关联的项目,以缩短构建时间,这个模式以后会成为默认模式。

按需配置模式下,项目配置遵循如下规则:

  • 根项目总会被配置
  • 构建的当前项目也会被配置
  • 项目的依赖会被配置
  • 任务依赖的对应项目会被配置(就是之前讲过的dependsOn)
  • 通过命令行构建任务时会配置相应的项目。比如构建 'project A:project B:someTask’时会配置projectB

进行构建时,可以在命令行加入gradle xxxx --config-on-deamon来指定按需配置模式来进行构建。

7、执行

在这个阶段之前,Gradle已经决定好了要构建的项目集,项目集由输入到命令行和当前目录决定的,然后Gradle会去执行任务集中的每个任务。
任务(Task)是由一系列活动(action)构成,当任务执行的时候,活动会依次执行。可以通过doFirst和doLast方法将活动添加到任务中。

那么在多项目构建时,Gradle是如何确定任务是哪个项目的?尤其是任务名称相同的情况下?
其次是Gradle如何确定多任务的执行顺序?

任务的位置
以命令行**$ gradle hello**为例,在执行阶段,Gradle会从构建的当前项目目录开始,依据项目树,往下查找名为 hello 的Task并执行,因此Gradle不会执行当前项目的父项目和兄弟项目的任务。

任务的顺序
如果没有额外的配置,Gradle将会以字母数字的顺序执行任务,比如“:consumer:action”将先于“:producer:action”执行

8、单项目构建示例

# 创建项目目录
$ 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

构建过程如下:

  1. 初始化阶段。
    执行settings.gradle脚本,所以首先输出’settings.gradle -> this is executed during the initialization phase.’。
  2. 配置阶段。
    由于settings.gradle没有定义项目,根项目即当前项目有build.gradle,因此只是执行这个文件。顺序执行:打印语句、三个task方法。
    这里的三个task方法对应Project实例中Task task(String name, Closure configureClosure),查看其Reference文档,说是这个方法会产生一个任务对象并添加到当前Project实例中,并且在返回这个任务的时候会执行闭包以配置这个任务。也就说在配置我们这个项目时,三个task方法的闭包都会被执行。
    以testBoth任务对应的task方法为例,它的闭包里包含doFirst、doLast和println三个方法。其中doFirst和doLast是Task的方法,查看doFirst的Reference文档,说是添加闭包到任务的action列表中,当任务执行的时候,会执行这个闭包。在配置阶段,并不会去执行任务,也就是说不会去执行doFirst和doLast闭包里的内容。
    因此,在配置阶段,Gradle会依次执行三个任务方法里的闭包,而不会执行闭包里添加action的闭包即doFirst和doLast的闭包,因此会依次输出
    ‘build.gradle -> task configured -> …’ 和‘build.gradle -> task testBoth -…’
  3. 执行阶段。
    由于在命令行只构建testBoth任务,因此这个阶段只执行testBoth任务中的Action列表,依次执行doFirst和doLast的闭包。

9、多项目构建

模拟一个常见的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

从输出可以看出:

  1. Gradle首先查找settings.gradle文件进行初始化,确定根项目目录并创建所有include的项目实例。其中包括services。
  2. 配置settings.gradle中include的所有的项目。项目配置的顺序,首先是根项目,其次是子项目,多个子项目看来也是按照字母顺序依次进行的。
  3. 最后执行这些Task。

猜你喜欢

转载自blog.csdn.net/rikkatheworld/article/details/88747752
今日推荐