Gradle是一个用于构建Android工程的工具,同时它也是一个编程框架。Gradle用于帮助我们自动管理编译流程,它允许我们定义个性化的编译内容,每个工程拥有自己的源码和资源的同时,还能使用一些通用的配置。而且Gradle和Android Studio是分离的,我们可以在不启动Android Studio的同时,单独使用命令行完成工程的编译工作。
Gradle语言具有以下特点:
-
基于Groovy语言
-
是一种DSL,即Domain Specific Language,领域相关语言,有自己特有的术语
- Groovy 语言简单介绍
- Gradle 介绍
- Android Studio 中常见的 Gradle 问题
Groovy 语言简单介绍
概况
Groovy是在 Java平台上的、 具有像Python, Ruby 和 Smalltalk 语言特性的灵活动态语言, Groovy保证了这些特性像 Java语法一样被 Java开发者使用。我们编写的Java代码可直接当成Groovy Code来执行。Java、Groovy和JVM的关系如下图所示:Groovy Code 与 Java Code一致在执行的时候变成Java字节码在JVM上执行
Groovy下载地址:http://www.groovy-lang.org/download.html
Groovy文档地址:http://www.groovy-lang.org/documentation.html
Groovy的执行效果大致如下所示:
Groovy 语法特点介绍
Groovy在语法上与Java类似,但它有如下特点:
- Groovy语句可以不用分号结尾
- Groovy中支持动态类型,即定义变量的时候可以不指定其类型。Groovy中,变量定义可以使用关键字def,也可以不使用(但推荐都加上def,不然有时会报找不到属性值的错误)
- 函数定义时,参数的类型也可以不指定(不指定意思即参数为动态类型)
- Groovy中函数的返回值也可以是动态类型的
- Groovy的函数里,可以不使用return来为函数设置返回值。如果不使用return语句的话,则函数里最后一句代码的执行结果会默认被设置成返回值
- 函数调用可以不带括号(不过不带括号有可能会使函数被误认为是变量,因此一般是在Groovy API或 Gradle API上比较常用的函数才不带括号) 例如:println 'hello groovy',一个调用println函数的例子
Groovy 字符串介绍
- 单引号''中的内容严格对应Java中的String,就是普通的字符串
- 双引号""的内容则和脚本语言的处理有点像,如果字符中有$符号的话,则它会先求$变量的值
def x = 10 println "x is $x" // x is 10
- 三个引号'''xxx'''中的字符串支持随意换行
Groovy 数据类型介绍
- Java中的基本数据类型(在实际执行过程中会被转换为对应的包装类型)
- Groovy中的容器类,容器类型有以下三种:
List,Map,Range(和List类似)
- 闭包
def closure = { paramters -> code } // 箭头前面为参数,后面为执行代码
def closure = { code } // 有一个默认无类型参数 it
def closure = { -> code } // 没有参数
closure.call(paramters)
closure(paramters)
闭包在Groovy中大量使用,比如很多类都定义了一些函数,这些函数最后一个参数都是一个闭包,又因为函数调用可以省略圆括号,因此我们经常看到如下代码:
// Android Studio 默认生成一段的Gradle脚本
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.0'
}
}
// 脚本补全后形态
buildscript ({ // 函数参数为闭包
repositories ({ // 函数参数为闭包
jcenter()
})
dependencies ({ // 函数参数为闭包
add('classpath', 'com.android.tools.build:gradle:2.1.0')
})
})
Closure很方便,但是它一定会和使用它的上下文有极强的关联,当某个函数参数是闭包的时候,我们有时连闭包中传入的参数是什么我们都不知道。
因此我们把闭包当作参数传递的时候一定要注意查看API文档:
Groovy API文档:http://www.groovy-lang.org/api.html
举个例子:
// 依次打印List中所有元素
list = [1, 2, 3]
list.each { // each函数的参数是闭包,根据文档会向闭包中传入list的每个元素
println it // 打印闭包的默认参数it
}
each方法的API如下所示:
Groovy 的GDK
Groovy基于Java的特性,在JDK上添加了一些方法使得它变成了更灵活的GDK
GDK 文档:http://www.groovy-lang.org/gdk.html
下面列出一些GDK的常见文件I/O操作:
// 读取文件相关:(targetFile 是一个 File对象)
// 读取文件每一行并打印
targetFile.eachLine {
String line ->
println line
}
// 文件内容一次性读出,返回类型为byte[]
targetFile.getBytes()
// 操作inputStream
targetFile.withInputStream {
ism ->
// 操作inputStream,结束后stream会自动close
}
// 写文件相关:
// 文件流copy
targetFile.withOutputStream {
osm ->
srcFile.withInputStream {
ism ->
osm << ism // 利用OutputStream的<<操作符重载完成
}
}
Groovy 的XML操作
Groovy还提供了一些API用于操作XML:http://www.groovy-lang.org/processing-xml.html
下面是一个打印AndroidManifest的versionName的例子,短短几行即可:
androidManifest = new XmlParser().parse('AndroidManifest.xml')
println androidManifest['@android:versionName']
println androidManifest.@'android:versionName'
上面是对Groovy语言的一些较基础的介绍,想要深入了解的同学建议去看看官方的文档继续学习。介绍完Groovy的一些比较基础的知识后,我们再来了解Gradle是什么东西。
Gradle介绍
Gradle相关资料
Gradle 文档位置: https://docs.gradle.org/current/release-notes
其中,文档中几个标签分别为:
- User Guide:介绍Gradle的一本书
- DSLReference:Gradle API的说明
- Javadoc:Gradle API的函数文档
- Groovydoc:Groovy的文档
Gradle 基本概念
首先我们来了解一些Gradle中比较重要的基本概念:
-
Project
在Gradle中,每个待编译的工程称为Project
每个Project对应一个build.gradle文件,用于配置Project的属性,类似Makefile -
Multi-Project build
为了一次性编译多个工程,我们可以构建一个MultiProjects
MultiProjects有一个build.gradle文件,用于配置gradle的配置和其它子Project的属性
MultiProjects文还有一个settings.gradle文件,用于指定这个Projects具体包含哪些子Project
在Android Studio中,最顶层的MultiProject叫做Project,其子Project叫做Module
-
Task
task是编译过程的执行者
每个Project都会包含一系列的task,每个task用于执行特定的编译任务
多个task之间可能会存在依赖关系,某个task执行前,会自动执行它所依赖的task
task拥有多种类型,一般我们构建的都是DefaultTask类型,但有时为了特殊的需要,我们也可以构建一些特殊类型的task(如:复制文件 Copy,删除文件 Delete),
不同类型的task会帮助我们执行不同的操作
-
Property
除了task外,Project还包含一系列的Property属性值
我们可以通过ext.xxx的方法来为某个Project设置额外的属性值
-
Plugin
Plugin即插件,插件包含了多个task以及一些与task相关的Property
在Project中,我们可以通过apply方法应用插件
我们通过修改插件的Property,执行插件的task得到不同的结果
Gradle 终端常用命令
- gradle xxx(task-name) 执行特定的任务
- gradle projects 查看当前Project及其子Project的信息
-
gradle tasks 查看可执行的任务信息
Gradle 工作流程
- Initialization:遍历文件目录,解析settings.gradle,确定project数目
- Configuration:解析每个project下的build.gradle,根据task间的依赖关系生成一个task的有向图
- Execution:根据输入的指令与task有向图执行相应的task
- Gradle 对象:Gradle对象在整个执行过程中只有一个,我们可以通过调用它的方法传入闭包,来在工作流程中添加自己的指令(即Hook)。我们在脚本中使用gradle访问该对象
- Project 对象:Project对象与build.gradle文件一一对应,可以获取一些关于project的属性信息(如:project文件目录,build文件目录等)或添加Hook。在脚本中用project访问当前的Project对象
- Settings 对象:Settings对象与settings.gradle对应
println 'Initialization'
include 'project1'
include 'project2'
打印了Initialization,并引用了project1与project2两个工程
println "Configuration $project.name"
task myTask << {
println "Execution $project.name"
}
其中打印了Configuration 当前工程的名称,并定义了一个名为myTask的任务(任务内容是打印Execution 当前执行工程名称)
可以看到一些Gradle为我们内置的任务,以及我们自定义的任务。其中白色的字体是任务分组名称,绿色字体是任务名称,而黄色字体是任务描述。(这里我们的myTask其实有三个,因为每个project都定义了自己的myTask任务,显示的时候就集中显示了)
编写settings.gradle
学会了一些Gradle相关的基本概念后,我们就来看看如何编写Gradle脚本。由于settings.gradle与Settings对象对应,因此编写settings.gradle实际就上调用Settings对象的函数
Settings对象文档:https://docs.gradle.org/current/dsl/org.gradle.api.initialization.Settings.html
编写settings.gradle的主要目标:确定multiProjects(顶层Project)包含哪些子Project
一个默认Android工程的settings.gradle文件如下:
脚本中的include就是指包含某个project的意思,这段代码看起来很陌生,其实当我们查阅Settings的函数时我们会发现一个include函数:
结合Groovy语言省略括号与分号的特性,代码就变成了上面略微飘逸的模样。
编写build.gradle(顶层Project)
Project对象文档: https://docs.gradle.org/current/dsl/org.gradle.api.Project.html
Android官方文档: https://developer.android.com/studio/build/index.html
- 设置gradle整体相关的配置
- 为子Projects进行统一的配置
编写build.gradle(子Project)
- 应用插件,即Plugin
- 为project编写依赖
- 最顶端的插件应用声明
- 中间的插件属性设置
- 底部的project依赖设置
- application:生成apk时使用
- library:lib工程使用
- test:测试使用
- Properties:编译属性相关配置,编译的SDK和工具等
- Signing:数字签名相关配置
- Flavors:打包渠道相关配置,支持的SDK,APP的Id等
- BuildTypes:编译类型相关配置,是否混淆,混淆的文件,是否压缩等
- Dependencies:依赖相关配置
Task相关
Task文档2: https://docs.gradle.org/current/userguide/more_about_tasks.html
- 创建task并依赖已有的task
- 修改已有的task
Gradle 相关的文件
- gradle-wrapper.properties:gradle wrapper配置文件,确定gradle wrapper的版本以及存放地点等。gradle wrapper用于解决本地gradle版本与项目不一致问题,在终端使用gradlew代替gradle(即:gradlew xxx)
- proguard-rules.pro:代码混淆配置文件
- gradle.properties:gradle属性配置文件,用于设置gradle下载代理,是否并行编译,是否后台编译,为JVM添加参数 。
文档:https://docs.gradle.org/current/userguide/build_environment.html
- local.properties:用于存储一些和Android相关的变量,也可以存储一些自定义的变量
贴一段用于获取属性值的代码:
Android Studio 中常见的 Gradle 问题
- Gradle下载代理问题
- Gradle Wrapper使用本地压缩包
- Dependencies 关键词
- Jar打包
- 开启守护进程编译和并行编译
- 获取工程svn版本号
C:\Users\用户名\.gradle\wrapper\dists\gradle-2.10-all\a4w5fzrkeut1ox71xslb49gst\
- Compile:对所有的build type以及favlors都会参与编译并且打包到最终的apk文件中
- Provided:对所有的build type以及favlors只在编译时使用,类似eclipse中的external-libs,只参与编译,不打包到最终apk
- APK:只会打包到apk文件中,而不参与编译,所以不能再代码中直接调用jar中的类或方法,否则在编译时会报错
- Test compile:仅仅是针对单元测试代码的编译编译以及最终打包测试apk时有效,而对正常的debug或者release apk包不起作用
- Debug compile:仅仅针对debug模式的编译和最终的debug apk打包
- Release compile:仅仅针对Release 模式的编译和最终的Release apk打包