一、前提知识

参考文档:http://www.paincker.com/gradle-develop-basics
参考文档:http://www.infoq.com/cn/articles/android-in-depth-gradle

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

  • Groovy语句可以不用分号结尾。Groovy为了尽量减少代码的输入,确实煞费苦心

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

  1. def variable1 = 1 // 可以不使用分号结尾
  2. def varable2 = "I am a person"
  3. def int x = 1 // 变量定义时,也可以直接指定类型
  • 函数定义时,参数的类型也可以不指定。比如
  1. // 无需指定参数类型
  2. String testFunction(arg1,arg2){
  3. ...
  4. }
  • 除了变量定义可以不指定类型外,Groovy中函数的返回值也可以是无类型的。比如:
  1. // 无类型的函数定义,必须使用def关键字
  2. def nonReturnTypeFunc(){
  3. last_line //最后一行代码的执行结果就是本函数的返回值
  4. }
  5. // 如果指定了函数返回类型,则可不必加def关键字来定义函数
  6. String getString(){
  7. return "I am a string"
  8. }
  • 函数返回值:Groovy的函数里,可以不使用return xxx来设置xxx为函数返回值。如果不使用return语句的话,则函数里最后一句代码的执行结果被设置成返回值。比如
  1. // 下面这个函数的返回值是字符串"getSomething return value"
  2. def getSomething(){
  3. // 如果这是最后一行代码,则返回类型为String
  4. "getSomething return value"
  5. // 如果这是最后一行代码,则返回类型为Integer
  6. 1000
  7. }

注意,如果函数定义时候指明了返回值类型的话,函数中则必须返回正确的数据类型,否则运行时报错。如果使用了动态类型的话,你就可以返回任何类型了。

  • Groovy对字符串支持相当强大,充分吸收了一些脚本语言的优点:
  1. 单引号’’中的内容严格对应Java中的String,不对$符号进行转义
  1. def singleQuote='I am $ dolloar' //输出就是I am $ dolloar
  1. 双引号””的内容则和脚本语言的处理有点像,如果字符中有$号的话,则它会$表达式先求值。
  1. def doubleQuoteWithoutDollar = "I am one dollar" //输出 I am one dollar
  2. def x = 1
  3. def doubleQuoteWithDollar = "I am $x dolloar" //输出I am 1 dolloar
  1. 三个引号’’’xxx’’’中的字符串支持随意换行 比如
  1. def multieLines = ''' begin
  2. line 1
  3. line 2
  4. end '''
  • 最后,除了每行代码不用加分号外,Groovy中函数调用的时候还可以不加括号。比如:
  1. println("test") ---> println "test"

注意,虽然写代码的时候,对于函数调用可以不带括号,但是Groovy经常把属性和函数调用混淆。比如

  1. def getSomething(){
  2. "hello"
  3. }
  4. getSomething() //如果不加括号的话,Groovy会误认为getSomething是一个变量。

所以,调用函数要不要带括号,我个人意见是如果这个函数是Groovy API或者Gradle API中比较常用的,比如println,就可以不带括号。否则还是带括号。Groovy自己也没有太好的办法解决这个问题,只能兵来将挡水来土掩了。

二、基础语法

2.1 基本数据类型

作为动态语言,Groovy世界中的所有事物都是对象。所以,int,boolean这些Java中的基本数据类型,在Groovy代码中其实对应的是它们的包装数据类型。比如int对应为Integer,boolean对应为Boolean

2.2 容器类

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

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

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

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

  1. 2.2.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

  1. <a name="vx8kxk"></a>
  2. ### 2.2.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添加新元素

  1. <a name="i3fwsh"></a>
  2. ### 2.2.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

  1. <a name="g9bhih"></a>
  2. # 三、Gradle DSL
  3. <a name="qeu1sx"></a>
  4. ## 3.1 `build.gradle`脚本
  5. 在`build.gradle`脚本中,可以给工程添加`compile`和`testCompile`两个`Configuration`,然后分别添加若干`Dependency`(包括远程模块和本地Project等类型依赖),如下。

