Gradle入门到实战(一) — 全面了解Gradle

声明:本文来自汪磊的博客,转载请注明出处

可关注个人公众号,那里更新更及时,阅读体验更好:

 友情提示由于文章是从个人公众号拷贝过来整理的,发现图片没有正常显示,没关注公众号的同学可通过如下链接查看:https://mp.weixin.qq.com/s?__biz=Mzg2NzAwMjY4MQ==&mid=2247483789&idx=1&sn=4b3bb2ab721c8ed7e05f1e8b2e0fbf70&chksm=ce4371dbf934f8cd7c484e8c5356d299bbd5d7790ee11bb0da9725068fa8e4b895f87379949f&token=655420148&lang=zh_CN#rd

本文目录

一、前言

从本篇起我们来系统学习一下Gradle相关知识,为什么要写本系列呢?因为我发现在平时工作中大部分开发同学只是仅仅会简单的配置Gradle,说白了就是用的时候配置一下,出问题查找一下然后配置上就完了,至于Gradle基本的工作流程完全不理解,其实我本人刚用AS开发的时候也是对Gradle完全不理解,甚至厌恶,为什么非要用这玩意?还要我花时间学习额外的玩意,但是当我慢慢熟悉Gradle之后就慢慢对其"刮目相看"了,简单,可扩展等方面非常友好,项目的构建我们可以编写一些自己的插件来完成一些重复无意义的操作。

本系列我会从最基本的groovy语言开始慢慢学习,直到最后编写我们自己的多渠道打包插件(美团也有同样功能的开源解决方案),图片压缩转换插件(jpg,png转换webp, 不能转换则进一步压缩),相信最后你一定会有所收获。

二、学习Gradle的基础---Groovy语言的学习

学习Geadle我们需要先来学习一下Groovy,很多同学估计听说过Groovy语言,有的也许没听说过,其实我们每天在配置Gradle的时候就是在跟Groovy接触了。

Groovy是一门jvm语言,最终编译成class文件然后在jvm上执行,Java语言的特性Groovy都支持,我们可以混写Java和Groovy,就是在同一文件中可以混写, 注意,这里与Java和Kotlin可不一样,Java和Kotlin是可以互相调用,而不能在同一文件混写 。

Groovy的优势是什么呢?Groovy增强了Java的很多功能。比如解析xml文件,文件读写等,Groovy只需要几行代码就能搞定,而如果用Java则需要几十行代码,简单说就是使用Groovy实现某些功能比Java要少些很多代码。

好了,说了那么多"废话",下面就具体来了解一下Groovy语言,我们只需要了解其中核心的一些使用就可以了,没必要完全了解,作为一门语言Groovy也算比较复杂的,但是我们学习Groovy是为了编写Gradle插件那就只需要了解其核心部分就可以了,其实从我的经验来说学习Groovy更多是为了写更少的代码来实现一些功能,否则我直接用Java写不就完了。

关于Groovy环境的配置很简单,自己查询配置一下就可以了,使用自带的编辑器学习即可。

Groovy的变量和方法声明

Groovy使用def来声明变量,声明变量可以不加类型,运行的时候可以自动推断出变量类型,并且声明语句可以不加分号结尾,如下:

1 def  i = 10
2 def  str = "hello groovy"
3 def  d = 1.25

def同样用来定义函数,Groovy中函数返回值可以是无类型的:

1 //定义函数
2 def showName(){
3    "wang lei" //最后一行执行结果默认作为函数返回值,可以不加return
4 }

如果我们指定了函数的返回值类型,那么可以不加def关键字:

1 //定义函数
2 String showName(){
3    "wang lei" //最后一行执行结果默认作为函数返回值
4 }
Groovy的字符串

Groovy中字符串分为三种,我们一个个来看。
单引号字符串,单引号字符串不会对$进行转义,原样输出

1 def i = 10
2 def str1 = 'i am $i yuan'//单引号不会转义
3 println str1 //输出  i am $i yuan

双引号字符串对$号进行转义,如下:

1 def i = 10
2 def str2 = "i am $i yuan"//双引号会转义
3 println str2 //输出  i am 10 yuan

三引号字符串,三引号字符串支持换行,原样输出,如下:

1 //三个引号原样输出
2 def str3 = '''
3    public static void main(){
4        println "miss you"
5    }
6 '''
7 println str3

输出:

1    public static void main(){
2        println "miss you"
3    }
Groovy的数据类型

Groovy数据类型主要包括以下三种:

  • Java中基本数据类型

  • 增强的List,Map,Range容器类

  • 闭包

    我们分别看一下

Groovy中的所有事物都是对象。int,double等Java中的基本数据类型,在Groovy中对应的是它们的包装数据类型。比如int对应为Integer。比如:

1 def x = 23
2 println x.getClass().getCanonicalName()//java.lang.Integer
3
4 def f = true
5 println f.getClass().getCanonicalName()//java.lang.Boolean

增强的List,Map,Range容器类

List
List:链表,其底层对应Java中的List接口,一般用ArrayList作为真正的实现 类。
使用如下:

 1 def myList = [5,'werw',true]//看到了吧,可以放任意数据,就是那么任性2 myList.add(34)3 //myList.add(12,55)//这样会报角标越界异常4 myList[6] = true5 println myList.size()6 //遍历7 myList.each{8    println "item: $it"9 }
10 myList.eachWithIndex {
11    it, i -> // `it` is the current element,`i` is the index
12    println "$i: $it"
13 }
14 println myList.getClass().getCanonicalName()

打印信息如下:

 1 72 item: 53 item: werw4 item: true5 item: 346 item: null7 item: null8 item: true9 0: 5
10 1: werw
11 2: true
12 3: 34
13 4: null
14 5: null
15 6: true
16 java.util.ArrayList

看到了吧,Groovy中List用起来方便多了,扩展了Java中的一些功能。

单独说明一下,List还有一种方式可以加入元素:<<操作。如下:

1myList << 34//这样也是加入元素

刚接触是不是感觉很别扭,慢慢来,习惯就好了,对于<<文档描述如下:


