美文网首页
Gradle基本学习路线总结

Gradle基本学习路线总结

作者: PeytonWu | 来源:发表于2017-08-04 15:57 被阅读0次

    Groovy :是一种动态语言。

    1:这种语言比较有特点,它和 Java 一样,也运行于 Java 虚拟机中。简单粗暴点儿看,你可以认为 Groovy 扩展了 Java 语言,二者关系如下图

    。这点和Kotlin很像都是将源文件,先转成jvm能识别的字节码文件。

    2:除了语言和 Java 相通外,Groovy 有时候又像一种脚本语言。当执行 Groovy 脚本时,Groovy 会先将其编译成 Java 类字节码,然后通过 Jvm 来执行这个 Java 类。

    一:Groovy 语言学习

    1:Groovy语言特性

    1.1:Groovy 注释标记和 Java 一样,支持//或者/**/

    1.2:Groovy 语句可以不用分号结尾。

    每行代码不用加分号外,Groovy 中函数调用的时候还可以不加括号,如

    println("test") ---> println "test"

    1.3 属性相关

    Groovy 中支持动态类型,即定义变量的时候可以不指定其类型。Groovy 中,变量定义可以使用关键字 def。注意,虽然 def 不是必须的,但是为了代码清晰,建议还是使用 def 关键字

    def variable1 = 1  //可以不使用分号结尾

    def varable2 = "I am a person"

    def  int x = 1  //变量定义时,也可以直接指定类型

    1.4函数相关

    1.4.1 函数定义时,参数的类型也可以不指定。比如

    String testFunction(arg1,arg2){//无需指定参数类型...}

    1.4.2:除了变量定义可以不指定类型外,Groovy 中函数的返回值也可以是无类型的。

    可以不使用 return xxx 来设置 xxx 为函数返回值。如果不使用 return 语句的话,则函数里最后一句代码的执行结果被设置成返回值。比如:

    //无类型的函数定义,必须使用 def 关键字

    def getSomething(){

    "getSomething return value" //如果这是最后一行代码,则返回类型为 String

    1000 //如果这是最后一行代码,则返回类型为 Integer

    }

    //如果指定了函数返回类型,则可不必加 def 关键字来定义函数

    String  getString(){return "I am a string"}

    1.5 Groovy 中的数据类型

    除了java定义的数据类型外,groovy有2种比较特殊的数据类型

    1.5.1Groovy 中的容器类。(其实只是对java容器进行了拓展,相对比较好理解)

    Groovy 中的容器类很简单,就三种:

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

    Map:键-值表,其底层对应 Java 中的 LinkedHashMap。

    Range:范围,它其实是 List 的一种拓展。

    1.List 类

    变量定义:List 变量由[]定义,比如

    def aList = [5,'string',true] //List 由[]定义,其元素可以是任何对象

    变量存取:可以直接通过索引存取,而且不用担心索引越界。如果索引超过当前链表长度,List 会自动 往该索引添加元素

    assert aList[1] == 'string'

    assert aList[5] == null //第 6 个元素为空

    aList[100] = 100  //设置第 101 个元素的值为 10

    assert aList[100] == 100

    那么,aList 到现在为止有多少个元素呢?

    println aList.size  ===>结果是 101

    2.Map 类

    容器变量定义

    变量定义:Map 变量由[:]定义,比如

    def aMap = ['key1':'value1','key2':true]

    Map 由[:]定义,注意其中的冒号。冒号左边是 key,右边是 Value。key 必须是字符串,value 可以是任何对象。另外,key 可以用''或""包起来,也可以不用引号包起来。比如

    def aNewMap = [key1:"value",key2:true] //其中的 key1 和 key2 默认被

    处理成字符串"key1"和"key2" 不过 Key 要是不使用引号包起来的话,也会带来一定混淆,比如

    def key1="wowo"

    def aConfusedMap=[key1:"who am i?"]

    aConfuseMap 中的 key1 到底是"key1"还是变量 key1 的值“wowo”?显然,答案是字符串"key1"。如果要是"wowo"的话,则 aConfusedMap 的定义必须设置成:

    def aConfusedMap=[(key1):"who am i?"]

    Map 中元素的存取更加方便,它支持多种方法:

    println aMap.keyName    <==这种表达方法好像 key 就是 aMap 的一个成员变量一样

    println aMap['keyName'] <==这种表达方法更传统一点

    aMap.anotherkey = "i am map"  <==为 map 添加新元素

    3.Range 类

    Range 是 Groovy 对 List 的一种拓展,变量定义和大体的使用方法如下:

    def aRange = 1..5  <==Range 类型的变量 由 begin 值+两个点+end 值表示

    左边这个 aRange 包含 1,2,3,4,5 这 5 个值

    如果不想包含最后一个元素,则

    def aRangeWithoutEnd = 1..<5  <==包含 1,2,3,4 这 4 个元素

    println aRange.from

    println aRange.to

    先定位到 Range 类。它位于 groovy.lang 包中:

    有了 API 文档,你就可以放心调用其中的函数了。不过,不过,不过:我们刚才代码中用到了 Range.from/to 属性值,但翻看 Range API 文档的时候,其实并没有这两个成员变量。下图 是 Range 的方法

    文档中并没有说明 Range 有 from 和 to 这两个属性,但是却有 getFrom 和 getTo 这两个函数。原来:

    根据 Groovy 的原则,如果一个类中有名为 xxyyzz 这样的属性(其实就是成员变量),Groovy 会自动为它添加 getXxyyzz 和 setXxyyzz 两个函数,用于获取和设置 xxyyzz 属性值。

    注意,get 和 set 后第一个字母是大写的

    所以,当你看到 Range 中有 getFrom 和 getTo 这两个函数时候,就得知道潜规则下,Range 有 from 和 to 这两个属性。当然,由于它们不可以被外界设置,所以没有公开 setFrom 和 setTo 函数。

    1.5.2 闭包(英文叫 Closure,是 Groovy 中非常重要的一个数据类型或者说一种概念了,脚本中最经常使用)

    闭包,是一种数据类型,它代表了一段可执行的代码。其外形如下:

    def aClosure = {//闭包是一段代码,所以需要用花括号括起来..

    String param1, int param2 ->  //这个箭头很关键。箭头前面是参数定义,箭头后面是代码

    println "this is code" //这是代码,最后一句是返回值,

    //也可以使用 return,和 Groovy 中普通函数一样

    }

    简而言之,Closure 的定义格式是:

    def xxx = {paramters -> code}  //或者

    def xxx = {无参数,纯 code}  这种 case 不需要->符号

    闭包中注意点a

    如果闭包没定义参数的话,则隐含有一个参数,这个参数名字叫 it,和 this 的作用类似。it 代表闭包的参数。如项目中用到的

    再如:

    def greeting = { "Hello, $it!" }

    assert greeting('Patrick') == 'Hello, Patrick!'

    等同于:

    def greeting = { it -> "Hello, $it!" }

    assert greeting('Patrick') == 'Hello, Patrick!'

    但是,如果在闭包定义时,采用下面这种写法,则表示闭包没有参数!

    def noParamClosure = { -> true }

    这个时候,我们就不能给 noParamClosure 传参数了!

    noParamClosure ("test")  <==报错

    注意点b

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

    public static List each(List self, Closure closure)

    上面这个函数表示针对 List 的每一个元素都会调用 closure 做一些处理。这里的 closure,就有点回调函数的感觉。但是,在使用这个 each 函数的时候,我们传递一个怎样的 Closure 进去?比如:

    def iamList = [1,2,3,4,5]  //定义一个 List

    iamList.each{  //调用它的 each,这段代码的格式看不懂了吧?each 是个函数,圆括号去哪了?

    println it

    }

    特点经常出现(这就是1.2中指出的函数省略括号特性),因为以后在 Gradle 中经常会出现图 7 这样的代码:

    经常碰见图 7 这样的没有圆括号的代码。省略圆括号虽然使得代码简洁,看起来更像脚本语言,以 doLast 为例,完整的代码应该按下面这种写法:

    doLast({

    println 'Hello world!'

    })

    有了圆括号,就知道 doLast 只是把一个 Closure 对象传了进去。很明显,它不代表这段脚本解析到 doLast 的时候就会调用 println 'Hello world!' 。但是把圆括号去掉后,就感觉好像 println 'Hello world!'立即就会被调用一样!

    注意点c 如何确定 Closure 的参数

    能写成下面这样吗?

    iamList.each{String name,int x ->

    return x

    }  //运行的时候肯定报错!

    所以,Closure 虽然很方便,但是它一定会和使用它的上下文有极强的关联。要不,作为类似回调这样的东西,我如何知道调用者传递什么参数给 Closure 呢?

    此问题如何破解?只能通过查询 API 文档才能了解上下文语义。比如下图 :

    图 中:

    each 函数说明中,将给指定的 closure 传递 Set 中的每一个 item。所以,closure 的参数只有一个。

    findAll 中,绝对抓瞎了。一个是没说明往 Closure 里传什么。另外没说明 Closure 的返回值是什么.....。

    对 Map 的 findAll 而言,Closure 可以有两个参数。findAll 会将 Key 和 Value 分别传进去。并且,Closure 返回 true,表示该元素是自己想要的。返回 false 表示该元素不是自己要找的。示意代码如图  所示:

    Closure 的使用有点坑,很大程度上依赖于你对 API 的熟悉程度,所以最初阶段,SDK 查询是少不了的

    1.5.3 String 特点

    Groovy 对字符串支持相当强大,充分吸收了一些脚本语言的优点:

    1  单引号''中的内容严格对应 Java 中的 String,不对$符号进行转义

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

    2  双引号""的内容则和脚本语言的处理有点像,如果字符中有$号的话,则它会$表达式先求值。

    def doubleQuoteWithoutDollar = "I am one dollar" //输出 I am one dollar

    def x = 1

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

    3 三个引号'''xxx'''中的字符串支持随意换行 比如

    def multieLines = ''' begin

    line  1

    line  2

    end '''

    NOTE:这就是为什么gradle脚本中如果 我们想用$占位符来输入版本时,必须用双引号testCompile "com.meiyou:peroid.base:${PERIOD_BASE_VERSION}" ,而如果强制指定版本,单引号和双引号都可以

    1.6 Groovy脚本

    Groovy 中可以像 Java 那样写 package,然后写类。比如在文件夹 com/cmbc/groovy/目录中放一个文件,叫 Test.groovy,如图 所示:

    如果不声明 public/private 等访问权限的话,Groovy 中类及其变量默认都是 public 的,这点与java有所不同。

    Java 中,我们最熟悉的是如上图。但是我们在 Java 的一个源码文件中,不能不写 class(interface 或者其他....),而 Groovy 可以像写脚本一样,把要做的事情都写在 xxx.groovy 中,而且可以通过 groovy xxx.groovy 直接执行这个脚本,如


    test.groovy 的运行结果是:

    println 'Groovy world!'

    Groovy 把它转换成这样的 Java 类,下面将进行验证

    执行 groovyc -d classes test.groovy

    groovyc 是 groovy 的编译命令,-d classes 用于将编译得到的 class 文件拷贝到 classes 文件夹下

    是 test.groovy 脚本转换得到的 java class。用 jd-gui 反编译它的代码:

    2:Groovy常用API介绍(Groovy 的 API 文档位于http://www.groovy-lang.org/api.html

    2.1文件 I/O 操作

    从 Groovy 的文件 I/O 操作掌握下Groovy常用api。比 Java 看起来简单,但要理解起来其实比较难。尤其是当你要自己查 SDK 并编写代码的时候。

    整体说来,Groovy 的 I/O 操作是在原有 Java I/O 操作上进行了更为简单方便的封装,并且使用 Closure 来简化代码编写。主要封装了如下一些了类:

    2.1.1.读文件

    Groovy 中,文件读操作简单到令人发指:

    def targetFile = new File(文件名) <==File 对象还是要创建的。

    然后打开http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/File.html看看 Groovy 定义的 API:

    1 读该文件中的每一行:eachLine 的唯一参数是一个 Closure。Closure 的参数是文件每一行的内容

    其内部实现肯定是 Groovy 打开这个文件,然后读取文件的一行,然后调用 Closure...

    targetFile.eachLine{

    String oneLine ->

    println oneLine

    2 直接得到文件内容

    targetFile.getBytes() <==文件内容一次性读出,返回类型为 byte[] 注意前面提到的 getter 和 setter 函数,这里可以直接使用 targetFile.bytes //....

    3 使用 InputStream.InputStream 的 SDK 在http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.html

    def ism =  targetFile.newInputStream()

    //操作 ism,最后记得关掉

    ism.close

    4 使用闭包操作 inputStream,以后在 Gradle 里会常看到这种搞法

    targetFile.withInputStream{ ism ->

    操作 ism. 不用 close。Groovy 会自动替你 close

    }

    确实够简单,令人发指。我当年死活也没找到 withInputStream 是个啥意思。所以,请各位开发者牢记 Groovy I/O 操作相关类的 SDK 地址:

    java.io.File:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/File.htmljava.io.InputStream:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.html

    java.io.OutputStream:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/OutputStream.htmljava.io.Reader:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Reader.htmljava.io.Writer:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Writer.htmljava.nio.file.Path:http://docs.groovy-lang.org/latest/html/groovy-jdk/java/nio/file/Path.html

    2.2.2.写文件

    和读文件差不多。不再啰嗦。这里给个例子,如何 copy 文件。

    def srcFile = new File(源文件名)

    def targetFile = new File(目标文件名)

    targetFile.withOutputStream{ os->

    srcFile.withInputStream{ ins->

    os << ins  //利用 OutputStream 的<<操作符重载,完成从 inputstream 到 OutputStream

    //的输出

    }

    }

    关于 OutputStream 的<<操作符重载,查看 SDK 文档后可知:

    ...

    NOTE:Groovy及gradle中的 << 并不是类似 C++中的插入运算符,而是相应对象的leftShift函数。如项目中


    其实是做了两件事(将在gradle部分析)

    1:调用project的task函数为project增加一个task 

    2:调用 task对象的leftShift函数,传入一个clouse作为参数,而task的leftShift函数其实和dolast是一个作用

    所以不同对象的不同<<符号是不一样的

    XML 操作

    除了 I/O 异常简单之外,Groovy 中的 XML 操作也极致得很。Groovy 中,XML 的解析提供了和 XPath 类似的方法,名为 GPath。这是一个类,提供相应 API。关于 XPath,请脑补https://en.wikipedia.org/wiki/XPath

    GPath 功能包括:给个例子好了,来自 Groovy 官方文档。

    test.xml 文件:

    Don Xijote

    Manuel De Cervantes

    Catcher in the Rye

    JD Salinger

    Alice in Wonderland

    Lewis Carroll

    Don Xijote

    Manuel De Cervantes

    现在来看怎么玩转 GPath:

    //第一步,创建 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

    使用 Gradle 的时候有个需求,就是获取 AndroidManifest.xml 版本号(versionName)。有了 GPath,一行代码搞定,请看:

    def androidManifest = new XmlSlurper().parse("AndroidManifest.xml")

    println androidManifest['@android:versionName']

    或者

    println androidManifest.@'android:versionName'

    groovy既然是一门编程语言,就能做很多事,本文只是列举常用及学习gradle中部分必须了解的知识

    二:gradle相关学习

     Gladle 可以理解成基G于Groovy的一套框架,同时也是DSL。 

    1:框架中常用概念介绍

    https://docs.gradle.org/current/dsl/

    https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html

    这2个文档非常重要重点,介绍了gradle框架学习中必须掌握的DS。

    Gradle 主要有三种对象,这三种对象和三种不同的脚本文件对应,在 gradle 执行的时候,会将脚本转换成对应的对端:

    Gradle 对象:当我们执行 gradle xxx 或者什么的时候,gradle 会从默认的配置脚本中构造出一个 Gradle 对象。在整个执行过程中,只有这么一个对象。Gradle 对象的数据类型就是 Gradle。我们一般很少去定制这个默认的配置脚本。

    Project 对象:每一个 build.gradle 会转换成一个 Project 对象。

    Settings 对象:显然,每一个 settings.gradle 都会转换成一个 Settings 对象。

    脚本中除了可以用到的delegate object外还有Scriptinterface 的api,因为:

    而具体一个脚本中能有多少方法及多少属性可用,主要来源于以下几个方面


    每一个对象都有对应的API,具体可以点开 api文档进行查阅。

    2:gradle命令介绍 

    点开AS gradle窗口即可看到目前项目中能执行的task(当然也可执行tasks任务查看。注:不包含引入的插件所 定义的可执行task,如需查看所有需加上all参数执行),执行gradle +taskName或者右键点击对应task即可执行对应task ,如执行projects,即可看到项目相应工程信息

    3:gradle工作流程

    Gradle 工作大概包含三个阶段:

    首先是初始化阶段。对我们前面的 multi-project build 而言,就是执行 settings.gradle

    Initiliazation phase 的下一个阶段是 Configration 阶段。

    Configration 阶段的目标是解析每个 project 中的 build.gradle。比如 multi-project build 例子中,解析每个子目录中的 build.gradle。在这两个阶段之间,我们可以加一些定制化的 Hook。这当然是通过 API 来添加的。

    Configuration 阶段完了后,整个 build 的 project 以及内部的 Task 关系就确定了。一个 Project 包含很多 Task,每个 Task 之间有依赖关系。Configuration 会建立一个有向图来描述 Task 之间的依赖关系。所以,我们可以添加一个 HOOK,即当 Task 关系图建立好后,执行一些操作。

    4:gradle常用api介绍(官方文档位置 https://docs.gradle.org/current/javadoc/)

    app 有一个特点。它有三个版本,分别是 debug、release 和 demo。这三个版本对应的代码都完全一样,但是在运行的时候需要从 assets/runtime_config 文件中读取参数。参数不同,则运行的时候会跳转到 debug、release 或者 demo 的逻辑上。

    引入 gradle 后,我们该如何处理呢?

    解决方法是:在编译 build、release 和 demo 版本前,在 build.gradle 中自动设置 runtime_config 的内容。代码如下所示:

    [build.gradle]

    apply plugin: 'com.android.application'  //加载 APP 插件

    //加载 utils.gradle

    apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"

    //buildscript 设置 android app 插件的位置

    buildscript {

    repositories { jcenter() }

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

    }

    //android ScriptBlock

    android {

    compileSdkVersion gradle.api

    buildToolsVersion "22.0.1"

    sourceSets{ //源码设置 SB

    main{

    manifest.srcFile 'AndroidManifest.xml'

    jni.srcDirs = []

    jniLibs.srcDir 'libs'

    aidl.srcDirs=['src']

    java.srcDirs=['src']

    res.srcDirs=['res']

    assets.srcDirs = ['assets'] //多了一个 assets 目录

    }

    }

    signingConfigs {//签名设置

    debug {  //debug 对应的 SB。注意

    if(project.gradle.debugKeystore != null){

    storeFile file("file://${project.gradle.debugKeystore}")

    storePassword "android"

    keyAlias "androiddebugkey"

    keyPassword "android"

    }

    }

    }

    /*

    最关键的内容来了: buildTypes ScriptBlock.

    buildTypes 和上面的 signingConfigs,当我们在 build.gradle 中通过{}配置它的时候,

    其背后的所代表的对象是 NamedDomainObjectContainer 和

    NamedDomainObjectContainer

    注意,NamedDomainObjectContainer是一种容器,

    容器的元素是 BuildType 或者 SigningConfig。我们在 debug{}要填充 BuildType 或者

    SigningConfig 所包的元素,比如 storePassword 就是 SigningConfig 类的成员。而 proguardFile 等

    是 BuildType 的成员。

    那么,为什么要使用 NamedDomainObjectContainer 这种数据结构呢?因为往这种容器里

    添加元素可以采用这样的方法: 比如 signingConfig 为例

    signingConfig{//这是一个 NamedDomainObjectContainer

    test1{//新建一个名为 test1 的 SigningConfig 元素,然后添加到容器里

    //在这个花括号中设置 SigningConfig 的成员变量的值

    }

    test2{//新建一个名为 test2 的 SigningConfig 元素,然后添加到容器里

    //在这个花括号中设置 SigningConfig 的成员变量的值

    }

    }

    在 buildTypes 中,Android 默认为这几个 NamedDomainObjectContainer 添加了

    debug 和 release 对应的对象。如果我们再添加别的名字的东西,那么 gradle assemble 的时候

    也会编译这个名字的 apk 出来。比如,我添加一个名为 test 的 buildTypes,那么 gradle assemble

    就会编译一个 xxx-test-yy.apk。在此,test 就好像 debug、release 一样。

    */

    buildTypes{

    debug{ //修改 debug 的 signingConfig 为 signingConfig.debug 配置

    signingConfig signingConfigs.debug

    }

    demo{ //demo 版需要混淆

    proguardFile 'proguard-project.txt'

    signingConfig signingConfigs.debug

    }

    //release 版没有设置,所以默认没有签名,没有混淆

    }

    ......//其他和 posdevice 类似的处理。来看如何动态生成 runtime_config 文件

    def  runtime_config_file = 'assets/runtime_config'

    /*

    我们在 gradle 解析完整个任务之后,找到对应的 Task,然后在里边添加一个 doFirst Action

    这样能确保编译开始的时候,我们就把 runtime_config 文件准备好了。

    注意,必须在 afterEvaluate 里边才能做,否则 gradle 没有建立完任务有向图,你是找不到

    什么 preDebugBuild 之类的任务的

    */

    project.afterEvaluate{

    //找到 preDebugBuild 任务,然后添加一个 Action

    tasks.getByName("preDebugBuild"){

    it.doFirst{

    println "generate debug configuration for ${project.name}"

    def configFile = new File(runtime_config_file)

    configFile.withOutputStream{os->

    os << I am Debug\n'  //往配置文件里写 I am Debug

    }

    }

    }

    //找到 preReleaseBuild 任务

    tasks.getByName("preReleaseBuild"){

    it.doFirst{

    println "generate release configuration for ${project.name}"

    def configFile = new File(runtime_config_file)

    configFile.withOutputStream{os->

    os << I am release\n'

    }

    }

    }

    //找到 preDemoBuild。这个任务明显是因为我们在 buildType 里添加了一个 demo 的元素

    //所以 Android APP 插件自动为我们生成的

    tasks.getByName("preDemoBuild"){

    it.doFirst{

    println "generate offlinedemo configuration for ${project.name}"

    def configFile = new File(runtime_config_file)

    configFile.withOutputStream{os->

    os << I am Demo\n'

    }

    }

    }

    }

    }

    .....//copyOutput

    三:android中由于一般使用的是'com.android.application' 和 'com.android.library' 两个插件,所以还需要去了解这2个插件中的api

    相关文章

      网友评论

          本文标题:Gradle基本学习路线总结

          本文链接:https://www.haomeiwen.com/subject/ehnvlxtx.html