// 给Project添加两个Configuration configurations { compile testCompile } // 在不同的Configuration中添加依赖项 dependencies { add(‘compile’, ‘com.demo:module0’) compile ‘com.demo:module1’ // 外部Module依赖 testCompile project(path: “:library”) // Project依赖 compile(‘com.demo:module2’) { // 支持Closure配置 transitive = false } debugCompile ‘com.demo:module3’ // 没有这个Configuration,会报错 } // 其他变式写法 dependencies.compile ‘com.demo:module4’ project.dependencies.compile ‘com.demo:module5’ project.dependencies { compile ‘com.demo:module6’ }

  1. <a name="t3rqks"></a>
  2. ## 3.2 `build.gradle`脚本的执行
  3. Gradle会在一个Project对象上执行`build.gradle`脚本(`Run build.gradle against a Project object`),可以理解成`build.gradle`的代理对象是Project。
  4. 示例代码中:
  5. 1. 创建Project对象。
  6. 2. 通过Groovy加装`build.gradle`文件,并将其视为Groovy脚本编译生成Script类,创建出一个GroovyObject实例。
  7. 3. 利用元编程,设置GroovyObject的代理对象为创建好的Project对象。
  8. 4. 执行GroovyObject的`run()`方法,即执行`build.gradle`脚本。

class Utils { static GroovyObject loadAndCreateGroovyObject(File sourceFile) { Class groovyClass = new GroovyClassLoader().parseClass(sourceFile); return (GroovyObject) groovyClass.newInstance(); } static void setDelegateForGroovyObject(GroovyObject obj, Object delegate) { obj.metaClass.getProperty = { String name -> def metaProperty = obj.metaClass.getMetaProperty(name) metaProperty != null ? metaProperty : delegate.getProperty(name) } obj.metaClass.invokeMethod = { String name, Object[] args -> def metaMethod = obj.metaClass.getMetaMethod(name, args) metaMethod != null ? metaMethod.invoke(obj, args) : delegate.invokeMethod(name, args) } } } class Project { // … } // 创建Project对象 def project = new Project() // 加载并实例化Groovy对象 def groovyObject = Utils.loadAndCreateGroovyObject(new File(‘./build.gradle’)) // 给groovyObject设置代理对象 Utils.setDelegateForGroovyObject(groovyObject, project) // 执行脚本(Run “build.gradle” against the Project object) groovyObject.invokeMethod(“run”, null)

  1. <a name="q5i5rx"></a>
  2. ## 3.3 Project, configuraitons, dependencies
  3. - `configurations {}`和`dependencies {}`语句其实都是调用Project定义的方法,后面的大括号则是方法的闭包参数。
  4. - `denpendencies.compile`这种写法,这里的`dependencies`则是在调用Project定义的属性。
  5. - `project.xxx`,这里的`project`也是Project定义的属性,指向其自身。

class Utils { /**

  1. * 指定代理对象,运行闭包
  2. */
  3. static void runClosureAgainstObject(Closure closure, Object delegate) {
  4. Closure c = (Closure) closure.clone()
  5. c.delegate = delegate
  6. c.call()
  7. }

} class Project { ConfigurationContainer configurations = new ConfigurationContainer() DependencyHandler dependencies = new DependencyHandler(this) Project project = this void configurations(Closure closure) { Utils.runClosureAgainstObject(closure, configurations) } void dependencies(Closure closure) { Utils.runClosureAgainstObject(closure, dependencies) } }

  1. <a name="bbfpna"></a>
  2. ## 3.4 ConfigrationContainer
  3. `configurations(Closure c)`方法在`ConfigurationContainer`对象上执行闭包参数,compile和testCompile都是在调用`ConfigurationContainer`的`propertyMissing()`。

class ConfigurationContainer { Map configurations = new HashMap<>() Object propertyMissing(String name) { println “add configuration ‘$name’” configurations.put(name, new Configuration()) } }