一定要学会查文档来学习,而不是遇见问题就查询,向这种<< 只有文档才说的最明白,况且我这篇文章也只是写出来最核心的一些资料,如果你想实现一些功能而我没写出来就需要自己去查文档。

文档地址:http://www.groovy-lang.org/groovy-dev-kit.html与 http://www.groovy-lang.org/api.html 建议还是浏览一下文档,里面有很多好玩的东西

Map
直接看示例:

 1 //Map类2 def map = ['key1':true,name:"str1",3:"str3",4:"str4"]3 println map.name // 相当于 map.get('name')4 println map.get(4)5 map.age = 14 //加入 age:14 数据项6 println map.age //输出147 println map.getClass().getCanonicalName()8 //遍历9 map.each{
10    key,value -> 
11       println key +"--"+ value
12    }
13
14 map.each{
15    it -> 
16       println "$it.key ::: $it.value"
17    }
18 // `entry` is a map entry, `i` the index in the map
19 map.eachWithIndex { entry, i ->
20    println "$i - Name: $entry.key Age: $entry.value"
21 }
22
23 // Key, value and i as the index in the map
24 map.eachWithIndex { key, value, i ->
25    println "$i - Name: $key Age: $value"
26 }
27
28 println map.containsKey('key1')  //判断map中是否包含给定的key
29 println map.containsValue(1)  //判断map中是否包含给定的value
30 //返回所有
31 println map.findAll{
32    entry ->
33         entry.value instanceof String//value是String类型的
34 }
35 //返回第一个符合要求的
36 println map.find{
37    entry ->
38        entry.key instanceof Integer && entry.key > 1
39 } //返回符合条件一个entry
40
41 //清空
42 map.clear()
43
44 println map.size()

Ranges
关于Ranges文档描述如下:


简单来说Ranges就是对List的扩展,用..或者..<来定义,具体使用如下:

 

 1 //Range2 def Range1 = 1..10//包括103 println Range1.contains(10)4 println Range1.from +"__"+Range1.to5 def Range2 = 1..<10//不包括106 println Range2.contains(10)7 println Range2.from +"__"+Range2.to8 //遍历9 for (i in 1..10) {
10    println "for ${i}"
11 }
12
13 (1..10).each { i ->
14    println "each ${i}"
15 }
16 def years = 23
17 def interestRate
18 switch (years) {
19    case 1..10: interestRate = 0.076; break;
20    case 11..25: interestRate = 0.052; break;
21    default: interestRate = 0.037;
22 }
23 println interestRate//0.052

 

好了,以上就是Groovy中的容器类,最核心的我已经列出来了,如果想继续查看其余功能请自己查看文档,当初看完这部分我的感觉就是这尼玛不就是kotlin吗(学习Groovy之前已经看了Kotlin),二者其实都是对Java的扩展,使用起来都更加简单,功能更加强大,好了,学习Gradle,掌握以上就已经基本够用了。

Groovy中的闭包

闭包Closure一开始接触的时候真是有点蒙圈的感觉,这是什么玩意,Kotlin中也有这玩意,接触多了也就慢慢习惯了,其实就是一段可执行的代码,我们可以像定义变量一样定义可执行的代码。

闭包一般定义如下:

1 def xxx = {
2    paramters -> 
3        code
4 }

比如,我们定义一下闭包:

1 def mClosure = {
2    p1,p2 ->
3         println p1 +"..."+p2
4 }

p1,p2也可以指定数据类型,如下:

1 //闭包定义
2 // ->前是参数定义, 后面是代码
3 def mClosure = {
4    String p1, int p2 ->
5         println p1 +"..."+p2
6 }

调用上述定义的闭包:

1 //调用 均可以
2 mClosure("qwewe",100)
3 mClosure.call("qwewe",100)

看到这里有C经验的是不是瞬间想到函数指针了,连调用都非常像。

定义闭包我们也可以不指定参数,如不指定则默认包含一个名为it的参数:

1 def hello = { "Hello, $it!" }
2 println hello("groovy")//Hello, groovy!

注意一点,在Groovy中,当函数的最后一个参数是闭包或者只有一个闭包参数,可以省略调用时的圆括号。比如:

 1 def fun(Closure closure){2    println "fun1"3    closure()4 }5 //调用方法6 fun({7    println "closure"8 })9 //可以不写()直接调用
10 fun{
11    println "closure"
12 }

或者如下:

 1 def fun(int a , Closure closure){2    println "fun1 $a"3    closure()4 }5 //调用方法6 fun(1, {7    println "closure"8 })9 //可以不写()直接调用,这就有点奇葩了
10 fun 1, {
11    println "closure"
12 }

关于闭包,刚接触肯定不习惯,自己一定要花时间慢慢体会,闭包在Gradle中大量使用,后面讲Gradle的时候会大量接触闭包的概念。

Groovy中的文件操作
在我电脑有如下文件:


接下来我们对in.txt与out.txt文件进行IO操作。

读文件
读文件有三种操作:一次读取一行,读取全部返回字节数据,数据流的形式读取,我们分别看一下:

一次读取一行:

1 def src = new File("C:\\Users\\wanglei55\\Desktop\\Groovy\\in.txt")
2 //每次读取一行
3 src.eachLine{
4    //it就是每一行数据
5    it ->
6        println it
7 }

读取全部返回字节数据:

1 //一次读取全部:字节数组
2 def bytes = src.getBytes()
3 def str = new String(bytes)
4 println "str::"+str

数据流的形式读取:

1 //返回流,不用主动关闭
2 src.withInputStream{
3    inStream ->
4        //操作。。。。
5 }

看到这里是不是不禁感叹:so easy!!!。是的,Groovy的IO操作就是那么潇洒,任性。

写文件
写文件的操作同样很简单,我们看下将in.txt文件拷贝到out.txt中怎么操作:

1 //写数据
2 def des = new File("C:\\Users\\wanglei55\\Desktop\\Groovy\\out.txt")
3 des.withOutputStream{ 
4    os->
5        os.leftShift(bytes)//左移在这里可以起到写入数据的作用
6 }

