Android默认使用Gradle作为构建工具。
Why Gradle
Android Studio Project Site的描述如下
Gradle的出现满足了很多现在构建工具的需求,Gradle提供了一个DSL(领域特定语言),一个约定优于配置的方法,还有更强大的依赖管理,Gradle使得我们可以抛弃XML的繁琐配置,引入动态语言Groovy来定义你的构建逻辑。
Groovy
由于Gradle基于Groovy,要理解Gradle,必须先了解Groovy。
Groovy 被很多人认为是一种脚本语言,其实不准确,官网表述:
Groovy是一种基于JVM(Java虚拟机)的动态语言,它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码。由于其运行在 JVM 上的特性,Groovy 可以使用其他 Java 语言编写的库。
基础:
- 注释和java一样
- 语句可以不以分号结尾
- 定义变量是可以不指定类型,定义变量使用def关键字(不强制,但推荐,def会改变作用域)
按套路,现在该上“HelloWorld”了
java版HelloWorld:
public class Test {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
Groovy版HelloWorld:
println "Hello World"
Groovy版本比java版本简单很多,我们可以将groovy文件编译为class查看:
groovyc -d classes test.groovy
编译后得到test.class:
- test.groovy被转为一个继承自Script的test类
- 每一个脚本都会生成一个main函数
- 脚本中的代码都会放到run函数中
- 如果脚本中定义了函数,则函数会被定义在test类中
之前说def会改变变量作用域,举个栗子:
def num = 1 // 也可以 int num = 1
def printNum() {
println num
}
printNum()
结果是运行异常:
Caught: groovy.lang.MissingPropertyException: No such property: num for class: test
groovy.lang.MissingPropertyException: No such property: num for class: test
at test.printNum(test.groovy:4)
at test.run(test.groovy:7)
找不到属性?我们生成class查看:
结果一目了然,num是run方法中的局部变量,当然无法在printNum中访问。
我们再看不加def的class结果:
此时通过callGroovyObjectGetProperty来访问num变量。
也可以将num变为成员变量,这样printNum自然就可以访问了
import groovy.transform.Field;
@Field num = 1 //在num前面加上@Field标注,num就是test的成员变量了。
引号:
Groovy支持单引号,双引号,三引号
- 单引号中的内容严格对应Java的String,不对$进行转义。
- 双引号的内容如果有表达式先求值
- 三引号可以指示一个多行的字符串,并可以在其中自由的使用单引号和双引号。
def name = "Candy"
println 'Hello $name'
println "Hello $name"
println "Hello ${name}"
输出:
Hello $name
Hello Candy
Hello Candy
函数:
除非指定了确定的返回类型(void也可以作为一种返回值),否则定义函数必须加上关键字def。
函数默认最后一行语句的值为函数返回值,可以显式的return返回值。
def method1() {
return "This is method1"
}
def method2() {
"This is method2"
}
String method3() {
return "This is method3"
}
void method4() {
println "hello method4"
}
println method1()
println method2()
println method3()
println method4()
输出:
This is method1
This is method2
This is method3
hello method4
null
参数可以不指定类型,参数可以有默认值:
def printPeople(name, age=18) {
println "name is ${name}, age is ${age}"
}
printPeople("Jack", 22)
printPeople("Terry")
// 输出:
// name is Jack, age is 22
// name is Terry, age is 18
当函数有一个或多个参数时,调用的时候可以不加括号,比如:
println "Hello World" // 等同于 println("Hello World")
def someMethod1(it) {
println "someMethod1, it=${it}"
}
def someMethod2() {
println "someMethod2"
}
someMethod1(1) // someMethod1, it=1
someMethod1 2 // someMethod1, it=2
someMethod2() // someMethod2
someMethod2 // 报错
另外一个语法特性是Command Chain,不仅可以省略圆括号,又可以省略”.”号。比如:a(b).c(d),可以写成:a b c d。
def name(name) {
println "name: $name"
return this
}
def age(age) {
println "age: $age"
return this
}
name "Jerry" age 18
函数调用不加括号在Gradle中有很多运用,举个栗子:
include ':app'
实际是函数调用:
闭包
Groovy闭包文档:http://www.groovy-lang.org/closures.html
闭包语句:
def clos = { param -> println "${param}" }
clos.call("Hello") // 等同于 clos("Hello"),输出 Hello
如果闭包没定义参数的话,则隐含有一个参数it:
def clos = { println "${it}" }
clos("Hello") // 输出 Hello
当然也可以指定闭包没有参数:
def clos = { -> println "${it}" }
clos("Hello") // 报错
当Closure作为函数最后一个参数时,可以将Closure拿到括号外边,比如:
def doSomething(arg, Closure clos) {
print "${arg} "
clos()
}
doSomething ("Hello", { println 'Candy' })
doSomething "Hello", { println 'Bob' }
doSomething ("Hello") { println 'Tom' }
// 输出:
// Hello Candy
// Hello Bob
// Hello Tom
闭包在gradle中有大量的运用,比如:
repositories {
jcenter()
}
这在gradle脚本中很常见的写法,其实repositories是一个函数:
/**
* Configures the repositories for the script dependencies. Executes the given closure against the {@link
* RepositoryHandler} for this handler. The {@link RepositoryHandler} is passed to the closure as the closure's
* delegate.
*
* @param configureClosure the closure to use to configure the repositories.
*/
void repositories(Closure configureClosure);
可以看到这个函数的参数是一个闭包,那么上面的写法就等同于:
repositories({
jcenter()
})
至此Groovy暂时告一段落,我们已经了解了一些Groovy的基础知识,下一步就是学习Gradle了。
Gradle
Gradle是一个框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。比如编译Java有Java插件,编译Groovy有Groovy插件,编译Android APP有Android APP插件,编译Android Library有Android Library插件。
我们在AndroidStudio中创建project时,会默认生成一个多项目结构的Gradle工程:
上图三个.gradle结尾的文件就是Gradle的构建脚本。
共有三种不同类型的脚本:
Init script
我们大部分人并不常用,但是它确实可以配置:
- 在命令行里指定:
gradle -I 或者 --init-script <pathToInit.gradle>
- 在USER_HOME/.gradle/init.d目录下,放置init.gradle文件
Settings script
settings.gradle对于多项目的Project是必须的,文件在rootProject根目录下,Settings脚本的Delegate是Settings对象,可以看下Settings类都有哪些函数:
我们常用的就是include函数了:
include ':app'
Build script
build.gradle脚本文件是我们最常编辑的,绝大部分配置工作都在这里面。每一个待编译的Project都对应一个build.gradle,它的Delegate是Project:
每一个待编译Project都有一个build.gradle,每一个build.gradle文件都会转换成一个Project对象,在构建的时候包含一系列的Task。比如一个Android APK的编译包含:Java源码编译Task、资源编译Task、JNI编译Task、lint检查Task、打包生成APK的Task、签名Task等。
Task
Task 是Gradle中的一种数据类型,它代表了一些要执行的工作。不同的插件可以添加不同的Task。每一个Task都需要和一个Project关联。
创建Task:
task hello1 {
doLast {
println '--- Task hello1 ---'
}
}
task hello2
hello2.doLast {
println '--- Task hello2 ---'
}
tasks.create('hello3')
hello3.doLast {
println '--- Task hello3 ---'
}
使用命令gradle [task]
来执行task,如果task是驼峰命名时(比如task名字是helloWorld),可以使用缩写 gradle hW
。
task依赖:
task hello {
doLast {
println 'Hello World !'
}
}
task intro() {
doLast {
println "I am Gradle"
}
}
intro.dependsOn(hello)
// 或:
// task intro(dependsOn: hello) {
// doLast {
// println "I am Gradle"
// }
// }
执行gradle -q intro
后输出:
Hello World !
I am Gradle
Android Plugin for Gradle
Android Plugin for Gradle 是通过Gradle插件机制实现的Android平台的Gradle构建插件,详情请参考:
Android Gradle Plugin User Guide
Android Plugin DSL Reference
Configure Your Build
build.gradle解读
defaultConfig:
defaultConfig {
// 包名
applicationId "com.goodl.gradledemo"
// 最低版本
minSdk 9
// 目标版本
targetSdk 25
// 版本号
versionCode 1
// 版本名称
versionName "1.0"
// 自动化测试
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a'
}
resConfigs "en", "zh"
}
signConfigs:
signingConfigs {
// debug 签名
debug {}
// release 签名
release {
// 签名文件所在路径
storeFile file("ray.jks")
// 签名密码
storePassword "111111"
// 别名
keyAlias "rayhahah"
keyPassword "111111"
v2SigningEnabled true
}
// 自定义签名配置
ray {
// 和上面的属性一致,根据个人需求实现不同配置
}
}
buildTypes:
buildTypes {
debug {
minifyEnabled false
signingConfig signingConfigs.debug
buildConfigField 'boolean', 'ENV_PRODUCTION', "false"
}
uat {
initWith debug // 从给定的构建类型复制所有属性
buildConfigField 'boolean', 'ENV_PRODUCTION', "false"
}
release {
minifyEnabled true // 是否混淆
zipAlignEnabled true // zipAlign优化
shrinkResources true // 移除无用的resource文件
signingConfig signingConfigs.release // 签名
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField 'boolean', 'ENV_PRODUCTION', "true"
}
}
sourceSets:
sourceSets {
// 这样的配置适用于将 Eclipse 中的项目结构迁移到 AndroidStudio 中
main {
// 指定src资源目标目录
java.srcDirs = ['src']
// 指定asset的目标目录
assets.srcDirs = ['assets']
// 指定res的目标目录
res.srcDirs = ['res']
// 指定依赖C文件的目标目录
jni.srcDirs = ['jni']
// 指定依赖so文件的目标目录
jniLibs.srcDirs = ['libs']
// 指定Manifest的目标文件路径
manifest.srcFile 'AndroidManifest.xml'
}
}
productFlavors:
// 多渠道打包配置
productFlavors {
xiaomi {
manifestPlaceholders = [CHANNEL_VALUE: "xiaomi"]
}
myapp {
manifestPlaceholders = [CHANNEL_VALUE: "myapp"]
}
}
lintOptions:
lintOptions {
// 启用出错停止grgradle构建
abortOnError false
// true -- 检查所有问题点,包含其他默认关闭项
checkAllWarnings true
// 关闭指定问题检查
disable 'TypographyFractions', 'TypographyQuotes'
// 打开指定问题检查
enable 'RtlHardcoded', 'RtlCompat', 'RtlEnabled'
// 仅检查指定问题
check 'NewApi', 'InlinedApi'
// true -- 生成HTML报告(带问题解释,源码位置,等)
htmlReport true
// html 报告可选路径(构建器默认是lint-results.html )
htmlOutput file("lint-report.html")
// 忽略指定问题的规则(同关闭检查)
ignore 'TypographyQuotes'
}
重命名 apk 名称:
static def releaseTime() {
return new Date().format("yyyyMMdd", TimeZone.getTimeZone("UTC"))
}
android {
defaultConfig {
// ...
}
// ...
applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "myapp_v${defaultConfig.versionName}_${releaseTime()}_${variant.name}.apk"
}
}
}