2015-10-27 1406
格式更加精美的PDF版请到http://vdisk.weibo.com/s/z68f8l0xTYrZt 下载
Gradle是当前非常“劲爆”得构建工具。本篇文章就是专为讲解Gradle而来。介绍Gradle之前先说点题外话。
说实话我在大法工作的时候就见过Gradle。但是当时我一直不知道这是什么东西。而且大法工具组的工程师还将其和Android Studio大法版一起推送偶一看就更没兴趣了。为什么那个时候如此不待见Gradle呢因为我此前一直是做ROM开发。在这个层面上我们用makemm或者mmm就可以了。而且编译耗时对我们来说也不是啥痛点因为用组内吊炸天的神机服务器完整编译大法的image也要耗费1个小时左右。所以那个时侯Gradle完全不是我们的菜。
现在搞APP开发居多编译/打包等问题立即就成痛点了。比如
上述问题对绝大部分APP开发者而言都不陌生而Gradle作为一种很方便的的构建工具可以非常轻松得解决构建过程中的各种问题。
构建叫build也好叫make也行。反正就是根据输入信息然后干一堆事情最后得到几个产出物Artifact。
最最简单的构建工具就是make了。make就是根据Makefile文件中写的规则执行对应的命令然后得到目标产物。
日常生活中和构建最类似的一个场景就是做菜。输入各种食材然后按固定的工序最后得到一盘菜。当然做同样一道菜由于需求不同做出来的东西也不尽相同。比如宫保鸡丁这道菜回民要求不能放大油、口淡的要求少放盐和各种油、辣不怕的男女汉子们可以要求多放辣子....总之做菜包含固定的工序但是对于不同条件或需求需要做不同的处理。
在Gradle爆红之前常用的构建工具是ANT然后又进化到Maven。ANT和Maven这两个工具其实也还算方便现在还有很多地方在使用。但是二者都有一些缺点所以让更懒得人觉得不是那么方便。比如Maven编译规则是用XML来编写的。XML虽然通俗易懂但是很难在xml中描述if{某条件成立编译某文件}/else{编译其他文件}这样有不同条件的任务。
怎么解决怎么解决好对程序员而言自然是编程解决但是有几个小要求
------------------------------------------------------------------------------
土匪蘑菇你哪路什么价什么人到哪里去
杨子荣哈想啥来啥想吃奶来了妈妈想娘家的人孩子他舅舅来了。找同行
杨子荣拜见三爷
土匪天王盖地虎你好大的胆敢来气你的祖宗
杨子荣宝塔镇河妖要是那样叫我从山上摔死掉河里淹死。
土匪野鸡闷头钻哪能上天王山你不是正牌的。
杨子荣地上有的是米喂呀有根底老子是正牌的老牌的。
------------------------------------------------------------------------------
Gradle中也有类似的行话比如sourceSets代表源文件的集合等.....太多了记不住。以后我们都会接触到这些行话。那么对使用者而言这些行话的好处是什么呢这就是
一句行话可以包含很多意思而且在这个行当里的人一听就懂不用解释。另外基于行话我们甚至可以建立一个模板使用者只要往这个模板里填必须要填的内容Gradle就可以非常漂亮得完成工作得到想要的东西。
这就和现在的智能炒菜机器似的只要选择菜谱把食材准备好剩下的事情就不用你操心了。吃货们对这种做菜方式肯定是以反感为主太没有特色了。但是程序员对Gradle类似做法却热烈拥抱。
到此大家应该明白要真正学会Gradle恐怕是离不开下面两个基础知识
Groovy是一种动态语言。这种语言比较有特点它和Java一样也运行于Java虚拟机中。恩对头简单粗暴点儿看你可以认为Groovy扩展了Java语言。比如Groovy对自己的定义就是Groovy是在 java平台上的、 具有像Python Ruby 和 Smalltalk 语言特性的灵活动态语言 Groovy保证了这些特性像 Java语法一样被 Java开发者使用。
除了语言和Java相通外Groovy有时候又像一种脚本语言。前文也提到过当我执行Groovy脚本时Groovy会先将其编译成Java类字节码然后通过Jvm来执行这个Java类。图1展示了Java、Groovy和Jvm之间的关系。
图1 Java、Groovy和JVM的关系 |
实际上由于Groovy Code在真正执行的时候已经变成了Java字节码所以JVM根本不知道自己运行的是Groovy代码。
下面我们将介绍Groovy。由于此文的主要目的是Gradle所以我们不会过多讨论Groovy中细枝末节的东西而是把知识点集中在以后和Gradle打交道时一些常用的地方上。
在学习本节的时候最好部署一下Groovy开发环境。根据Groovy官网的介绍http://www.groovy-lang.org/download.html#gvm部署Groovy开发环境非常简单在Ubuntu或者cygwin之类的地方
执行完最后一步Groovy就下载并安装了。图1是安装时候的示意图
图1 Groovy安装示意图 |
然后创建一个test.groovy文件里边只有一行代码
执行groovy test.groovy输出结果如图2所示
图2 执行groovy脚本 |
亲们必须要完成上面的操作啊。做完后有什么感觉和体会
最大的感觉可能就是groovy和shell脚本或者python好类似。
另外除了可以直接使用JDK之外Groovy还有一套GDK网址是http://www.groovy-lang.org/api.html。
说实话看了这么多家API文档还是Google的Android API文档做得好。其页面中右上角有一个搜索栏在里边输入一些关键字瞬间就能列出候选类相关文档方便得不得了啊.....
为了后面讲述方面这里先介绍一些前提知识。初期接触可能有些别扭看习惯就好了。
其实所谓的无返回类型的函数我估计内部都是按返回Object类型来处理的。毕竟Groovy是基于Java的而且最终会转成Java Code运行在JVM上
注意如果函数定义时候指明了返回值类型的话函数中则必须返回正确的数据类型否则运行时报错。如果使用了动态类型的话你就可以返回任何类型了。
getSomething() //如果不加括号的话Groovy会误认为getSomething是一个变量。比如
图3 错误示意 |
所以调用函数要不要带括号我个人意见是如果这个函数是Groovy API或者Gradle API中比较常用的比如println就可以不带括号。否则还是带括号。Groovy自己也没有太好的办法解决这个问题只能兵来将挡水来土掩了。
好了了解上面一些基础知识后我们再介绍点深入的内容。
Groovy中的数据类型我们就介绍两种和Java不太一样的
放心这里介绍的东西都很简单
作为动态语言Groovy世界中的所有事物都是对象。所以intboolean这些Java中的基本数据类型在Groovy代码中其实对应的是它们的包装数据类型。比如int对应为Integerboolean对应为Boolean。比如下图中的代码执行结果
图4 int实际上是Integer
Groovy中的容器类很简单就三种
对容器而言我们最重要的是了解它们的用法。下面是一些简单的例子
Range是Groovy对List的一种拓展变量定义和大体的使用方法如下
前面讲这些东西主要是让大家了解Groovy的语法。实际上在coding的时候是离不开SDK的。由于Groovy是动态语言所以要使用它的SDK也需要掌握一些小诀窍。
Groovy的API文档位于http://www.groovy-lang.org/api.html
以上文介绍的Range为例我们该如何更好得使用它呢
l 先定位到Range类。它位于groovy.lang包中
图5 Range类API文档 |
有了API文档你就可以放心调用其中的函数了。不过不过不过我们刚才代码中用到了Range.from/to属性值但翻看Range API文档的时候其实并没有这两个成员变量。图6是Range的方法
图6 Range类的方法 |
文档中并没有说明Range有from和to这两个属性但是却有getFrom和getTo这两个函数。What happened原来
根据Groovy的原则如果一个类中有名为xxyyzz这样的属性其实就是成员变量Groovy会自动为它添加getXxyyzz和setXxyyzz两个函数用于获取和设置xxyyzz属性值。
注意get和set后第一个字母是大写的
所以当你看到Range中有getFrom和getTo这两个函数时候就得知道潜规则下Range有from和to这两个属性。当然由于它们不可以被外界设置所以没有公开setFrom和setTo函数。
闭包英文叫Closure是Groovy中非常重要的一个数据类型或者说一种概念了。闭包的历史来源种种好处我就不说了。我们直接看怎么使用它
闭包是一种数据类型它代表了一段可执行的代码。其外形如下
简而言之Closure的定义格式是
说实话从C/C++语言的角度看闭包和函数指针很像。闭包定义好后要调用它的方法就是
闭包对象.call(参数) 或者更像函数指针调用的方法
闭包对象(参数)
比如
上面就是一个闭包的定义和使用。在闭包中还需要注意一点
如果闭包没定义参数的话则隐含有一个参数这个参数名字叫it和this的作用类似。it代表闭包的参数。
闭包在Groovy中大量使用比如很多类都定义了一些函数这些函数最后一个参数都是一个闭包。比如
注意这个特点非常关键因为以后在Gradle中经常会出现图7这样的代码
图7 闭包调用 |
经常碰见图7这样的没有圆括号的代码。省略圆括号虽然使得代码简洁看起来更像脚本语言但是它这经常会让我confuse不知道其他人是否有同感以doLast为例完整的代码应该按下面这种写法
有了圆括号你会知道 doLast只是把一个Closure对象传了进去。很明显它不代表这段脚本解析到doLast的时候就会调用println 'Hello world!' 。
但是把圆括号去掉后就感觉好像println 'Hello world!'立即就会被调用一样
另外一个比较让人头疼的地方是Closure的参数该怎么搞还是刚才的each函数
如何使用它呢比如
看起来很轻松其实
l 对于each所需要的Closure它的参数是什么有多少个参数返回值是什么
我们能写成下面这样吗
此问题如何破解只能通过查询API文档才能了解上下文语义。比如下图8
图8 文档说明 |
图8中
对Map的findAll而言Closure可以有两个参数。findAll会将Key和Value分别传进去。并且Closure返回true表示该元素是自己想要的。返回false表示该元素不是自己要找的。示意代码如图9所示
图9 Closure调用示例 |
Closure的使用有点坑很大程度上依赖于你对API的熟悉程度所以最初阶段SDK查询是少不了的。
最后我们来看一下Groovy中比较高级的用法。
Groovy中可以像Java那样写package然后写类。比如在文件夹com/cmbc/groovy/目录中放一个文件叫Test.groovy如图10所示
图10 com/cmbc/groovy/Test.groovy文件 |
你看图10中的Test.groovy和Java类就很相似了。当然如果不声明public/private等访问权限的话Groovy中类及其变量默认都是public的。
现在我们在测试的根目录下建立一个test.groovy文件。其代码如下所示
图11 test.groovy访问com/cmbc/groovy包 |
你看test.groovy先import了com.cmbc.groovy.Test类然后创建了一个Test类型的对象接着调用它的print函数。
这两个groovy文件的目录结构如图12所示
图12 Test.groovy和test.groovy目录结构 |
在groovy中系统自带会加载当前目录/子目录下的xxx.groovy文件。所以当执行groovy test.groovy的时候test.groovy import的Test类能被自动搜索并加载到。
Java中我们最熟悉的是类。但是我们在Java的一个源码文件中不能不写classinterface或者其他....而Groovy可以像写脚本一样把要做的事情都写在xxx.groovy中而且可以通过groovy xxx.groovy直接执行这个脚本。这到底是怎么搞的
既然是基于Java的Groovy会先把xxx.groovy中的内容转换成一个Java类。比如
test.groovy的代码是
Groovy把它转换成这样的Java类
执行 groovyc-d classes test.groovy
groovyc是groovy的编译命令-dclasses用于将编译得到的class文件拷贝到classes文件夹下
图13是test.groovy脚本转换得到的java class。用jd-gui反编译它的代码
图13 groovy脚本反编译得到的Java类源码 |
图13中
groovyc是一个比较好的命令读者要掌握它的用法。然后利用jd-gui来查看对应class的Java源码。
前面说了xxx.groovy只要不是和Java那样的class那么它就是一个脚本。而且脚本的代码其实都会被放到run函数中去执行。那么在Groovy的脚本中很重要的一点就是脚本中定义的变量和它的作用域。举例
printx() <==报错说x找不到
为什么继续来看反编译后的class文件。
图14 反编译后的test.class文件 |
图14中
printx() <==OK
这次Java源码又变成什么样了呢
图15 进化版的test.groovy |
图15中x也没有被定义成test的成员函数而是在run的执行过程中将x作为一个属性添加到test实例对象中了。然后在printx中先获取这个属性。
注意Groovy的文档说 x = 1这种定义将使得x变成test的成员变量但从反编译情况看这是不对得.....
虽然printx可以访问x变量了但是假如有其他脚本却无法访问x变量。因为它不是test的成员变量。
比如我在测试目录下创建一个新的名为test1.groovy。这个test1将访问test.groovy中定义的printx函数
图16 test1.groovy使用test.groovy中的函数 |
这种方法使得我们可以将代码分成模块来编写比如将公共的功能放到test.groovy中然后使用公共功能的代码放到test1.groovy中。
执行groovy test1.groovy报错。说x找不到。这是因为x是在test的run函数动态加进去的。怎么办
查看编译后的test.class文件得到
图17 x现在是test类的成员变量了 |
这个时候test.groovy中的x就成了test类的成员函数了。如此我们可以在script中定义那些需要输出给外部脚本或类使用的变量了
本节介绍下Groovy的文件I/O操作。直接来看例子吧虽然比Java看起来简单但要理解起来其实比较难。尤其是当你要自己查SDK并编写代码的时候。
整体说来Groovy的I/O操作是在原有Java I/O操作上进行了更为简单方便的封装并且使用Closure来简化代码编写。主要封装了如下一些了类
图18 Groovy File I/o常用类和SDK文档位置 |
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...
2 直接得到文件内容
注意前面提到的getter和setter函数这里可以直接使用targetFile.bytes //....
3 使用InputStream.InputStream的SDK在
http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.html
4 使用闭包操作inputStream以后在Gradle里会常看到这种搞法
确实够简单令人发指。我当年死活也没找到withInputStream是个啥意思。所以请各位开发者牢记Groovy I/O操作相关类的SDK地址
和读文件差不多。不再啰嗦。这里给个例子告诉大家如何copy文件。
尼玛....关于OutputStream的<<操作符重载查看SDK文档后可知
图19 OutputStream的<<操作符重载 |
再一次向极致简单致敬。但是SDK恐怕是离不开手了...
除了I/O异常简单之外Groovy中的XML操作也极致得很。Groovy中XML的解析提供了和XPath类似的方法名为GPath。这是一个类提供相应API。关于XPath请脑补https://en.wikipedia.org/wiki/XPath。
GPath功能包括给个例子好了来自Groovy官方文档。
l 现在来看怎么玩转GPath
作为一门语言Groovy是复杂的是需要深入学习和钻研的。一本厚书甚至都无法描述Groovy的方方面面。
Anyway从使用角度看尤其是又限定在Gradle这个领域内能用到的都是Groovy中一些简单的知识。
现在正式进入Gradle。Gradle是一个工具同时它也是一个编程框架。前面也提到过使用这个工具可以完成app的编译打包等工作。当然你也可以用它干其他的事情。
Gradle是什么学习它到什么地步就可以了
----------------------------------------------------------------------------------------------------------
=====>看待问题的时候所站的角度非常重要。
-->当你把Gradle当工具看的时候我们只想着如何用好它。会写、写好配置脚本就OK
-->当你把它当做编程框架看的时候你可能需要学习很多更深入的内容。
另外今天我们把它当工具看明天因为需求发生变化我们可能又得把它当编程框架看。
----------------------------------------------------------------------------------------------------------
Gradle的官网http://gradle.org/
文档位置https://docs.gradle.org/current/release-notes。其中的UserGuide和DSL Reference很关键。User Guide就是介绍Gradle的一本书而DSLReference是Gradle API的说明。
以Ubuntu为例下载Gradlehttp://gradle.org/gradle-download/ 选择Completedistribution和Binary only distribution都行。然后解压到指定目录。
最后设置~/.bashrc把Gradle加到PATH里如图20所示
图20 配置Gradle到bashrc |
执行source ~/.bashrc初始化环境。
执行gradle --version如果成功运行就OK了。
注意为什么说Gradle是一个编程框架来看它提供的API文档
https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html
图21 Project接口说明 |
原来我们编写所谓的编译脚本其实就是玩Gradle的API....所以它从更底层意义上看是一个编程框架
既然是编程框架我在讲解Gradle的时候尽量会从API的角度来介绍。有些读者肯定会不耐烦为嘛这么费事
从我个人的经历来看因为我从网上学习到的资料来看几乎全是从脚本的角度来介绍Gradle结果学习一通下来只记住参数怎么配置却不知道它们都是函数调用都是严格对应相关API的。
而从API角度来看待Gradle的话有了SDK文档你就可以编程。编程是靠记住一行行代码来实现的吗不是是在你掌握大体流程然后根据SDK+API来完成的
其实Gradle自己的User Guide也明确说了
Buildscripts are code
Gradle是一个框架它定义一套自己的游戏规则。我们要玩转Gradle必须要遵守它设计的规则。下面我们来讲讲Gradle的基本组件
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插件
好了。到现在为止你知道Gradle中每一个待编译的工程都是一个Project一个具体的编译过程是由一个一个的Task来定义和执行的。
下面我们来看一个实际的例子。这个例子非常有代表意义。图22是一个名为posdevice的目录。这个目录里包含3个Android Library工程2个Android APP工程。
图22 重要例子 |
在图22的例子中
请回答问题在上面这个例子中有多少个Project
请回答问题在上面这个例子中有多少个Project
请回答问题在上面这个例子中有多少个Project
答案是每一个Library和每一个App都是单独的Project。根据Gradle的要求每一个Project在其根目录下都需要有一个build.gradle。build.gradle文件就是该Project的编译脚本类似于Makefile。
看起来好像很简单但是请注意posdevice虽然包含5个独立的Project但是要独立编译他们的话得
l cd 某个Project的目录。比如 cd CPosDeviceSdk
l 然后执行 gradle xxxxxxx是任务的名字。对Android来说assemble这个Task会生成最终的产物所以gradleassemble
这很麻烦啊有10个独立Project就得重复执行10次这样的命令。更有甚者所谓的独立Project其实有依赖关系的。比如我们这个例子。
那么我想在posdevice目录下直接执行gradle assemble是否能把这5个Project的东西都编译出来呢
答案自然是可以。在Gradle中这叫Multi-Projects Build。把posdevice改造成支持Gradle的Multi-Projects Build很容易需要
来看settings.gradle的内容最关键的内容就是告诉Gradle这个multiprojects包含哪些子projects:
[settings.gradle]
强烈建议
如果你确实只有一个Project需要编译我也建议你在目录下添加一个settings.gradle。我们团队内部的所有单个Project都已经改成支持Multiple-Project Build了。改得方法就是添加settings.gradle然后include对应的project名字。
另外settings.gradle除了可以include外还可以设置一些函数。这些函数会在gradle构建整个工程任务的时候执行所以可以在settings做一些初始化的工作。比如我的settings.gradle的内容
//定义一个名为initMinshengGradleEnvironment的函数。该函数内部完成一些初始化操作
//比如创建特定的目录设置特定的参数等
到目前为止我们了解了Gradle什么呢
l 每一个Project都必须设置一个build.gradle文件。至于其内容我们留到后面再说。
l 对于multi-projects build需要在根目录下也放一个build.gradle和一个settings.gradle。
l 一个Project是由若干tasks来组成的当gradlexxx的时候实际上是要求gradle执行xxx任务。这个任务就能完成具体的工作。
l 当然具体的工作和不同的插件有关系。编译Java要使用Java插件编译Android APP需要使用Android APP插件。这些我们都留待后续讨论
gradle提供一些方便命令来查看和ProjectTask相关的信息。比如在posdevice中我想看这个multi projects到底包含多少个子Project
执行gradle projects得到图23
图23 gradle projects |
你看multi projects的情况下posdevice这个目录对应的build.gradle叫Root
Project它包含5个子Project。
如果你修改settings.gradle使得include只有一个参数则gradle projects的子project也会变少比如图24
图24 修改settings.gradle使得只包含CPosSystemSdk工程 |
查看了Project信息这个还比较简单直接看settings.gradle也知道。那么Project包含哪些Task信息怎么看呢图23,24中最后的输出也告诉你了想看某个Project包含哪些Task信息只要执行
gradleproject-path:tasks 就行。注意project-path是目录名后面必须跟冒号。
对于Multi-project在根目录中需要指定你想看哪个poject的任务。不过你要是已经cd到某个Project的目录了则不需指定Project-path。
来看图25
图25 gradle CPosSystemSdk:tasks |
图25是gradleCPosSystemSdk:tasks的结果。
CPosSystemSdk是一个Android Library工程Android Library对应的插件定义了好多Task。每种插件定义的Task都不尽相同这就是所谓的Domain Specific需要我们对相关领域有比较多的了解。
这些都是后话我们以后会详细介绍。
图25中列出了好多任务这时候就可以通过 gradle 任务名来执行某个任务。这和make xxx很像。比如
gradle tasks会列出每个任务的描述通过描述我们大概能知道这些任务是干什么的.....。然后gradletask-name执行它就好。
这里要强调一点Task和Task之间往往是有关系的这就是所谓的依赖关系。比如assemble task就依赖其他task先执行assemble才能完成最终的输出。
依赖关系对我们使用gradle有什么意义呢
如果知道Task之间的依赖关系那么开发者就可以添加一些定制化的Task。比如我为assemble添加一个SpecialTest任务并指定assemble依赖于SpecialTest。当assemble执行的时候就会先处理完它依赖的task。自然SpecialTest就会得到执行了...
大家先了解这么多等后面介绍如何写gradle脚本的时候这就是调用几个函数的事情Nothing Special!
Gradle的工作流程其实蛮简单用一个图26来表达
图26 Gradle工作流程 |
图26告诉我们Gradle工作包含三个阶段
下面展示一下我按图26为posdevice项目添加的Hook它的执行结果
图26 加了Hook后的执行结果 |
我在
好了Hook的代码怎么写估计你很好奇而且肯定会埋汰搞毛这么就还没告诉我怎么写Gradle。马上了
最后关于Gradle的工作流程你只要记住
下面来告诉你怎么写代码
希望你在进入此节之前一定花时间把前面内容看一遍
https://docs.gradle.org/current/dsl/ <==这个文档很重要
Gradle基于GroovyGroovy又基于Java。所以Gradle执行的时候和Groovy一样会把脚本转换成Java对象。Gradle主要有三种对象这三种对象和三种不同的脚本文件对应在gradle执行的时候会将脚本转换成对应的对端
注意对于其他gradle文件除非定义了class否则会转换成一个实现了Script接口的对象。这一点和3.5节中Groovy的脚本类相似
当我们执行gradle的时候gradle首先是按顺序解析各个gradle文件。这里边就有所所谓的生命周期的问题即先解析谁后解析谁。图27是Gradle文档中对生命周期的介绍结合上一节的内容相信大家都能看明白了。现在只需要看红框里的内容
图27 Gradle对LifeCycle的介绍 |
我们先来看Gradle对象它有哪些属性呢如图28所示
图28 Gradle的属性 |
我在posdevice build.gradle中和settings.gradle中分别加了如下输出
得到结果如图29所示
图29 gradle示例 |
Gradle的函数接口在文档中也有。
每一个build.gradle文件都会转换成一个Project对象。在Gradle术语中Project对象对应的是BuildScript。
Project包含若干Tasks。另外由于Project对应具体的工程所以需要为Project加载所需要的插件比如为Java工程加载Java插件。其实一个Project包含多少Task往往是插件决定的。
所以在Project中我们要
Project的API位于https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html。加载插件是调用它的apply函数.apply其实是Project实现的PluginAware接口定义的
图30 apply函数 |
来看代码
[apply函数的用法]
apply是一个函数此处调用的是图30中最后一个apply函数。注意Groovy支持
函数调用的时候通过 参数名1:参数值2参数名2参数值2 的方式来传递参数
除了加载二进制的插件上面的插件其实都是下载了对应的jar包这也是通常意义上我们所理解的插件还可以加载一个gradle文件。为什么要加载gradle文件呢
其实这和代码的模块划分有关。一般而言我会把一些通用的函数放到一个名叫utils.gradle文件里。然后在其他工程的build.gradle来加载这个utils.gradle。这样通过一些处理我就可以调用utils.gradle中定义的函数了。
加载utils.gradle插件的代码如下
utils.gradle是我封装的一个gradle脚本里边定义了一些方便函数比如读取AndroidManifest.xml中
的versionName或者是copy jar包/APK包到指定的目录
也是使用apply的最后一个函数。那么apply最后一个函数到底支持哪些参数呢还是得看图31中的API说明
图31 apply API说明 |
我这里不遗余力的列出API图片就是希望大家在写脚本的时候碰到不会的一定要去查看API文档
如果是单个脚本则不需要考虑属性的跨脚本传播但是Gradle往往包含不止一个build.gradle文件比如我设置的utils.gradlesettings.gradle。如何在多个脚本中设置属性呢
Gradle提供了一种名为extra property的方法。extra property是额外属性的意思在第一次定义该属性的时候需要通过ext前缀来标示它是一个额外的属性。定义好之后后面的存取就不需要ext前缀了。ext属性支持Project和Gradle对象。即Project和Gradle对象都可以设置ext属性
举个例子
我在settings.gradle中想为Gradle对象设置一些外置属性所以在initMinshengGradleEnvironment函数中
再来一个例子强化一下
我在utils.gradle中定义了一些函数然后想在其他build.gradle中调用这些函数。那该怎么做呢
[utils.gradle]
上面代码中有两个问题
上面两个问题比较关键我也是花了很长时间才搞清楚。这两个问题归结到一起其实就是
加载utils.gradle的Project对象和utils.gradle本身所代表的Script对象到底有什么关系
我们在Groovy中也讲过怎么在一个Script中import另外一个Script中定义的类或者函数见3.5 脚本类、文件I/O和XML操作一节。在Gradle中这一块的处理比Groovy要复杂具体怎么搞我还没完全弄清楚但是Project和utils.gradle对于的Script的对象的关系是
现在你知道问题1,2和答案了
比如我在posdevice每个build.gradle中都有如下的代码
通过这种方式我将一些常用的函数放到utils.gradle中然后为加载它的Project设置ext属性。最后Project中就可以调用这种赋值函数了
注意此处我研究的还不是很深而且我个人感觉
1 在Java和Groovy中我们会把常用的函数放到一个辅助类和公共类中然后在别的地方import并调用它们。
2 但是在Gradle更正规的方法是在xxx.gradle中定义插件。然后通过添加Task的方式来完成工作。gradle的user guide有详细介绍如何实现自己的插件。
Task是Gradle中的一种数据类型它代表了一些要执行或者要干的工作。不同的插件可以添加不同的Task。每一个Task都需要和一个Project关联。
Task的API文档位于https://docs.gradle.org/current/dsl/org.gradle.api.Task.html。关于Task我这里简单介绍下build.gradle中怎么写它以及Task中一些常见的类型
关于Task。来看下面的例子
[build.gradle]
/
上述代码中都用了Project的一个函数名为task注意
图32是Project中关于task函数说明
图32 Project中task函数 |
陆陆续续讲了这么些内容我自己感觉都有点烦了。是得Gradle用一整本书来讲都嫌不够呢。
anyway到目前为止我介绍的都是一些比较基础的东西还不是特别多。但是后续例子该涉及到的知识点都有了。下面我们直接上例子。这里有两个例子
现在正是开始通过例子来介绍怎么玩gradle。这里要特别强调一点根据Gradle的哲学。gradle文件中包含一些所谓的Script Block姑且这么称它。ScriptBlock作用是让我们来配置相关的信息。不同的SB有不同的需要配置的东西。这也是我最早说的行话。比如源码对应的SB就需要我们配置源码在哪个文件夹里。关于SB我们后面将见识到
posdevice是一个multi project。下面包含5个Project。对于这种Project请大家回想下我们该创建哪些文件
马上一个一个来看它们。
utils.gradle是我自己加的为我们团队特意加了一些常见函数。主要代码如下
[utils.gradle]
图33展示了被disable的Debug任务的部分信息
图33 disable的Debug Task信息 |
这个文件中我们该干什么调用include把需要包含的子Project加进来。代码如下
[settings.gradle]
注意对于Android来说local.properties文件是必须的它的内容如下
[local.properties]
再次强调sdk.dir和ndk.dir是Android Gradle必须要指定的其他都是我自己加的属性。当然。不编译ndk就不需要ndk.dir属性了。
作为multi-project根目录一般情况下它的build.gradle是做一些全局配置。来看我的build.gradle
[posdevicebuild.gradle]
感觉解释得好苍白SB在Gradle的API文档中也是有的。先来看Gradle定义了哪些SB。如图34所示
图34 SB的类型 |
你看subprojects、dependencies、repositories都是SB。那么SB到底是什么它是怎么完成所谓配置的呢
仔细研究你会发现SB后面都需要跟一个花括号而花括号恩我们感觉里边可能一个Closure。由于图34说这些SB的Description都有“Configure xxx for this project”所以很可能subprojects是一个函数然后其参数是一个Closure。是这样的吗
Absolutely right。只是这些函数你直接到Project API里不一定能找全。不过要是你好奇心重不妨到https://docs.gradle.org/current/javadoc/选择Index这一项然后ctrl+f输入图34中任何一个Block你都会找到对应的函数。比如我替你找了几个API如图35所示
图35 SB对应的函数 |
特别提示当你下次看到一个不认识的SB的时候就去看API吧。
下面来解释代码中的各个SB
CPosDeviceSdk是一个Android Library。按Google的想法Android Library编译出来的应该是一个AAR文件。但是我的项目有些特殊我需要发布CPosDeviceSdk.jar包给其他人使用。jar在编译过程中会生成但是它不属于Android Library的标准输出。在这种情况下我需要在编译完成后主动copy jar包到我自己设计的产出物目录中。
Android自己定义了好多ScriptBlock。Android定义的DSL参考文档在
https://developer.android.com/tools/building/plugin-for-gradle.html下载。注意它居然没有提供在线文档。
图36所示为Android的DSL参考信息。
图36 Android Gradle DSL参考示意 |
图37为buildToolsVersion和compileSdkVersion的说明
图37 buildToolsVersion和compileSdkVersion的说明 |
从图37可知这两个变量是必须要设置的.....
再来看一个APK的build它包含NDK的编译并且还要签名。根据项目的需求我们只能签debug版的而release版的签名得发布unsigned包给领导签名。另外CPosDeviceServerAPK依赖CPosDeviceSdk。
虽然我可以先编译CPosDeviceSdk得到对应的jar包然后设置CPosDeviceServerApk直接依赖这个jar包就好。但是我更希望CPosDeviceServerApk能直接依赖于CPosDeviceSdk这个工程。这样整个posdevice可以做到这几个Project的依赖关系是最新的。
[build.gradle]
在posdevice下执行gradle assemble命令最终的输出文件都会拷贝到我指定的目录结果如图38所示
图38 posdevice执行结果 |
图38所示为posdevice gradle assemble的执行结果
下面这个实例也是来自一个实际的APP。这个APP对应的是一个单独的Project。但是根据我前面的建议我会把它改造成支持Multi-ProjectsBuild的样子。即在工程目录下放一个settings.build。
另外这个app有一个特点。它有三个版本分别是debug、release和demo。这三个版本对应的代码都完全一样但是在运行的时候需要从assets/runtime_config文件中读取参数。参数不同则运行的时候会跳转到debug、release或者demo的逻辑上。
注意我知道assets/runtime_config这种做法不decent但这是一个既有项目我们只能做小范围的适配而不是伤筋动骨改用更好的方法。另外从未来的需求来看暂时也没有大改的必要。
引入gradle后我们该如何处理呢
解决方法是在编译build、release和demo版本前在build.gradle中自动设置runtime_config的内容。代码如下所示
[build.gradle]
最终的结果如图39所示
图39 实例2的结果 |
几个问题为什么我知道有preXXXBuild这样的任务
答案gradle tasks --all查看所有任务。然后多尝试几次直到成功
到此我个人觉得Gradle相关的内容都讲完了。很难相信我仅花了1个小时不到的时间就为实例2添加了gradle编译支持。在一周以前我还觉得这是个心病。回想学习gradle的一个月时间里走过不少弯路求解问题的思路也和最开始不一样
书中说如果代码没有加<<则这个任务在脚本initialization也就是你无论执行什么任务这个任务都会被执行I am myTask都会被输出的时候执行如果加了<<则在gradle myTask后才执行。
尼玛我开始完全不知道为什么死记硬背。现在你明白了吗
这和我们调用task这个函数的方式有关如果没有<<则闭包在task函数返回前会执行而如果加了<<则变成调用myTask.doLast添加一个Action了自然它会等到grdle myTask的时候才会执行
现在想起这个事情我还是很愤怒API都说很清楚了......而且如果你把Gradle当做编程框架来看对于我们这些程序员来说写这几百行代码那还算是事嘛
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。