gradle 学习笔记

gradle 学习笔记

每次使用Android studio,gradle构建时输出一大堆看不懂得日志,所以专门学习gradle,此文是学习的笔记.

Groovy 简单语法学习

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
println "hello groovy"

def variable1 = 1 //可以不使用分号结尾
def double money = 30.25 //定义有自定类型的变量

println variable1
println money

//有返回值的函数
String getString(){
return "I am a string"
}
getString()


def nonReturnTypeFunc(){
"getSomething return value" //如果这是最后一行代码,则返回类型为String
1000 //如果这是最后一行代码,则返回类型为Integer
}
nonReturnTypeFunc()


String getString(weight){
//双引号""的内容则和脚本语言的处理有点像,如果字符中有$号的话,则它会$表达式先求值
//单引号,不对$符号进行转义
return "I am a weight is $weight kg"
}
getString(75)

//三个引号'''xxx'''中的字符串支持随意换行
def multieLines = '''
哈哈哈
嘻嘻嘻
呜呜呜
'''


print(multieLines)

//基本数据类型
//作为动态语言,Groovy世界中的所有事物都是对象。所以,int,boolean这些Java中的基本数据类型,
//在Groovy代码中其实对应的是它们的包装数据类型。比如int对应为Integer,boolean对应为Boolean
def double num = 1.00
def boolean bool = false
println num.getClass()
println bool.getClass()



//容器

//List 链表,其底层对应Java中的List接口,一般用ArrayList作为真正的实现类。
def aList = [5,'string',true,0.55] //List由[]定义,其元素可以是任何对象
println aList[2]
println aList.size

//变量存取:可以直接通过索引存取,而且不用担心索引越界。
//如果索引超过当前链表长度,List会自动往该索引添加元素
aList[100] = 100
println aList[100]
println aList.size //结果是101


//Map:键-值表,其底层对应Java中的LinkedHashMap。
//Map由[:]定义,注意其中的冒号。冒号左边是key,右边是Value。key必须是字符串,
//value可以是任何对象。另外,key可以用''或""包起来,也可以不用引号包起来
def aMap = ['key1':'value1','key2':true]

println aMap.key1
println aMap['key2'] //aMap[key2] 会报错
aMap.anotherkey = "i am map"//为map添加新元素
println aMap.size() // 3个


//Range:范围,它其实是List的一种拓展。
//Range类型的变量 由begin值+两个点+end值
def aRange = 1..5 //表示左边这个aRange包含1,2,3,4,5这5个值
println aRange.size() //5
println aRange.getFrom() //1
println aRange.getTo() //5 方法看groovy api文档

//闭包 Closure,很总要 gradle中很多地方用到了

def aClosure = { //闭包是一段代码,所以需要用花括号括起来..
Stringparam1, int arg -> //这个箭头很关键。箭头前面是参数定义,箭头后面是代码
println "Stringparam1 is $Stringparam1, $arg" //这是代码,最后一句是返回值,也可以使用return,和Groovy中普通函数一样
}
aClosure("xx",2)
aClosure.call("yy",100)
//无参闭包
def noArgumnetClouser = {
//如果闭包没定义参数的话,则隐含有一个参数,这个参数名字叫it,
//和this的作用类似。it代表闭包的参数。
"我是一个无参闭包 $it" //直接是纯代码没有 ->符号
}
def resultClouser = noArgumnetClouser()
print resultClouser

def noParamClosure = { -> true }
//noParamClosure ("test") //会报错

//闭包在Groovy中大量使用
//比如 List的 each方法 public static <T> List<T> each(List<T> self, Closure closure)
def iamList = [1,2,3,4,5] //定义一个List
iamList.each{ //调用它的each,each是个函数,圆括号去哪了?
println it
}

//Groovy中,当函数的最后一个参数是闭包的话,可以省略圆括号
def testClosure(Closure closure){
//do something
closure() //调用闭包
}

testClosure{
println "i am in closure "
}


//对Map的findAll而言,Closure可以有两个参数。findAll会将Key和Value分别传进去。
//并且,Closure返回true,表示该元素是自己想要的。返回false表示该元素不是自己要找的
def aMap2 = ['name':'qiu','age':23,'isMan':true]

def results = aMap2.findAll{
key,value ->
println "key=$key , value=$value"
}

什么是groovy脚本

创建test.groovy文件,
写入println 'Hello Groovy!',
然后使用groovyc -d classes test.groovy进行编译成class文件
使用jd-gui反编译会发现test.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class test extends Script
{

public test()
{

test this;
CallSite[] arrayOfCallSite = $getCallSiteArray();
}

public test(Binding context)
{

super(context);
}

public static void main(String[] args)
{

CallSite[] arrayOfCallSite = $getCallSiteArray();
arrayOfCallSite[0].call(InvokerHelper.class, test.class, args);
}

public Object run()
{

CallSite[] arrayOfCallSite = $getCallSiteArray(); return arrayOfCallSite[1].callCurrent(this, "Hello Groovy!"); return null;
}
}
  • test.groovy被转换成了一个test类,继承Script
  • 每一个脚本都会生成一个static main函数。这样,当我们groovy test.groovy的时候,其实就是用java去执行这个main函数
  • 脚本中的所有代码都会放到run函数中。比如,println ‘Hello Groovy!’,这句代码实际上是包含在run函数里的。
  • 如果脚本中定义了函数,则函数会被定义在test类中。

脚本中的变量和作用域

groovy重要API

io操作api链接

File
InputStream
OutputStream
Reader
Writer
Path

读文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def targetFile = new File("/home/xx")
//读取每一行
targetFile.eachLine{ //eachLine参数是一个Closure
String oneLine ->
println oneLine
}
//文件内容一次性读出,返回类型为byte[]
targetFile.getBytes()

//获取流
def ism = targetFile.newInputStream()
//操作ism,最后记得关掉
ism.close

//使用闭包操作inputStream
//以后在Gradle里会常看到这种搞法
targetFile.withInputStream{
ism -> //操作ism. 不用close。Groovy会自动替你close
...
}

写文件

1
2
3
4
5
6
7
8
9
//copy文件
def srcFile = new File(源文件名)
def targetFile = new File(目标文件名)
targetFile.withOutputStream{ os->
srcFile.withInputStream{ ins->
os << ins //利用OutputStream的<<操作符重载,完成从inputstream到OutputStream
//的输出
}
}

xml操作

test.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<response version-api="2.0">  
<value>
<books>
<book available="20" id="1">
<title>Don Xijote</title>
<author id="1">Manuel De Cervantes</author>
</book>
<book available="14" id="2">
<title>Catcher in the Rye</title>
<author id="2">JD Salinger</author>
</book>
<book available="13" id="3">
<title>Alice in Wonderland</title>
<author id="3">Lewis Carroll</author>
</book>
<book available="5" id="4">
<title>Don Xijote</title>
<author id="4">Manuel De Cervantes</author>
</book>
</books>
</value>
</response>

GPath伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import groovy.util.XmlSlurper  //解析XML时候要引入这个groovy的package  
//第一步,创建XmlSlurper类
def xparser = new XmlSlurper()
def targetFile = new File("test.xml")
//轰轰的GPath出场
GPathResult gpathResult =xparser.parse(targetFile)

//开始玩test.xml。现在我要访问id=4的book元素。
//下面这种搞法,gpathResult代表根元素response。通过e1.e2.e3这种
//格式就能访问到各级子元素....
def book4 = gpathResult.value.books.book[3]
//得到book4的author元素
def author = book4.author
//再来获取元素的属性和textvalue
assert author.text() == ' Manuel De Cervantes '
获取属性更直观
author.@id == '4' 或者 author['@id'] == '4'
属性一般是字符串,可通过toInteger转换成整数
author.@id.toInteger() == 4
好了。GPath就说到这。再看个例子。我在使用Gradle的时候有个需求,就是获取AndroidManifest.xml版本号(versionName)。有了GPath,一行代码搞定,请看:
def androidManifest = newXmlSlurper().parse("AndroidManifest.xml")
println androidManifest['@android:versionName']
或者
println androidManifest.@'android:versionName'

Groovy的Bean和闭包的delegate机制

Bean的概念

Groovy中的Bean和Java中的Bean有一个很大的不同,即Groovy为每一个字段都会自动生成getter和setter,并且我们可以通过像访问字段本身一样调用getter和setter

1
2
3
4
5
6
7
class User {
private String name
}

def bean = new User()
bean.name = 'this is name'
println bean.name

User只定义了一个私有的name属性,并没有getter和setter。但是在使用时,我们可以直接对name进行访问,无论时读还是写。事实上,我们并不是在直接访问name属性,当我们执行”bean.name = ‘this is name’”时,我们实际调用的是”bean.setName(‘this is name’)”,而在调用”println bean.name”时,我们实际调用的是”println bean.getName()”。这里的原因在于,Groovy动态地为name创建了getter和setter,采用像直接访问的方式的目的是为了增加代码的可读性,使它更加自然,而在内部,Groovy依然是在调用setter和getter方法

闭包的delegate机制

请参考这篇文章,本人没看懂…

gradle 部分

gradle官网
gradle官方文档
很重要的gradle文档
User Guide是介绍Gradle的一本书
DSL Reference是Gradle API的说明

Gradle中,每一个待编译的工程都叫一个Project。每一个Project在构建的时候都包含一系列的Task。比如一个Android APK的编译可能包含:Java源码编译Task、资源编译Task、JNI编译Task、lint检查Task、打包生成APK的Task、签名Task等。

一个Project到底包含多少个Task,其实是由编译脚本指定的插件决定。插件是什么呢?插件就是用来定义Task,并具体执行这些Task的东西。