  1. <a name="kplkcg"></a>
  2. ## 3.5 DependencyHandler
  3. `dependencies(Closure c)`方法在`DependencyHandler`上执行闭包参数。
  4. - 闭包中的`compile xxx`、`testCompile xxx`等都是在调用`DependencyHandler`的`methodMissing()`,最后被转到调用`add()`方法,从而添加依赖。
  5. - 闭包中的`project(path: ‘xxx‘)`也是`DependencyHandler`定义的一个方法,参数为Map,返回一个`Dependency`对象。
  6. - 调用`compile xxx {}`时,最后可以传入一个闭包参数,用于配置`transitive`属性等操作。当`DependencyHandler.add`方法传入了closure,会执行`Utils.configureObjectWithClosure(dependency, closure)`,用闭包配置`Dependency`,闭包中的`transitive=false`会覆盖`Dependency`中的对应属性。

class Utils { static void configureObjectWithClosure(Object object, Closure closure) { Closure c = (Closure) closure.clone() c.resolveStrategy = Closure.DELEGATE_FIRST; c.delegate = object c.call() } } class DependencyHandler { Project project; DependencyHandler(Project project) { this.project = project; } void add(String configuration, String dependencyNotation) { add(configuration, new Dependency(dependencyNotation), null) } void add(String configuration, String dependencyNotation, Closure closure) { add(configuration, new Dependency(dependencyNotation), closure) } void add(String configuration, Dependency dependency) { add(configuration, dependency, null) } void add(String configuration, Dependency dependency, Closure closure) { Configuration cfg = this.project.configurations.configurations.get(configuration) if (cfg != null) { if (closure != null) { Utils.configureObjectWithClosure(dependency, closure) } cfg.dependencies.add(dependency) println “add dependency ‘${dependency}’ to ‘${configuration}’” } else { println “configuration ‘${configuration}’ not found, dependency is ‘${dependency}’” } } Dependency project(Map notation) { return new Dependency(“project(${notation.get(“path”)})”) } Object methodMissing(String name, Object args) { Object[] arr = (Object[]) args; if (arr.length >= 1 && (arr[0] instanceof String || arr[0] instanceof Dependency) && this.project.configurations.configurations.get(name) != null) { Dependency dependency = arr[0] instanceof String ? new Dependency((String) arr[0]) : (Dependency) arr[0]; if (arr.length == 1) { add(name, dependency) } else if (arr.length == 2 && arr[1] instanceof Closure) { add(name, dependency, (Closure) arr[1]) } } else { println “method ‘${name}’ with args ‘${args}’ not found!” } return null } }

  1. <a name="mcxweq"></a>
  2. # 四、Android
  3. <a name="rlp4yx"></a>
  4. ## 4.1 build.gradle(Project)
  5. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538186524265-90da77fd-a3ee-4e08-ae80-4bf56d5b377f.png#width=700)
  6. 我们这里,分为四个标签来讲:
  7. <a name="vn5nvq"></a>
  8. ### 4.1.1 buildscript
  9. buildscript中的声明是gradle脚本自身需要使用的资源。可以声明的资源包括依赖项、第三方插件、maven仓库地址等
  10. <a name="bnoqbt"></a>
  11. ### 4.1.2 ext
  12. ext是自定义属性,现在很多人都喜欢把所有关于版本的信息都利用ext放在另一个自己新建的gradle文件中集中管理,下面我介绍一下ext是怎么用的:
  13. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538186537978-71a8c37e-986d-4b9e-9370-62a48ee50521.png#width=661)
  14. <a name="ntucnc"></a>
  15. #### 4.1.2.1 首先我们新建两个文件,分别叫build.gradle和version.gradle
  16. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538186654428-5a3ddbc5-f23a-415c-84bf-e8ec99329287.png#width=570)
  17. <a name="lh1xqc"></a>
  18. #### 4.1.2.2 然后分别在两个文件中打上相应的代码
  19. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538186667102-2a70d1e2-8d2c-4b12-a929-cee4a6efac43.png#width=447)
  20. <a name="75pqra"></a>
  21. #### 4.1.2.3 最后在Android Studio的Terminal移动到相应的文件夹中运行task。
  22. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538186700908-81be562e-e1df-4fef-b265-45c67e6e3aaf.png#width=700)
  23. 我们可以很神奇的发现,当我们在build.gradle文件中输入了apply from:'version.gradle'这句话,我们就可以读取到该文件下ext的信息。
  24. 现在在项目中我也是这种方法统一管理所有第三方插件的版本号的。
  25. <a name="yo97fg"></a>
  26. ### 4.1.3 repositories
  27. 顾名思义就是仓库的意思啦,而jcenter()、maven()和google()就是托管第三方插件的平台
  28. <a name="s05zte"></a>
  29. ### 4.1.4 dependencies
  30. 当然配置了仓库还不够,我们还需要在dependencies{}里面的配置里,把需要配置的依赖用classpath配置上,因为这个dependencies在buildscript{}里面,所以代表的是Gradle需要的插件。
  31. 下面我们再看看build.gradle(Project)的另一部分代码
  32. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538186786559-24600778-044c-4177-a191-1e5515381886.png#width=505)
  33. <a name="z809dl"></a>
  34. ### 4.1.5 allprojects
  35. allprojects块的repositories用于多项目构建,为所有项目提供共同所需依赖包。而子项目可以配置自己的repositories以获取自己独需的依赖包。
  36. <a name="pda1ko"></a>
  37. ### 4.1.6 buildscript和allprojects的作用和区别
  38. buildscript中的声明是gradle脚本自身需要使用的资源,就是说他是管家自己需要的资源,跟你这个大少爷其实并没有什么关系。而allprojects声明的却是你所有module所需要使用的资源,就是说如果大少爷你的每个module都需要用同一个第三库的时候,你可以在allprojects里面声明。这下解释应该可以明白了吧。
  39. 好了,下面该说说build.gradle(Project)文件的最后一个一段代码了
  40. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538186833430-2aabc388-7c68-4872-948d-02967acf8cd8.png#width=305)
  41. 运行gradle clean时,执行此处定义的task。该任务继承自Delete,删除根目录中的build目录。相当于执行Delete.delete(rootProject.buildDir)。其实这个任务的执行就是可以删除生成的Build文件的,跟Android Studio的clean是一个道理。
  42. <a name="1xfbyq"></a>
  43. ## 4.2 build.gradle(Module)
  44. <a name="l436gg"></a>
  45. ### 4.2.1 apply plugin
  46. 首先要说下apply plugin:'×××'
  47. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538187124692-3b293f34-990d-4917-be71-c35db18bf369.png#width=396)
  48. 这种叫做引入Gradle插件,而Gradle插件大致分为分为两种:
  49. <a name="eguhgh"></a>
  50. #### 4.2.1.1 apply plugin:'×××'
  51. 二进制插件,二进制插件一般都是被打包在一个jar里独立发布的,比如我们自定义的插件,再发布的时候我们也可以为其指定plugin id,这个plugin id最好是一个全限定名称,就像你的包名一样;
  52. <a name="q2r6re"></a>
  53. #### 4.2.1.2 apply from:'×××':
  54. 应用脚本插件,其实这不能算一个插件,它只是一个脚本。应用脚本插件,其实就是把这个脚本加载进来,和二进制插件不同的是它使用的是from关键字.后面紧跟的坫一个脚本文件,可以是本地的,也可以是网络存在的,如果是网络上的话要使用HTTP URL.
  55. 虽然它不是一个真正的插件,但是不能忽视它的作用.它是脚本文件模块化的基础,我们可以把庞大的脚本文件.进行分块、分段整理.拆分成一个个共用、职责分明的文件,然后使用apply from来引用它们,比如我们可以把常用的函数放在一个Utils.gradle脚本里,供其他脚本文件引用。示例中我们把 App的版本名称和版本号单独放在一个脚本文件里,清晰、简单、方便、快捷.我们也可以使用自动化对该文件自动处理,生成版本。
  56. <a name="zz8ots"></a>
  57. ### 4.2.2 Gradle插件的作用
  58. 把插件应用到你的项目中,插件会扩展项目的功能,帮助你在项目的构建过程中做很多事情。
  59. 1. 可以添加任务到你的项目中,帮你完成一些亊情,比如测试、编译、打包。
  60. 2. 可以添加依赖配置到你的项目中,我们可以通过它们配置我们项目在构建过程中需要的依赖.比 如我们编译的时候依赖的第三方库等。
  61. 3. 可以向项目中现有的对象类型添加新的扩展属性、 方法等,让你可以使用它们帮助我们配置、优化构建,比如android{}这个配置块就是Android Gradle插件为Project对象添加的一个扩展。
  62. 4. 可以对项目进行一些约定,比如应用Java插 件之后,约定src/main/java目录下是我们的源代码存放位置,在编译的时候也是编译这个目录下的Java源代码文件。
  63. 然后我们说说'com.android.application'
  64. Android Gradle插件的分类其实是根据Android工程的属性分类的。在Andriod中有3类工程,一类是App应用工程,它可以生成一个可运行的apk应用:一类是Library库工程,它可以生成AAR包给其他的App工程公用,就和我们的Jar一样,但是它包含了Android的资源等信息,是一个特殊的Jar包;最后一类是Test测试工程,用于对App工程或者Library库工程进行单元测试。
  65. App插件id:com.android.application.
  66. Library插件id:com.android.library.
  67. Test插件id:com.android.test.
  68. 一般一个项目只会设置一个App插件,而module一般是会设置为Library插件。
  69. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538187303637-f237d8e7-7bf9-492a-9d38-fb80093bc1ca.png#width=586)
  70. <a name="linefi"></a>
  71. ### 4.2.3 android{}
  72. 是Android插件提供的一个扩展类型,可以让我们自定义Android Gradle工程,是Android Gradle工程配置的唯一入口。
  73. <a name="azcmdo"></a>
  74. ### 2.2.4 compileSdkVersion
  75. 是编译所依赖的Android SDK的版本,这里是API Level。
  76. <a name="6glllg"></a>
  77. ### 4.2.5 buildToolsVersion
  78. 是构建该Android工程所用构建工具的版本。
  79. <a name="96cklr"></a>
  80. ### 4.2.6 defaultConfig{}
  81. defaultConfig是默认的配置,它是一个ProductFlavor。ProductFlavor允许我们根据不同的情况同时生成多个不同的apk包。
  82. <a name="shrilu"></a>
  83. ### 4.2.7 applicationId
  84. 配置我们的包名,包名是app的唯一标识,其实他跟AndroidManifest里面的package是可以不同的,他们之间并没有直接的关系。
  85. package指的是代码目录下路径;applicationId指的是app对外发布的唯一标识,会在签名、申请第三方库、发布时候用到。
  86. <a name="ierrnl"></a>
  87. ### 4.2.8 minSdkVersion
  88. 是支持的Android系统的api level,这里是15,也就是说低于Android 15版本的机型不能使用这个app。
  89. <a name="fhsfrx"></a>
  90. ### 4.2.9 targetSdkVersion
  91. 表明我们是基于哪个Android版本开发的,这里是22。
  92. <a name="d0hhwe"></a>
  93. ### 4.2.10 versionCode
  94. 表明我们的app应用内部版本号,一般用于控制app升级,当然我在使用的bugly自动升级能不能接受到升级推送就是基于这个。
  95. <a name="96sacy"></a>
  96. ### 4.2.11 versionName
  97. 表明我们的app应用的版本名称,一般是发布的时候写在app上告诉用户的,这样当你修复了一个bug并更新了版本,别人却发现说怎么你这个bug还在,你这时候就可以自信的告诉他自己看下app的版本号。(亲身经历在撕逼的时候可以从容的应对)
  98. <a name="qskdgn"></a>
  99. ### 4.2.12 multiDexEnabled
  100. 用于配置该BuildType是否启用自动拆分多个Dex的功能。一般用程序中代码太多,超过了65535个方法的时候。
  101. <a name="hnrtul"></a>
  102. ### 4.2.13 ndk{}
  103. 多平台编译,生成有so包的时候使用,包括四个平台'armeabi', 'x86', 'armeabi-v7a', 'mips'。一般使用第三方提供的SDK的时候,可能会附带so库。
  104. <a name="24ipmo"></a>
  105. ### 4.2.14 sourceSets
  106. 源代码集合,是Java插件用来描述和管理源代码及资源的一个抽象概念,是一个Java源代码文件和资源文件的集合,我们可以通过sourceSets更改源集的Java目录或者资源目录等。
  107. 譬如像上图,我通过sourceSets告诉了Gradle我的关于jni so包的存放路径就在app/libs上了,叫他编译的时候自己去找。
  108. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538187413052-7df9900d-d6a0-4342-9d47-aad2f6bde31d.png#width=700)

name:build type的名字 applicationIdSuffix:应用id后缀 versionNameSuffix:版本名称后缀 debuggable:是否生成一个debug的apk minifyEnabled:是否混淆 proguardFiles:混淆文件 signingConfig:签名配置 manifestPlaceholders:清单占位符 shrinkResources:是否去除未利用的资源,默认false,表示不去除。 zipAlignEnable:是否使用zipalign工具压缩。 multiDexEnabled:是否拆成多个Dex multiDexKeepFile:指定文本文件编译进主Dex文件中 multiDexKeepProguard:指定混淆文件编译进主Dex文件中