我们也可以逐行写入数据:

1 des.withWriter('utf-8') { writer ->
2    writer.writeLine 'i'
3    writer.writeLine 'am'
4    writer.writeLine 'wanglei'
5 }

我们也可以这样写入数据:

1 des << '''i love groovy'''

是不是爽歪歪?IO操作就是这么简单,像遍历文件夹内容等操作自己可以查询文档写写看,一定要学会自己查文档,上面很多代码都是从文档示例上看到的。

好了,以上就是IO操作的一些核心,接下来我们看下Groovy怎么操作XML以及Json数据。

XML解析
同样我电脑有如下xml文档:


内容如下:

 1<response version-api="2.0">2    <value>3        <persons>4            <person id = "1">5                <name>zhangsan</name>6                <age>12</age>7            </person>8            <person id = "2">9                <name>lisi</name>
10                <age>23</age>
11            </person>
12        </persons>
13    </value>
14</response>

Groovy解析xml文档同样非常简单,我们可以像操作对象一样操作xml:

1 //xml解析
2 def xmlFile = new File("C:\\Users\\wanglei55\\Desktop\\Groovy\\test.xml")
3 def parse = new XmlSlurper()
4 def result =parse.parse(xmlFile)
5
6 def p0 = result.value.persons.person[0]
7 println p0['@id']
8 println p0.name
9 println p0.age

很简单,没什么要特别说明的,这里稍微记一下,后面写插件的时候会解析安卓的AndroidManifest.xml文件

Json

直接看代码吧,很简单:

 1 //Person类2 class Person{3    def first4    def last5 }67 def p = new Person(first: 'wanglei',last: '456')8 //转换对象为json数据9 def jsonOutput = new JsonOutput()
10 def json = jsonOutput.toJson(p)
11 println(json)
12 //格式化输出
13 println(jsonOutput.prettyPrint(json))
14
15 //解析json
16 def slurper = new JsonSlurper()
17 Person person = slurper.parseText(json)
18 println person.first
19 println person.last

打印输出如下:

1 {"first":"wanglei","last":"456"}
2 {
3    "first": "wanglei",
4    "last": "456"
5 }
6 wanglei
7 456

好了,以上就是Groovy的一些核心了,对于学习Gradle掌握上面这些就差不多了。下面进入Gradle的学习。

三、认识Gradle

Gradle到底是个什么玩意?为什么要先抛出这个问题,从我经验来说,大量开发者都只是会配置Gradle,配置这个,配置那个,至于为什么这么配置,出了问题怎么查找等等完全扒瞎,很多开发者都仅仅停留在配置这个阶段,我认为Gradle的学习有三个层次:简单配置->认为Gradle是个脚本工具->Gradle同样是编程框架。

我个人更倾向认为Gradle是一个编程框架,其实无论你认为是框架还是脚本,只要能够为我所用即可。

Gradle学习主要参考文档如下:

Gradle API:https://docs.gradle.org/current/javadoc/org/gradle/api/package-summary.html

Gradle DSL:https://docs.gradle.org/current/dsl/org.gradle.api.Project.html

Gradle 用户手册:https://docs.gradle.org/current/userguide/userguide.html

Android插件DSL参考:http://google.github.io/android-gradle-dsl/current/index.html

到这里可能你还有点蒙圈,一看文档这都是什么玩意啊,别着急,一步步来慢慢认识Gradle。

Gradle环境搭建

安装Gradle开发环境很简单,参考官方文档即可:安装Gradle
安装Gradle前最好将JDK先升级到1.8版本,Gradle最新版本需要的JDK版本为1.8。

请自行安装好Gradle环境。

初识Gradle

Gradle是一个开源的构建自动化工具,既然是用于项目构建,那它肯定要制定一些规则,在Gradle中每一个待编译的工程都称为Project,每个Project都可以引入自己需要的插件,引入插件的目的其实就是引入插件中包含的Task,一个Project可以引入多个插件,每个插件同样可以包含一个或多个Task,关于插件和Task我们后面还会单独细说。

比如,如下项目:


工程GradleLearn包含三个module:app,library1,library2。其中app是App module,而library1,library2均是library module。

相信现在大部分都是这种多module项目了,对于Gradle来说这种叫做multiprojects,看这意思是多projects的项目,那么对于Gradle来说有多少个项目呢?

我们可以通过gradle projects命令来查看有多少个projects:


这里我是在命令行里执行的。

看到了吧,对于Gradle来说有4个project,其中GradleLearn叫做Root project,其余三个moudule均叫做Project。

那Gradle怎么知道有多少个Project呢?在我们创建工程的时候在根目录下有个settings.gradle文件,我们看下其中内容:

1include ':app', ':library1', ':library2'

默认只有app模块,之后我们新建的library1,library2模块都会自动包含进来,Gradle就是通过查看这个配置文件来确定我们工程有多少了project的,我们修改settings.gradle文件:

1include ':app', ':library1'

去掉library2模块,然后在执行gradle projects命令,如下:


看到了吧,已经没有library2模块了。

好了,现在我们对settings.gradle有了初步的认识,我们在看看build.gradle文件的作用,工程根目录下有个build.gradle,每个module下也有自己的build.gradle文件:


记得刚接触Gradle的时候这几个build.gradle文件真是让我蒙圈,怎么这么多,都干什么的,相信很多同学刚开始都有这疑问。

上面我们提到GradleLearn叫做Root project,其余三个moudule均叫做Project,其实在Gradle中GradleLearn被当做三个module的父级project,在父project的build.gradle中配置的信息可以作用在子project中,比如根目录build.gradle内容如下:

 1 ..........23 allprojects {4    repositories {5        google()6        jcenter()7    }8 }9
10 .........

这里的意思就是配置此项目及其每个子项目的仓储库为google()与 jcenter(),这样我们就不用在每个子project的build.gradle中再单独配置了,否则我们需要为每个子project单独配置一下需要引入的插件的仓储库。

根project的build.gradle文件并不是必须的,我们甚至可以删除掉,其存在的意义主要就是将子project公共的配置提取到父build.gradle来统一管理,是不是有点像“基类”的意思。