Gradle是一个框架,作为框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。比如编译Java有Java插件,编译Groovy有Groovy插件,编译Android APP有Android APP插件,编译Android Library有Android Library插件

多工程项目 Multi-Projects build

  • 在项目根目录下创建一个settings.gradle的文件,include所有工程
  • 每一个Library和每一个App都是单独的Project.
  • 每一个Project在其根目录下都需要有一个build.gradle。build.gradle文件就是该Project的编译脚本,类似于Makefile。
  • 一个Project是由若干tasks来组成的,当gradle xxx的时候,实际上是要求gradle执行xxx任务。这个任务就能完成具体的工作。

settings.build模板

1
2
3
4
5
include ':app', ':myLib', ':aaLib'

//如果lib工程放到一个ThirdPart文件夹下,可以配置
project(':myLib').projectDir = new File('ThirdPart/myLib')
project(':aaLib').projectDir =new File('ThirdPart/aaLib')

gradle工作流程

Gradle工作包含三个阶段,每个阶段之后可以添加自定义Hook(钩子)代码

  1. Initiliazation phase 初始化阶段
    • 对于multi-project build而言,就是执行settings.gradle
  2. Configration phase 设置阶段
    • 目标是解析每个project中的build.gradle.比如multi-project build例子中,解析每个子目录中的build.gradle
    • Configuration阶段完了后,整个build的project以及内部的Task关系就确定了.一个Project包含很多Task,每个Task之间有依赖关系,Configuration会建立一个有向图来描述Task之间的依赖关系
    • 可以在Task关系图建立好后,执行一些操作(添加一个HOOK)。
  3. Execution phase 执行阶段
    • 执行完成,也可以加上hook

gradle 主要api

Gradle基于Groovy,Groovy又基于Java。所以,Gradle执行的时候和Groovy一样,会把脚本转换成Java对象。Gradle主要有三种对象,这三种对象和三种不同的脚本文件对应,在gradle执行的时候,会将脚本转换成对应的对端:

  • Gradle对象:当我们执行gradle xxx或者什么的时候,gradle会从默认的配置脚本中构造出一个Gradle对象。在整个执行过程中,只有这么一个对象。Gradle对象的数据类型就是Gradle。我们一般很少去定制这个默认的配置脚本。
  • Project对象: 每一个build.gradle会转换成一个Project对象
  • Settings对象:每一个settings.gradle都会转换成一个Settings对象
Type of script Delegates to instance of
Build script Project
Init script Gradle
Settings script Settings

https://docs.gradle.org/current/dsl/此文档中都有对应的说明

gradle task

官方文档位置

在我们创建Android工程的时候,在project级别的build.gradle会出现下面的任务

1
2
3
4
//任务,在other下可以看到,使用 gradle tasks --all进行查看
task clean(type: Delete) { //Property为
delete rootProject.buildDir
}
1
2
3
4
5
task myTask
task myTask { configure closure }
task myType << { task action } //<< doLast的缩写
task myTask(type: SomeType)
task myTask(type: SomeType) { configure closure }
  • task关键字,就是一个groovy中的方法,大括号传里面其实是一个Closure(闭包).
  • 一般都是project的task()方法创建,并且所有的Task都存放在Project的TaskContainer中
  • 一个Task包含若干Action。所以,Task有doFirst和doLast两个函数,用于添加需要最先执行的Action和需要和需要最后执行的Action。Action就是一个闭包。
  • task创建的时候可以指定Type,通过type:名字表达,其实就是告诉Gradle,这个新建的Task对象会从哪个基类Task派生, 比如,Gradle本身提供了一些通用的Task,Copy、Delete等,比如上面就Delete任务
  • 当我们使用 task myTask{ xxx}的时候。花括号是一个closure。这会导致gradle在创建这个Task之后,返回给用户之前,会先执行closure的内容
  • 当我们使用task myTask << {xxx}的时候,我们创建了一个Task对象,同时把closure做为一个action加到这个Task的action队列中,并且告诉它“最后才执行这个closure”。

doFirst 和Delete doLast

1
2
3
4
5
6
7
8
9
task myTask << { 
println "doLast test!"
}
//等效于上面
task myTask2(){
doLast{
println "doLast test!"
}
}

‘<<’ 表示 Task.doLast(Closure)

dependsOn依赖关系

1
2
3
4
5
6
7
8
task taskB() << {
println 'taskB run'
}
task taskA(dependsOn:taskB) << {
println 'taskA run'
}
//也可以定义完了
taskA.dependsOn taskB

执行任务A时会依赖任务B,会先执行任务B

通过TaskContainer的create()方法创建Task
每一个project都可以使用TaskContainer来创建task

1
2
3
tasks.create(name: 'taskC') << {
println 'taskC run'
}

android多渠道打包

多渠道打包配置的例子

参考:
infoQ 深入理解gradle
吴小龙同学
http://ask.android-studio.org/?/article/37
google官方studio文档
gradle Task详解