从gradle.com的userguide可以看到这样一段话:
Gradle is an open-source build automation tool focused on flexibility and performance. Gradle build scripts are written using a Groovy or Kotlin DSL. Read about Gradle features to learn what is possible with Gradle.
翻译过来就是Gradle是一个专注于灵活性和性能的开源构建自动化工具。Gradle构建脚本是用Groovy或Kotlin语言写的。Gradle是出现个人觉得还是非常革命性的,以它出现以前出存在构建工具很久了,常用的是ant,maven,做过服务端的或是早期android开发的一定对这两个不陌生,最早的时候是用ant的,它用xml文件来配置构建过程,比如:
//......
//......
ant使用一些xml标记来指一定一些构建命令或者依赖及参数,上面这个是指定了一个package任务它依赖于compile任务,它执行时用jar命令对编译生成物进行打包。而maven也是基于xml来描述依赖等,但它ant更进一步的是它使用了公共仓库依赖管理,比如一些公共库它放到maven center上进行共享,引用时只要在xml配置一下不用将它放在本地,方便了库的更新管理等。这两个工具有个共同的缺陷就是它们都是使用xml进行描述,因为xml并不是编程语言,在一些需要逻辑的构建任务中使用会非常吃力,为了能达到有if、while这样的表达能力,它们必须用xml一些特殊的标记来表示,而且使用xml会让配置文件可读性很差,没法很好地维护,为了解决这些问题(当然也为了解决其他问题),于是gradle出现了,gradle使用编程语言groovy来描述构建过程,这一举措极大地提高了编写和维护构建脚本,而且groovy语言本身非常简单易用,另外还支持了DSL,这让gradle一下得到了极大的发展,特别是在Android Studio集成了之后,因为从此做Android开发构建配置非常的简单高效。
其实不论是什么构建工具,都有构建脚本,ant和maven用xml,gradle用groovy,运行的过程也都会有解析脚本,生成对应的执行任务然后执行。gradle有个project的概念,这个跟ant的project有点像,一次构建过程至少会包含一个project,而gradle是用task作为最小的执行单位来实现的,一个project里会有多个task,task之间形成了依赖关系,gradle在运行一次构建过程时会有三个步骤:
以上是gradle的大概原理,那实际上是怎样的呢,比如脚本要怎么写,task怎么定义?要理解这些,可以先来看一个Android的构建实例,用Android Studio创建一个空的工程,可以看到几个关键的文件:
// setting.gradle
include ':app'
setting.gradle描述了构建过程包含什么project,上面的例子中是包含app这个project,在android studio中其实就是名为app的module。其中":"号是指当前目录,指定后gradle会去这个目录找对应的build.gradle文件。
// build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
// 编译脚本的配置
buildscript {
// 依赖地址
repositories {
google()
jcenter()
}
// 依赖库
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
// 这个跟上面的不一样,是指project里的引用库,不是对脚本本身生效,是全局的
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
上面的脚本指定了编译过程用到的依赖库,这个需要说明一下,因为gradle非常灵活自由,所以可以被用来实现很多自定义的构建,一些公共的构建逻辑放在了jcenter或maven库中,引用了才能使用相关的api,比如classpath 'com.android.tools.build:gradle:3.1.2’这一行就是指定要引用android的gradle编译的脚本库, 和全局的project的依赖
// app/buid.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "cmdmac.org.myapplication"
minSdkVersion 22
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/license.txt'
exclude 'META-INF/NOTICE'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/notice.txt'
exclude 'META-INF/ASL2.0'
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
// AS默认配置,如果如果没有记得加上
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {exclude group: 'com.android.support', module: 'support-annotations'})
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
implementation 'org.jooq:joor:0.9.6'
}
上面这个脚本是在module里面的,它定义了这个module应该怎么编译,大概意思是这个project即这个gradle文件使用com.android.application插件,这里要理解一下什么是gradle插件,gradle插件可以理解为也是编译的类库,比如里面的android包起来的东西,如果没有引用这个库是编译这个脚本时会报错,gradle已经有很多常用的插件库,比如java,groovy,android等,你也可以自己实现一个插件库来定制自己的构建逻辑。
在刚接触gradle脚本时,肯定会有点一头雾水,比如android里面用大括号包起来里面又有key-value的写法,这些其实都是groovy语言的特性,可以简单这么理解这个文件,这个文件被编译和配置后,会创建一个project的隐藏实例(有前端经验的可以理解它是document内置对象),里面这些写法都用调用这个实例的方法,比如:
android {
compileSdkVersion 27
defaultConfig {
// ......
applicationId "cmdmac.org.myapplication"
}
}
可以理解为project.android.setCompileSdkVersion(27)和project.android.defaultConfig.setApplicationId(“cmdmac.org.myapplication”),这些都是groovy语言的DSL的写法,key-value的形式就是用DSL来表达set,这样理解之后你就会知道其实gradle脚本就是用DSL的写法调用了对应的API,而API的实现是放在gradle插件中的即脚本中看到的apply plugin: ‘com.android.application’,这类似java的import,插件的引用地址能过前面的buildscript里的repositories和dependencies指定了路径。jcenter是一个远程的仓库,可以访问http://jcenter.bintray.com/看到发布的插件库。
在第一节中提到了gradle是用task作为最小执行单位的,有人可能会有点疑问,为什么只看到一个task:
task clean(type: Delete) {
delete rootProject.buildDir
}
其实task都封装在gradle插件中了,在脚本中看不到而矣,想看到有什么task可以在命令行执行./gradlew tasks --all看到或者在Android Studio中的gradle面板中看到。上面能看到的task没有其他相关代码,说明它是一个单独的task,只有在指定执行这个task时才会被执行到。关于更多具体的task知识可以google一下,下面简单的介绍一下
Groovy语言,因为gradle脚本是用groovy语言写的的,那么就必须得再理解一下groovy,groovy是一门基于JVM的语言,怎么理解呢,因为JVM规范是公开的,因此只要实现了JVM规范就可以把语言跑在JVM上,大家知道Java语言代码是跑在JVM上的,groovy就相当于java语言,但groovy语言更强大和自由,也可以支持在groovy语言是直接使用java语言,这里再多介绍一下gradle脚本中的例子:
android {
compileSdkVersion 27
defaultConfig {
// ......
applicationId "cmdmac.org.myapplication"
}
}
gradle脚本即是脚本也是groovy代码,上面{}括起来的其实是groovy的闭包,怎么理解呢,可以简单地理解闭包就是一段函数,在groovy中,函数是可以作为参数传进去的,上面的代码用java代码翻译一遍的话可能是这样:
project.android(new android() {
delegate_android.compileSdkVersion(27);
delegate_android.defaultConfig(new defaultConfig() {
delegate_defaultConfig.applicationId("cmdmac.org.myapplication")
});
})
可以看到,每个{}前面可以理解为是调用一个函数,参数就是{}包起来的代码块,代码块里可以执行继续调用函数和执行代码,代码块里调用函数的对象就是{}前面定义的,我在上面的脚本上再加几行代码可能会更容易理解一点:
android {
compileSdkVersion 27
int a = 1
int b = 2
int c = a + b
if (c == 3) {
println(c)
}
defaultConfig {
// ......
applicationId "cmdmac.org.myapplication"
}
}
在执行脚本的时候会打印出3,这样明白了吗{}里面可以就简单理解是一个函数的代码段!
理解gradle最主要是理解这几个:
1.gradle脚本是groovy代码,gradle使用task作为最基本的执行单位
2.groovy代码可以兼容java代码,闭包是一段groovy函数代码段,闭包可以作为函数参数传进另一个函数
3.compileSdkVersion等各个配置项是gradle插件里的实现,可以查看相应的插件配置文档
理解了上面几个之后,只要查一下groovy语法和gradle帮助文件基本就没太大困难,如有兴趣可以查看插件的源码实现会更加深刻〜