好了,以上只是大概了解了一下settings.gradle与build.gradle,都是及其简单的。

settings.gradle与build.gradle的本质

大家有没有想过这样一个问题为什么settings.gradle可以include子模块,又为什么build.gradle可以写如下配置呢:

 1 buildscript {2    repositories {3        google()4        jcenter()5    }6    dependencies {7        classpath 'com.android.tools.build:gradle:3.3.1'8    }9}
10
11 allprojects {
12    repositories {
13        google()
14        jcenter()
15    }
16 }
17
18 task clean(type: Delete) {
19    delete rootProject.buildDir
20 }

其实我们在settings.gradle与build.gradle中看似配置的信息其实都是调用对应对象的方法或者脚本块设置对应信息。

对应对象是什么玩意?其实settings.gradle文件最终会被翻译为Settings对象,而build.gradle文件最终会被翻译为Project对象,build.gradle文件对应的就是每个project的配置。

Settings与Project在Gradle中都有对应的类,也就是说只有Gradle这个框架定义的我们才能用,至于Settings与Project都定义了什么我们只能查看其官方文档啊。

Settings对象
比如Settings类中定义了include方法: 


include方法api说明为:


看到了吧,我们每一个配置都是调用对应对象的方法。

我还发现Settings类中定义了如下方法:


那我们在setting.gradle文件里面写上如下代码试一下:

1 include ':app', ':library1', ':library2'
2
3 def pro = findProject(':app')
4 println '----------------------------'
5 println pro.getPath()
6 println '----------------------------'

然后执行gradle assembleDebug命令编译我们的项目输出如下:


输出了app这个project的信息。

Project对象
每个build.gradle文件都会被翻译为对应的Project对象,build.gradle文件最重要的作用就是:

  • 引入插件并配置相应信息

  • 添加依赖信息

引入插件重要的就是引入插件中包含的tasks,至于插件与tasks后面会详细讲到。

那怎么引入插件呢?

Project定义了apply函数供我们调用:


平时我们看到的如下:

apply plugin: 'com.android.library'

最终都是调用那个的上面apply方法。

在Project类中有个很重要的方法,如下:


这个方法在配置完当前project后立刻调用,并且传给我们project参数,我们调用试试,在根build.gradle中添加如下代码:

1 afterEvaluate{
2
3    project ->
4        println "root module -----> $project.name"
5 }

app module的build.gradle添加如下代码:

1 afterEvaluate{
2
3    project ->
4        println "app module -----> $project.name"
5 }

同样,执行assemble debug命令,打印如下:


输出了相应的信息,上面的都不难,我只是想告诉大家Gradle有它自己的规则,怎么配置,配置什么都在文档中有对应规定,我们配置xxx.gradle都有对应的类,最终都翻译成对应对象,有问题查阅相关文档即可。

Gradle对象

在我们执行gradle相关命令的时候,Gradle框架会为我们创建一个gradle对象,并且整个工程只有这一个gradle对象,这个对象不像上面两个一样有对应文件,这个对象一般不需要我们配置什么,主要用于给我们提供一些工程信息,具体有哪些信息呢?查看文档中Gradle类包含以下信息:


我们可以打印一些信息看看,在根build.gradle中添加如下代码:

1 println "homeDir = $gradle.gradleHomeDir"
2 println "UserHomeDir = $gradle.gradleUserHomeDir"
3 println "gradleVersion = $gradle.gradleVersion"

执行gradle assembleDebug命令打印如下信息:


gradle对象最重要的就是在构建工程的时候加入各种回调,通过加入回调我们可以监听工程构建的各个时期,这部分后面会讲到。

好了,到这里Gradle主要的三个类就基本介绍完了,至于每个类定义了什么属性,方法等等还需要自己去看看文档。

四、Gradle工作时序

Gradle执行分为三个过程:

Initiliazation
初始化阶段只要为每个module创建project实例。这个阶段settings.gradle文件会被解析执行。

Configration
这个阶段解析每个模块的build.gradle文件,这个阶段完成后整个项目的tasks执行顺序也就确定了并且task准备就绪处于待执行状态,整个tasks任务会构成一个有向无环图。

执行任务
这阶段就是按照顺序执行具体任务了。

在每个阶段我们可以通过gradle对象添加回调监听。

我们在settings.gradle文件与每个module的build.gradle文件添加如下信息:

settings.gradle:

1 println "settings start"
2 include ':app', ':library1', ':library2'
3 println "settings end"

根目录的build.gradle:通过gradle对象添加了一些回调监听

 1 。。。。。。。。2 afterEvaluate{34    project ->5        println "root module afterEvaluate -----> $project.name"6 }78 gradle.beforeProject{9    project ->
10        println "beforeProject $project.name"
11 }
12
13 gradle.afterProject{
14    project ->
15        println "afterProject $project.name"
16 }
17
18 gradle.taskGraph.whenReady {
19    println "taskGraph.whenReady"
20 }
21
22 gradle.buildFinished{
23    result ->
24        println "buildFinished"
25 }

app module的build.gradle:

 1 apply plugin: 'com.android.application'2 println "app start"3 。。。。。。。。。。4 afterEvaluate{56    project ->7        println "app module afterEvaluate -----> $project.name"8 }9
10 println "app end"

library1 module的build.gradle:

 1 apply plugin: 'com.android.library'2 println "library1 start"34 。。。。。。。。。。。56 afterEvaluate{78    project ->9        println "library1 module afterEvaluate -----> $project.name"
10 }
11
12 println "library1 end"

library2 module的build.gradle:

 1 apply plugin: 'com.android.library'2 println "library2 start"34 。。。。。。。。。。。。。。。56 afterEvaluate{78    project ->9        println "library2 module afterEvaluate -----> $project.name"
10 }
11
12 println "library2 end"

接下来我们执行gradle assembleDebug命令,打印如下信息:


以上就是Gradle构建工程的大体过程,在这个过程我们可以加入各种回调监听。

五、Gradle中的task