  1. <a name="g322vk"></a>
  2. ### 4.2.15 buildType
  3. 构建类型,在Android Gradle工程中,它已经帮我们内置了debug和release两个构建类型,两种模式主要车别在于,能否在设备上调试以及签名不一样,其他代码和文件资源都是一样的。一般用在代码混淆,而指定的混淆文件在下图的目录上,minifyEnabled=true就会开启混淆:
  4. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538187463975-ea946474-0563-45b6-a7c1-6bb04d237f43.png#width=411)
  5. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538187471623-bd69ac21-f542-475c-af3c-562017b503a9.png#width=700)
  6. <a name="7vliwv"></a>
  7. ### 4.2.16 signingConfigs
  8. 签名配置,一个app只有在签名之后才能被发布、安装、使用,签名是保护app的方式,标记该app的唯一性。如果app被恶意删改,签名就不一样了,无法升级安装,一定程度保护了我们的app。而signingConfigs就很方便为我们提供这个签名的配置。storeFile签名文件,storePassword签名证书文件的密码,storeType签名证书类型,keyAlias签名证书中秘钥别名,keyPassword签名证书中改密钥的密码。
  9. 默认情况下,debug模式的签名已经被配置好了,使用的就是Android SDK自动生成的debug证书,它一般位于$HOME/.android/debug.keystore,其key和密码是已经知道的,一般情况下我们不需要单独配置debug模式的签名信息。
  10. <a name="07rauc"></a>
  11. ### 4.2.17 productFlavors
  12. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538187520083-a26e1d28-6022-4661-b85b-e08f12fe3188.png#width=700)
  13. 在我看来他就是Gradle的多渠道打包,你可以在不同的包定义不同的变量,实现自己的定制化版本的需求。
  14. <a name="qs5czn"></a>
  15. ### 4.2.18 manifestPlaceholders
  16. 占位符,我们可以通过它动态配置AndroidManifest文件一些内容,譬如app的名字:
  17. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538187564523-4bda77f0-79f7-43e4-961f-a0a380db2ef9.png#width=553)
  18. 看看上图,我们就能发现我们在productFlavors中定义manifestPlaceholders = [APP_NAME: "(测试)"]之后,在AndroidManifest的label加上"${APP_NAME}",我们就能控制每个包打出来的名字是我们想要不同的名字,譬如测试服务器和生产服务器的包应该名字不一样。
  19. <a name="nvy6iu"></a>
  20. ### 4.2.19 buildConfigField
  21. 他是BuildConfig文件的一个函数,而BuildConfig这个类是Android Gradle构建脚本在编译后生成的。而buildConfigField就是其中的自定义函数变量,看下图我们分别定义了三个常量:
  22. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538187589494-52ddf409-3c57-4dc4-951a-1bfcf995e347.png#width=700)
  23. 我们可以在BuildConfig文件中看到我们声明的三个变量
  24. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538187600232-eba38a72-8bbc-4df8-a3ca-c7ff605624fd.png#width=700)
  25. 然后我们就可以在代码中用这些变量控制不同版本的代码:
  26. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538187610578-4f09440c-3324-4259-bd15-c1a0a5f2eeda.png#width=470)
  27. 我们这样加个if,就可以轻轻松松的控制测试和生产版本付费的问题了,再也不用手动的改来改去了,那问题来了,我怎么去选择不同的版本呢,看下图:
  28. ![](https://cdn.nlark.com/yuque/0/2018/png/177396/1538187621453-ab379fae-6cce-43e0-8439-7c68f089fd87.png#width=413)
  29. 如果你是Android Studio,找到Build Variants就可以选择你当前要编译的版本啦。
  30. <a name="kcdmph"></a>
  31. ### 4.2.20 flavorDimensions
  32. 顾名思义就是维度,Gradle3.0以后要用flavorDimensions的变量必须在defaultConfig{}中定义才能使用,不然会报错:

Error:All flavors must now belong to a named flavor dimension. The flavor ‘flavor_name’ is not assigned to a flavor dimension. ```

Gradle - 图1

这样我们就可以在不同的包中形成不同的applicationId和versionName了。

Gradle - 图2

4.2.21 dexOptions{}

我们知道,Android中的Java源代码被编译成class字节码后,在打包成apk的时候
被dx命令优化成Android虚拟机可执行的DEX文件。

DEX文件比较紧凑,Android费尽心思做了这个DEX格式,就是为了能使我们的程序在Android中平台上运行快一些。对于这些生成DEX文件的过程和处理,Android Gradle插件都帮我们处理好了,Android Gradle插件会调用SDK中的dx命令进行处理。

但是有的时候可能会遇到提示内存不足的错误,大致提示异常是

java,lang.OutOfMemoryError: GC overhead limit exceeded,为什么会提示内存不足呢?

其实这个dx命令只是一个脚本,它调用的还是Java编写的dx.jar库,是Java程序处理的,所以当内存不足的时候,我们会看到这个Java异常信息.默认情况下给dx分配的内存是一个G8,也就是 1024MB。

所以我们只需要把内存设置大一点,就可以解决这个问题,上图我的项目就把内存设置为4g。

4.2.22 dependencies{}

Gradle - 图3

我们平时用的最多的大概就这个了,

  1. 首先第一句compile fileTree(include: [‘.jar’], dir: ‘libs’)*,这样配置之后本地libs文件夹下的扩展名为jar的都会被依赖,非常方便。

  2. 如果你要引入某个本地module的话,那么需要用compile project(‘×××’)。

  3. 如果要引入网上仓库里面的依赖,我们需要这样写compile group:’com.squareup.okhttp3’,name:’okhttp’,version:’3.0.1’,当然这样是最完整的版本,缩写就把group、name、version去掉,然后以”:”分割即可。
    compile ‘com.squareup.okhttp3:okhttp:3.0.1’

Gradle - 图4

但是到了gradle3.0以后build.gradle中的依赖默认为implementation,而不是
之前的compile。另外,还有依赖指令api。

那么下面我们就来说说:

4.2.23 gradle 3.0中依赖implementation、api的区别:

其实api跟以前的compile没什么区别,将compile全部改成api是不会错的;
而implementation指令依赖是不会传递的,也就是说当前引用的第三方库仅限于本module内使用,其他module需要重新添加依赖才能用,下面用两个图说明:

Gradle - 图5