声明:本文来自汪磊的博客,转载请注明出处
可关注个人公众号,那里更新更及时,阅读体验更好:
友情提示由于文章是从个人公众号拷贝过来整理的,发现图片没有正常显示,没关注公众号的同学可通过如下链接查看: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
2
3
4
5 zhangsan
6 12
7
8
9 lisi
10 23
11
12
13
14
复制代码
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有它自己的规则,怎么配置,配置什么都在文档中有对应规定,我们