task可以说Gradle中很重要的部分,task是Gradle的执行单元,一个task就是具体执行某一任务。

在AS中我们一创建工程的时候就存在很多task:


这些task都是我们引入安卓插件帮我们自动引入的,关于插件马上就讲到了。

自定义task
我们可以定义自己的task,在app模块的build.gradle中添加如下代码:

1 task aTask{
2    println ">>>>>>>>>>>>>>>>task..."
3 }

这样我们就定义了名称为aTask的任务了,在AS中同步之后我们可以看到:


由于我们没有指明分组所以这里默认属于other分组,我们可以指定自己的分组:

1 task aTask(group:'myGroup'){
2    println ">>>>>>>>>>>>>>>>task..."
3 }

同步之后,如下:


上面我们说过build.gradle对应Gradle中的Project类,Project类中定义了如下方法:


正式由于Project类中定义了上面方法,我们才可以以上面方式来创建task任务。

我们可以通过gradle task名称的命令来执行某一个task任务,比如执行上述任务:gradle aTask


上述打印信息的代码是在Configure阶段来执行的,也就是我们可以提前配置一下我们的task,如果我们不需要配置想在执行我们task的时候来执行一些操作,我们也可以修改代码如下:

 1 task aTask(group:'myGroup'){2    println ">>>>>>>>>>>>>>>>config..."3    //任务开始执行初期执行4    doFirst{5        println ">>>>>>>>>>>>>>>>aTask doFirst..."6    }7    //任务开始执行末期执行8    doLast{9        println ">>>>>>>>>>>>>>>>aTask doLast..."
10    }
11 }

执行gradle aTask命令来执行任务:


此外,有时我们也会看到如下写法:

1 task B << {
2    println 'B'
3 }

这种写法和如下写法都是一样效果:

1 task B {
2    doLast{
3        println 'B'
4    }
5 }

task的依赖关系
task之间可以指定依赖关系,比如指定A task依赖B task那么在执行A task的时候需要先执行B task,我们修改代码如下:

 1 task aTask(group:'myGroup'){2    println ">>>>>>>>>>>>>>>>a config..."34    doFirst{5        println ">>>>>>>>>>>>>>>>aTask doFirst..."6    }78    doLast{9        println ">>>>>>>>>>>>>>>>aTask doLast..."
10    }
11 }
12
13 task bTask(group:'myGroup'){
14    println ">>>>>>>>>>>>>>>>b config..."
15
16    doFirst{
17        println ">>>>>>>>>>>>>>>>bTask doFirst..."
18    }
19
20    doLast{
21        println ">>>>>>>>>>>>>>>>bTask doLast..."
22    }
23}
24 //这里指定aTask 依赖于bTask
25 aTask.dependsOn bTask

我们也可以创建task的时候就直接指定依赖:

 1 //通过dependsOn指定依赖2 task aTask(group:'myGroup', dependsOn: ["bTask"]){3    println ">>>>>>>>>>>>>>>>a config..."45    doFirst{6        println ">>>>>>>>>>>>>>>>aTask doFirst..."7    }89    doLast{
10        println ">>>>>>>>>>>>>>>>aTask doLast..."
11    }
12 }
13
14 task bTask(group:'myGroup'){
15    println ">>>>>>>>>>>>>>>>b config..."
16
17    doFirst{
18        println ">>>>>>>>>>>>>>>>bTask doFirst..."
19    }
20
21    doLast{
22        println ">>>>>>>>>>>>>>>>bTask doLast..."
23    }
24 }
25
26 //aTask.dependsOn bTask

二者同样效果,我们执行aTask:


我们也可以定义task类,此类需要继承DefaultTask:

 1 class IncrementTask extends DefaultTask {23    String msg = 'default'45    IncrementTask() {6        group '自定义任务' //指定任务的分组7        description '任务描述'//指定描述8    }9
10    @TaskAction
11    void run() {
12        println "IncrementTask __$msg"
13    }
14 }

上面用@TaskAction标注方法run(),这样我们在执行任务的时候会调用run()方法。

接下来我们可以通过如下方式创建实例:

1 task increment2(type: IncrementTask) {
2    msg = 'create task'
3 }

创建task的时候我们可以指定type,也就是指定其父类,如果我们不制定默认为DefaultTask的子类。

我们也可以通过如下方式创建:

1 tasks.create('increment1', IncrementTask){
2    msg = 'tasks.create'//重新指定msg信息
3 }

这里我在说一下查文档的重要性,tasks是什么鬼?由于我们是在build.gradle中使用的,其对应Project类,所以我们去Project类中查一下,属性描述有对其描述:


原来tasks是一个属性,点进去看一下:


其对应的是TaskContainer类,我们还需要再看一下这个类都包含什么:


看到了吧,这个类中包含各种create方法用来创建task任务。

好了,task就说这么多,有什么问题希望你自己去查文档解决,比如创建的时候可以指定type,那还可以指定其余的信息吗?都可以指定什么信息,这些文档中都有描述。

六、Gradle中任务的增量构建

作为构建工具我们要防止做重复工作。例如AS编译出APK文件,如果我们已经编译出了APK文件,再次编译的时候如果我们没有删除APK文件并且代码资源等都没变化那么就没必要在重新走一遍流程执行各个task任务最重输出APK文件,中间的任务都可以跳过,这样可以提升效率缩短编译时间。

Gradle是通过增量构建的特性来支持这个功能的。

Gradle在执行任务的时候会检查任务的输入输出是否有变化,如果任务的输入输出均没有变化则认为任务是up-to-date的,会跳过任务不去执行它,任务至少有一个输出否则增量构建不起作用

我们改造IncrementTask,使其成为可以增量构建的任务,指明其输入输出:

 1 class IncrementTask extends DefaultTask {23    @Input //指明输入4    String msg = 'default'56    @OutputFile //指明输出7    File file89    IncrementTask() {
10        group '自定义任务'
11        description '任务描述'
12    }
13
14    @TaskAction
15    void run() {
16        println "IncrementTask __$msg"
17    }
18 }

