声明:本文来自汪磊的博客,转载请注明出处
可关注个人公众号,那里更新更及时,阅读体验更好:
友情提示由于文章是从个人公众号拷贝过来整理的,发现图片没有正常显示,没关注公众号的同学可通过如下链接查看: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] = true
5 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 7
2 item: 5
3 item: werw
4 item: true
5 item: 34
6 item: null
7 item: null
8 item: true
9 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 //输出14
7 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 //Range
2 def Range1 = 1..10//包括10
3 println Range1.contains(10)
4 println Range1.from +"__"+Range1.to
5 def Range2 = 1..<10//不包括10
6 println Range2.contains(10)
7 println Range2.from +"__"+Range2.to
8 //遍历
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 first
4 def last
5 }
6
7 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 ..........
2
3 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{
3
4 project ->
5 println "root module afterEvaluate -----> $project.name"
6 }
7
8 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{
5
6 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"
3
4 。。。。。。。。。。。
5
6 afterEvaluate{
7
8 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"
3
4 。。。。。。。。。。。。。。。
5
6 afterEvaluate{
7
8 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..."
3
4 doFirst{
5 println ">>>>>>>>>>>>>>>>aTask doFirst..."
6 }
7
8 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..."
4
5 doFirst{
6 println ">>>>>>>>>>>>>>>>aTask doFirst..."
7 }
8
9 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 {
2
3 String msg = 'default'
4
5 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 {
2
3 @Input //指明输入
4 String msg = 'default'
5
6 @OutputFile //指明输出
7 File file
8
9 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'
3
4 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'
3
4 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{
2
3 String msg = 'default'
4
5 MyTask() {
6 group '自定义任务'
7 description '任务描述'
8 }
9
10 @TaskAction
11 void run() {
12 println "MyTask2__$msg"
13 }
14 }
新建了任务类,然后我们就可以在创建这个任务了:
1 class Plugin1 implements Plugin<Project>{
2
3 @Override
4 void apply(Project project) {
5 println "我是自定义插件"
6
7 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 28
3 defaultConfig {
4 applicationId "com.wanglei55.gradlelearn"
5 minSdkVersion 15
6 targetSdkVersion 28
7 versionCode 1
8 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>{
2
3 @Override
4 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文件都不敢动一个冒号,生怕出问题,配置的什么完全不懂,没办法只能自己硬着头皮一点点学习,到现在不敢说精通起码很多方面我明白是干什么的了,还有一点我们不是在配置脚本是在写代码背后都有对应的类,好了,不啰嗦了。
本篇到此结束,希望对你有用。
第一时间获取最新博文可关注个人公众号,那里阅读体验更好: