Gradle详解

时间:2022-04-27
本文章向大家介绍Gradle详解,主要内容包括Gradle简介、Gradle入门、Groovy、Groovy数据类型、闭包、脚本、Gradle、Android Gradle、Android tasks、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

Gradle简介

Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建工具,gradle跟ant、maven一样,是一种依赖管理、自动化构建工具。但跟ant、maven不一样的是,它并没有使用xml语言,而是采用了Groovy语言,这使得它更加简洁、灵活,更加强大的是,gradle完全兼容maven和ivy。详细介绍可以查看官网的介绍:http://www.gradle.org/

Gradle入门

在学习Gradle之前,先看一下Groovy的介绍。

Groovy安装

Groovy环境配置非常简单,根据Groovy官网 的介绍,安装的时候使用如下的命令即可安装Groovy。

curl -s get.gvmtool.net | bash
source "$HOME/.gvm/bin/gvm-init.sh"
gvm install groovy

然后我们新建一个以xxx.groovy结尾的文件,然后在里面添加如下代码:

println  "hello groovy"

然后使用如下的命令运行即可。

groovy xxx.groovy

groovy除了支持jdk之外,groovy还提供GDK,相关API请查看GDK官网

Groovy

Groovy语法介绍 1,Groovy为了尽量减少代码的输入,可以不使用分号结尾。 2,Groovy支持动态类型,即定义变量的时候可以不指定其类型,为了代码的清晰,变量定义常常使用关键字def。例如:

def variable1 = 1   //可以不使用分号结尾  
def varable2 = "I am a person"  
def  int x = 1   //变量定义时,也可以直接指定类型  

3,添加函数时,参数的类型也可以不指定。例如:

String testFunction(arg1,arg2){
  ...
}

4,Groovy对字符串支持相当强大,充分吸收了一些脚本语言的优点。单引号”中的内容严格对应Java中的String,不对$符号进行转义。例如:

def singleQuote='I am $ dolloar'  //输出就是I am $ dolloar  

对于双引号”“的内容,如果字符中有号的话,则它会号的话,则它会 表达式 先求值。例如:

def doubleQuoteWithoutDollar = "I am one dollar" //输出 I am one dollar  
def x = 1  
def doubleQuoteWithDollar = "I am $x dolloar" //输出I am 1 dolloar 

5,Groovy中函数调用可以不加括号。例如:

Groovy中函数调用的时候还可以不加括号

Groovy数据类型

基本数据类型

作为面向对象的动态语言,Groovy有诸多面向对象的特性。不过和Java等面向对象的语言不通的是,Java使用基本数据类型,而Groovy则使用基本数据类型的包装数据类型。

容器

Groovy支持的容器类型主要有三类:List、Map、Range。

  • List:链表,其底层对应Java中的List接口,一般用ArrayList作为真正的实现类。
  • Map:键-值表,其底层对应Java中的LinkedHashMap。
  • Range:范围,它其实是List的一种拓展。

List

def aList = [5,'string',true] //定义一个List,其元素可以是任何对象。如果索引超过当前链表长度,List会自动扩展数组的大小。  数据索引从0开始。

assert aList[1] == 'string'  
assert aList[5] == null //第6个元素为空  
aList[100] = 100  //设置第101个元素的值为100  
assert aList[100] == 100  
//此时,aList的大小为101
println aList.size  ===>101  

Range Range是Groovy对List的一种拓展。例如:

def aRangeWithoutEnd = 1..<5    
println aRange.from  
println aRange.to 

闭包

闭包,英文名Closure。是一种典型的数据类型,代表了一段可执行的代码。

  def aClosure = {    
    Stringparam1, int param2 ->  
    println"this is code"   
}

闭包定义好后,要调用它的方法就是:闭包对象.Call或者闭包对象(参数)。例如:

aClosure.call("this is string",100)或者    
aClosure("this is string", 100) 

注:如果闭包没定义参数的话,则隐含有一个参数,这个参数名字叫it,和this的作用类似。例如:

def greeting = { it -> "Hello, $it!" }  
assert greeting('Patrick') == 'Hello, Patrick!'  

闭包注意点

闭包在Groovy中大量使用,比如很多类都定义了一些函数,这些都是闭包。

public static <T> List<T> each(List<T> self, Closure closure) 

1,在Groovy中,当函数的最后一个参数是闭包的话,可以省略圆括号。例如:

def  testClosure(int a1,String b1, Closure closure){  
      //do something  
      closure() //调用闭包  
}  
那么调用的时候,就可以免括号!  
testClosure (4, "test", {  
   println "i am in closure"  
} ) 

上面的代码在调用的时候就可以省略()。

脚本

Groovy中可以像Java那样写package,然后写类。比如在文件夹com/cmbc/groovy/目录中放一个文件,叫Test.groovy。如果不声明访问权限的话,Groovy中类及其变量默认都是public的。Groovy可以把自己转换成Java类。例如,将test.groovy转换为Java类的代码为:

groovyc -d classes test.groovy

Gradle

gradle跟maven一样,也有一个配置文件,maven里面是叫pom.xml,而在gradle中是叫build.gradle,相信Android开发人员对这个都比较了解。Android Studio中的android项目通常至少包含两个build.gradle文件,一个是project范围的,另一个是module范围的,由于一个project可以有多个module,所以每个module下都会对应一个build.gradle。 Gradle只是提供了构建项目的一个框架,真正起作用的是Plugin。Gradle在默认情况下为我们提供了许多常用的Plugin,其中包括有构建Java项目的Plugin,还有War,Ear等。

现在我们都在谈领域驱动设计,Gradle本身的领域对象主要有Project和Task。Project为Task提供了执行上下文,所有的Plugin要么向Project中添加用于配置的Property,要么向Project中添加不同的Task。一个Task表示一个逻辑上较为独立的执行过程,比如编译Java源代码,拷贝文件,打包Jar文件,甚至可以是执行一个系统命令或者调用Ant。另外,一个Task可以读取和设置Project的Property以完成特定的操作。让我们来看一个最简单的Task,例如:

task helloWorld << {
   println "Hello World!"
}

这里的“<<”表示向helloWorld中加入执行代码——其实就是groovy代码。Gradle向我们提供了一整套DSL,所以在很多时候我们写的代码似乎已经脱离了groovy,但是在底层依然是执行的groovy。比如上面的task关键字,其实就是一个groovy中的方法,而大括号之间的内容则表示传递给task()方法的一个闭包。  在与build.gradle相同的目录下执行:

gradle helloWorld

输出结果如下:

:helloWorld
Hello World!

BUILD SUCCESSFUL

Total time: 2.544 secs

在默认情况下,Gradle将当前目录下的build.gradle文件作为项目的构建文件。在上面的例子中,我们创建了一个名为helloWorld的Task,在执行gradle命令时,我们指定执行这个helloWorld Task。这里的helloWorld是一个DefaultTask类型的对象,这也是定义一个Task时的默认类型,当然我们也可以显式地声明Task的类型,甚至可以自定义一个Task类型。  比如,我们可以定义一个用于文件拷贝的Task:

task copyFile(type: Copy) {
   from 'xml'
   into 'destination'
}

  以上copyFile将xml文件夹中的所有内容拷贝到destination文件夹中。这里的两个文件夹都是相对于当前Project而言的,即build.gradle文件所在的目录。

  Task之间可以存在依赖关系,比如taskA依赖于taskB,那么在执行taskA时,Gradle会先执行taskB,然后再执行taskA。声明Task依赖关系的一种方式是在定义一个Task的时候:

task taskA(dependsOn: taskB) {
   //do something
}

 Gradle在默认情况下为我们提供了几个常用的Task,比如查看Project的Properties、显示当前Project中定义的所有Task等。可以通过一下命令查看Project中所有的Task:

gradle tasks

Android Gradle

Gradle 是 Android 现在主流的编译工具,是系统推荐的应用构建方案,虽然在Gradle 出现之前和之后都有对应更快的编译工具出现。 本文将结合Gradle在Android项目中的使用来讲解。 这里借用网络的图片来看一下历史版本中Android是如何编译项目的。如图:

在Gradle出现之前,Android使用ant的方式构建和编译项目,相关的资料可以查看我之前关于ant的介绍。 在Gradle 出现之后,也有新的编译工具出现,就是FaceBook 的Buck工具。这些编译工具在出现的时候大多都比 Gradle 要快,Gradle 之所以慢是跟它的编译周期有很大关系,那为什么Android还是使用Gradle来构建和编译项目呢?只因为Gradle是google的亲儿子。

Gradle生命周期