我们在创建任务的时候指明其输入输出:

1 tasks.create('increment1', IncrementTask){
2    msg = 'tasks.create1'//重新指定msg信息
3    file = file('path.txt')
4 }

我们在第一次执行任务的时候,输出如下:


执行了run方法输出相应信息,接着我们再次执行:


看到了吧,没有执行run方法,并且提示:up-to-date。

我们也可以关闭任务的增量构建,使其每次执行的时候都会执行run方法:

1 。。。。。。。
2 IncrementTask() {
3        group '自定义任务'
4        description '任务描述'
5        outputs.upToDateWhen { false }//返回false关闭增量构建
6    }
7 。。。。。。

增量构建功能主要是为了提升编译效率,否则一个大的项目包含几十几百的任务不能每次编译都要挨个重新执行每次任务吧,这里只是简单介绍了一下Gradle的增量构建功能,详细学习可以自己查阅文档看一下。

七、自定义插件

官方自定义插件教程:编写自定义插件

引入插件最大的作用就是引入其中包含的一个个任务,一个任务往往完成一项工作,比如java代码编译为class字节码的任务,处理ndk的任务等等。
接下来我们先看看怎么自定义插件,说那么多显得很苍白。

Gradle提供了三种方式:

  • build.gradle脚本中直接编写

  • buildSrc中编写

  • 独立Module中编写

    其中前两种方式只能用在自己项目,而第三种方式比较灵活,可以发布到jcenter仓库供别人引入使用,这里我采用独立module的方式编写。

我们新建plugin module,在其build.gradle中引入Groovy插件,并将Gradle API添加为编译时依赖项:

 1 apply plugin: 'java-library'2 apply plugin: 'groovy'34 dependencies {5    compile gradleApi()6    compile localGroovy()7    implementation fileTree(dir: 'libs', include: ['*.jar'])8 }9
10 sourceCompatibility = "1.7"
11 targetCompatibility = "1.7"

下面我们就可以编写自己的插件了,自定义插件需要实现Plugin接口并实现apply方法:

新建Plugin1.groovy类编写如下代码:

1 class Plugin1 implements Plugin<Project>{
2
3    @Override
4    void apply(Project project) {
5        println "我是自定义插件"
6    }
7 }

很简单就是一句输出,别急慢慢来,慢慢就复杂了。

这时别的模块怎么使用我们的插件呢?想想我们安卓中我们怎么引入插件的,我们需要指定仓库的地址,插件的版本,然后在调用apply方法引入插件,这里我们也需要这样指定。

我们先指定我们插件的完整版本,怎么还完整版本?我们看看怎么引入安卓插件版本的:

1 classpath 'com.android.tools.build:gradle:3.3.1'

这里包含三部分:


三部分以分号: 分开,所以我们的插件也需要指定这些信息,怎么指定呢?很简单在build.gradle文件中指定即可,如下:


我们再看引入安卓插件的apply写法:

1 apply plugin: 'com.android.application'

'com.android.application'这个字符串又是什么?我的理解就是给我们的插件起个别名通过这个别名引入就可以了,那怎么配置这个别名呢?
如下:


看到了吧,在main的同级目录下新建resources目录,然后新建META-INF目录,在新建gradle-plugins目录,最后创建xxx.properties配置文件,这个文件的名字就是我们插件的别名 我这里的文件名是:com.wanglei.plugin所以可通过如下方式引入插件:

1 apply plugin : "com.wanglei.plugin"

xxx.properties文件中需要配置如下信息:

1 implementation-class=插件实现类的完整路径

我这里Plugin1的完整实现类为:com.wanglei55.plugin.Plugin1,所以配置如下:

1 implementation-class=com.wanglei55.plugin.Plugin1

到这,所有的插件相关配置都已经配置完了。

接下来我们就可以将我们的插件发布到仓库了,这里为了简便我发布到本地仓库,本地仓库就是发布到我们自己的电脑上,引入maven-publish插件用来发布到本地仓库,配置如下,这里我列出plugin模块下build.gradle文件所有内容:

 1 apply plugin: 'java-library'2 apply plugin: 'groovy'34 dependencies {5    compile gradleApi()6    compile localGroovy()7    implementation fileTree(dir: 'libs', include: ['*.jar'])8 }9
10 sourceCompatibility = "1.7"
11 targetCompatibility = "1.7"
12
13 group 'com.wanglei.plugin'
14 version '1.0'
15 //用于发布到本地仓库
16 apply plugin: 'maven-publish'
17 //发布配置
18 publishing{
19    publications{
20        plugin(MavenPublication){
21            from components.java
22            artifactId 'testplugin'
23        }
24    }
25 }

同步工程后,就多了如下任务:


双击publishPluginPublicationToMavenLocal任务执行,就将我们插件发布到本地仓库了。

比如我的这台电脑发布完如下:


这样我们就可以在别的module引入自己写的插件了,比如我在app模块下引入:

1 buildscript{
2    repositories{
3        mavenLocal()
4    }
5    dependencies{
6        classpath 'com.wanglei.plugin:testplugin:1.0'
7    }
8 }
9 apply plugin : "com.wanglei.plugin"

执行gradle assembleDebug编译命令,输出如下:


输出了相应信息。

上面只是输出简单的信息,还没有为我们的插件添加任何任务,下面为插件添加任务,新建MyTask.groovy类,编写如下代码:

 1 class MyTask extends DefaultTask{23    String msg = 'default'45    MyTask() {6        group '自定义任务'7        description '任务描述'8    }9
10    @TaskAction
11    void run() {
12        println "MyTask2__$msg"
13    }
14 }

新建了任务类,然后我们就可以在创建这个任务了:

 1 class Plugin1 implements Plugin<Project>{23    @Override4    void apply(Project project) {5        println "我是自定义插件"67        project.afterEvaluate {8            //创建任务9            MyTask task = project.tasks.create('myTask',MyTask)
10            //添加任务依赖关系
11            project.tasks.getByName('checkDebugManifest').dependsOn task
12        }
13    }
14 }

这里在afterEvaluate后也就是project评估配置完在创建我们的task,因为只有评估配置完才知道我们配置了什么啊,并且我们让checkDebugManifest这个任务依赖于我们的任务,这个是个注意点,如果不加这个依赖我们执行gradle assembleDebug命令是不会执行我们的任务的,为什么?gradle assembleDebug就是让安卓编译出APK,编译APK需要执行一连串任务,那一连串任务都是安卓插件定义好的依赖关系,而我们自己定义的任务跟人家那些任务有什么关系呢?当然不会执行我们的任务,添加上面依赖相当于把我们的任务加入进上面的任务图中,使人家玩起来的时候能带上我们玩玩。

接下来执行gradle assembleDebug命令:


很简单,就不在多余解释了。

下面我们看看怎么像插件中传递参数。
引入安卓插件我们可以配置一些信息,如下:

 1 android {2    compileSdkVersion 283    defaultConfig {4        applicationId "com.wanglei55.gradlelearn"5        minSdkVersion 156        targetSdkVersion 287        versionCode 18        versionName "1.0"9        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
10    }
11    buildTypes {
12        release {
13            minifyEnabled false
14            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
15        }
16    }
17 }

这些配置最后都是传递进安卓插件的,我们也可以这样玩玩。

定义两个类:

1 class AExtensions{
2    def compileSdkVersion
3 }
4
5 class BExtensions {
6   def  applicationId
7 }

然后我们就可以为插件创建扩展了:

 1 class Plugin1 implements Plugin<Project>{23    @Override4    void apply(Project project) {5        println "我是自定义插件"6        //创建扩展7        project.extensions.create('aExtensions',AExtensions)8        //为aExtensions创建扩展9        project.aExtensions.extensions.create('bExtensions',BExtensions)
10        //gradle分析完成之后
11        project.afterEvaluate {
12            //获取我们配置的compileSdkVersion与applicationId并打印
13            println project.aExtensions.compileSdkVersion
14            println project.aExtensions.bExtensions.applicationId
15            //创建任务
16            MyTask task = project.tasks.create('myTask',MyTask){
17                //为任务中msg赋值
18                msg = project.aExtensions.bExtensions.applicationId
19            }
20            //添加任务依赖关系
21            project.tasks.getByName('checkDebugManifest').dependsOn task
22        }
23    }
24 }

我们在引入插件的时候就可以为插件配置一些参数了:

1 apply plugin : "com.wanglei.plugin"
2
3 aExtensions{
4    compileSdkVersion 29
5    bExtensions {
6        applicationId "com.wl.gradle"
7    }
8 }

执行gradle assembleDebug命令:


到此自定义插件的各个方面就介绍完了,其实都不难。

八、总结

零零散散写了一大篇,介绍了Gradle的各个方面,有些地方我自己都觉得写的太啰嗦,既然写出来了就这样吧,Gradle作为构建工具或者框架或者脚本我们有必要深入了解一些,为我们更高效的构建自己工程打好基础,学习Gradle一定要自己学会查阅文档,我刚接触Gradle的时候build.gradle文件都不敢动一个冒号,生怕出问题,配置的什么完全不懂,没办法只能自己硬着头皮一点点学习,到现在不敢说精通起码很多方面我明白是干什么的了,还有一点我们不是在配置脚本是在写代码背后都有对应的类,好了,不啰嗦了。

本篇到此结束,希望对你有用。

 

第一时间获取最新博文可关注个人公众号,那里阅读体验更好:

 

转载于:https://www.cnblogs.com/leipDao/p/10385155.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/395101.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

javaweb 图书管理系统完整代码_看一名Java开发人员以红队思维五分钟审计一套代码(续)...

前言上篇文章的发布引起了很多读者的浏览&#xff0c;有很多读者也催更希望读到续集&#xff0c;作者也收获到读者的鼓励&#xff0c;说明这条路线对大家有帮助&#xff0c;是有意义的。所以&#xff0c;今天作者将继续阐述在审计Java代码时的思路。概述上篇文章所讲的SQL注入和…

爱立信数据分析解决方案抓住物联网发展机遇

爱立信在2016年1月6日至9日于美国拉斯维加斯举办的国际消费电子展&#xff08;CES&#xff09;上推出“用户和物联网数据分析”解决方案。该解决方案将能帮助运营商提高对用户和物联网终端的内部管理效率&#xff0c;同时探索跨越多个垂直领域的新型物联网应用。 用户和物联网数…

JAVA实现在面板中添加图表_Java 创建PowerPoint图表并为其添加趋势线

图表&#xff0c;是指将既得数据用图形的方式表示出来。在前文中我们介绍过如何使用Java程序来为Excel文档创建图表的方法。本文将通过使用Java程序来演示如何创建PowerPoint图表及为图表添加趋势线。趋势线的运用能够显示数据的变化趋势&#xff0c;同时能够帮助预测数据的未来…

idea设置中文界面_《英雄联盟手游》设置界面中文翻译图分享 外服汉化界面一览...

导读 英雄联盟手游目前以及正式上线了&#xff0c;不过现在的话是没有中文版的&#xff0c;只有外服&#xff0c;所有很多地方是看不懂的&#xff0c;也不明白的&#xff0c;这样的话就需要翻译了&#xff0c;具体要怎么设置会比较&#xff0c;相关的步骤是什么呢&#xff1f;下…

安卓10不支持qmc解码_官宣:安卓10已发布!21款手机已适配,小米华为率先支持...

随着今天凌晨谷歌 I/O 大会的召开&#xff0c;Android Q 也就是安卓10正式官宣&#xff0c;新版的安卓10加入了诸多新手势&#xff0c;从 Home 键设计上看&#xff0c;安卓10 的新版手势很大程度上有疑似借鉴 iOS 的手势&#xff0c;底部一个长条&#xff0c;作为 Home 用来返回…

linux系统编程之进程(七):system()函数使用【转】

本文转载自&#xff1a;http://www.cnblogs.com/mickole/p/3187974.html 一&#xff0c;system()理解 功能&#xff1a;system()函数调用“/bin/sh -c command”执行特定的命令&#xff0c;阻塞当前进程直到command命令执行完毕 原型&#xff1a; int system(const char *comma…