在上面我们说过, Gradle 中最非常重要的两个对象:Project和Task。 每个项目的编译至少有一个 Project,一个 build.gradle就代表一个project,每个project里面包含了多个task,task 里面又包含很多action,action是一个代码块,里面包含了需要被执行的代码。 在编译过程中, Gradle 会根据 build 相关文件,聚合所有的project和task,执行task 中的 action。因为 build.gradle文件中的task非常多,先执行哪个后执行那个需要一种逻辑来保证。这种逻辑就是依赖逻辑,几乎所有的Task 都需要依赖其他 task 来执行,没有被依赖的task 会首先被执行。所以到最后所有的 Task 会构成一个 有向无环图(DAG Directed Acyclic Graph)的数据结构。 Gradle的编译过程分为三个阶段:

  • 初始化阶段:创建 Project 对象,如果有多个build.gradle,也会创建多个project.
  • 配置阶段:在这个阶段,会执行所有的编译脚本,同时还会创建project的所有的task,为后一个阶段做准备。
  • 执行阶段:在这个阶段,gradle 会根据传入的参数决定如何执行这些task,真正action的执行代码就在这里.

和大家在Android项目看到的一样,对于一个gradle 项目来说必含有三个.gradle文件:一个是项目的setting.gradle,最顶层的 build.gradle文件,每个Module 的build.gradle文件。

setting.gradle 对于 setting 文件定义了哪些module 应该被加入到编译过程,对于单个module 的项目可以不用需要这个文件,但是对于 multimodule 的项目我们就需要这个文件,否则gradle 不知道要加载哪些项目。 顶层的build.gradle 顶层的build.gradle文件的配置最终会被应用到所有项目中。配置如下:

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:1.2.3'
    }
}

allprojects{
    repositories{
        jcenter()
    }
}

说明: buildscript:定义了 Android 编译工具的类路径。repositories中,jCenter是一个著名的 Maven 仓库。 allprojects:中定义的属性会被应用到所有 moudle 中,但是为了保证每个项目的独立性,我们一般不会在这里面操作太多共有的东西。 moudle的build.gradle 每一个moudle都有自己的build.gradle,和顶层的build.gradle格式相同。

说明: buildTypes:定义了编译类型,针对每个类型我们可以有不同的编译配置,不同的编译配置对应的有不同的编译命令。默认的有debug、release 的类型。 dependencies:是属于gradle 的依赖配置。它定义了当前项目需要依赖的其他库。

Android tasks

Gradle有四个基本的Task,Android继承他们并做了一定的补充。

  • assemble:对所有的 buildType 生成 apk 包。
  • clean:移除所有的编译输出文件,比如apk
  • check:执行lint检测编译。
  • build:同时执行assemble和check命令 这些都是基本的命令,在实际项目中会根据不同的配置,会对这些task 设置不同的依赖。比如 默认的 assmeble 会依赖 assembleDebug 和assembleRelease,如果直接执行assmeble,最后会编译debug,和release 的所有版本出来。如果我们只需要编译debug 版本,我们可以运行assembleDebug。

除此之外还有一些常用的新增的其他命令,比如 install命令,会将编译后的apk 安装到连接的设备。

我们运行的许多命令除了会输出到命令行,还会在build文件夹下生产一份运行报告。比如check命令会生成lint-results.html.在build/outputs中。

BuildConfig

对于这个大家肯定不会陌生,最常用的用法就是通过BuildConfig.DEBUG来判断当前的版本是否是debug版本,如果是就会输出一些只有在 debug 环境下才会执行的操作。

Repositories

Repositories 就是代码仓库,这个相信大家都知道,我们平时的添加的一些 dependency 就是从这里下载的,Gradle 支持三种类型的仓库:Maven,Ivy和一些静态文件或者文件夹。在编译的执行阶段,gradle 将会从仓库中取出对应需要的依赖文件,当然,gradle 本地也会有自己的缓存,下次就不用去仓库里面取了。

gradle 支持多种 Maven 仓库,一般我们就是用共有的jCenter就可以了。如果用到私有的仓库,也可以手动加入(如果需要账号的则需要配置用户名和密码)。

Dependencies

在引用库的时候,每个库必须包含三个元素:组名:库名称:版本号。 如果我们要保证我们依赖的库始终处于最新状态,我们可以通过添加通配符的方式。

Local dependencies

如果涉及到本地的库,还可以添加本地库。通过files()方法可以添加文件依赖,如果有很多jar文件,我们也可以通过fileTree()方法添加一个文件夹。

如果涉及到配置本地 .so库。在配置文件中做如下配置,然后在对应位置建立文件夹,加入对应平台的.so文件。