汉王云名片识别(SM)组件开发详解

大家好&#xff0c;最近在DeviceOne平台上做了一个汉王云名片识别的功能组件。下面把我开发过程给大家做一个分享&#xff0c;希望可以帮助到大家。 下面我把我的思路给大家讲解一下。1.找到我要集成的sdk&#xff0c;也就是汉王云名片的sdk下载&#xff08;android和ios&#…

[App Store Connect帮助]八、维护您的 App(4.2)查看评分与评论

您可以查看 App 的总评分或单个顾客评论。如有必要&#xff0c;您可以针对某条评论报告问题。 【注】顾客可以为您的 iOS 和 macOS App 评分并撰写评论&#xff0c;但只能为 Apple TVOS App 评分。 必要职能&#xff1a;“帐户持有人”职能、“管理”职能、“App 管理”职能、“…

visio studio json工具_《产品经理入门指南》彩蛋2:别技淫原型图!你的Visio和Balsamiq Mockup入门了吗?...

温馨提示&#xff1a;如果你有关于产品经理方面的任何问题&#xff0c;欢迎关注我的微信公众号与我互动。最新的免费系统的产品经理课程《老司机带你做产品》已经推出&#xff0c;请关注微信公众号&#xff1a;iamliuwenzhi很多产品新人刚开始工作时&#xff0c;喜欢一拿到需求…

mysql牵引例子_MySQL学习06(事务和索引)

事务概述什么是事务事务就是将一组SQL语句放在同一批次内去执行如果一个SQL语句出错,则该批次内的所有SQL都将被取消执行MySQL事务处理只支持InnoDB和BDB数据表类型事务的ACID原则原子性(Atomic)整个事务中的所有操作&#xff0c;要么全部完成&#xff0c;要么全部不完成&#…

android操作系统 真的吗_旋挖机培训学校真的能学会吗,旋挖钻机到底有哪些操作系统...

旋挖机培训学校真的能学会吗添加微&#xff1a;yywyyc 旋挖钻机到底有哪些操作系统【前言】很多使用旋挖钻机或者想要了解旋挖钻机的工程公司可能不是特别了解旋挖钻机本身的系统以及部件&#xff0c;本篇文章针对旋挖钻机的操作系统来给大家介绍一下&#xff0c;让大家了…

WebAPI 2参数绑定方法

简单类型参数 Example 1: Sending a simple parameter in the Url [RoutePrefix("api/values")] public class ValuesController : ApiController {// http://localhost:49407/api/values/example1?id2[Route("example1")][HttpGet]public string Get(int…

java怎么引入html文件路径_如何在public_html中读取文件但在域外?使用相对路径...

我正在尝试从我的(附加组件)域目录之外的目录中读取文件 . 这是我的目录结构&#xff1a;public_html /domain /file_read.phpfile_write.phpsensitive /file.dat虽然我能够使用“../sensitive/file.dat”写入敏感&#xff0c;但我无法使用相同的方法进行读取 . 有什么想法吗&a…

csv文件怎么转成excel_Java读写excel,excel转成json写入磁盘文件

pom读写excel主要的dependency<dependency> <groupId>org.apache.poigroupId> <artifactId>poiartifactId> <version>3.16version> dependency> <dependency> <groupId>org.apache.poigroupId> …

前端做CRM管理系统是做什么_代办行业的CRM客户关系管理系统应该是什么样子的?...

随着互联网的深耕细化&#xff0c;很多企业也在不断优化自己的办公方式&#xff0c;以优化企业的办公流程&#xff0c;提高企业的办事效率。因此实现办公自动化&#xff0c;或者说实现数字化办公就需要逐渐提上日程。今天给大家讲讲可以帮助代办行业实现办公自动化的产品&#…

蓝牙 sig base uuid_蓝牙模块采用陶瓷天线和PCB天线的区别

一、陶瓷天线陶瓷天线是一种适合于蓝牙设备使用的小型化天线,又分为块状陶瓷天线和多层陶瓷天线。陶瓷天线占用空间很小、性能比较好&#xff1b; 带宽窄&#xff0c;比较难做到多频段&#xff1b;有效提高主板的整合度&#xff0c;并可降低天线对ID的限制&#xff1b;需要在主…

app启动页自动跳转源码_关于移动端App启动页的策划方案

App启动页是指app在启东时需要加载必要的运行环境和配置&#xff0c;在这个过程中提示用户等待的一个过渡页面。在产品经理眼里启动页是app给予用户重要的第一印象&#xff1b;也是App最重要的黄金页面之一&#xff0c;所有用户100%都会看到的页面。启动页适合用来做以下几个事…

java 如何排查内存溢出_java 内存溢出排查

测试代码&#xff0c;如下示例&#xff1a;import java.util.ArrayList;import java.util.List;/*** Description 测试内存溢出, 启动时设置参数&#xff0c;最大堆内存为1m, 内存溢出时dump出内存文件 -Xmx1m -XX:HeapDumpOutOfMemoryError* Author luzy* Date 2018/10/5 11:0…

《企业级ios应用开发实战》一2.2 iOS框架介绍

2.2 iOS框架介绍 iOS衍生自Mac OS X的成熟内核&#xff0c;但iOS操作系统更紧凑和高效&#xff0c;支持iPhone和iPod Touch的硬件。iOS继承了Mac OS X的风格&#xff0c;包括&#xff1a;统一的OS X 内核&#xff0c;针对网络的BSD套接字&#xff0c;以及Objective-C和C/C编译器…

python的opencv 车牌识别 开源_毕节进出口车牌识别系统怎么样

毕节进出口车牌识别系统怎么样 gzheu8il毕节进出口车牌识别系统怎么样 系统拓扑图如下&#xff1a;该系统以社区中心机房为枢纽&#xff0c;有机的将智慧家居住户、社区数字化服务、物业数字化管理、社区智能化管理结合起来&#xff0c;真正的实现&#xff1a;住户与住户之间的…