参考资料:
来自Gradle开发团队的Gradle入门教程 1.Gradle与Groovy基础 2.Gradle构建 3.插件编写入门
Gradle 基础
Gradle基础概念
gradle是一个通用的构建工具 gradle是纯java编写的,核心代码都是java的,也是运行在jvm上。 和绝大多数运行在jvm上的库一样,gradle本身的安装包就是一个脚本和一堆java的lib的集合。
gradle的源码在github上都有【github】
gradle网站上的三种包:
gradle-7.4.2-src.zip —— sources包:某个版本的源代码打包,不可以运行。
gradle-7.4.2-bin.zip —— bin包:可以运行,且不包含多余的东西
gradle-7.4.2-all.zip —— all:可以运行,但是包含一些例子,sample和文档
gradle的本质
基于jvm的程序本质就是一个bin脚本,这个bin脚本分为unix上的和windows上的,做的事情就是启动jvm 加载lib目录下的运行所需要的库。(maven和groovy也是相似的结构)
lib目录中可以看到都是jar包
wrapper介绍
我们先来看下执行下面这条命令实际上会做些什么吧
./gradlew help
./gradlew 是为了保证在运行gradle的时候使用设定好的gradle版本,如果没有,则会下载。 执行 ./gradlew本质是启动一个很小的jvm,这个jvm去加载gradle-wrapper.jar,然后去把真正的安装包下载下来。接下来在这个指定的这个gradle版本中去执行对应的命令。
gradle-wrapper.properties文件介绍
这个配置文件主要负责提供gradle远程下载地址和提供本地保存路径
#本机 Gradle 压缩包解压后主地址
distributionBase=GRADLE_USER_HOME
#Gradle 压缩包下载地址
distributionUrl=https://services.gradle.org/distributions/gradle-7.0.2-bin.zip
#本机 Gradle 压缩包解压后路径 主要在这里找
distributionPath=wrapper/dists
#本机存放 Gradle 压缩包主路径
zipStorePath=wrapper/dists
#本机存放 Gradle 压缩包主地址
zipStoreBase=GRADLE_USER_HOME
GRADLE_USER_HOME
~/.gradle
gradle在机器上运行时只和两个目录打交道,一个是你的项目目录,另外一个就是gradleuserhome。因为执行的时候就是去调用不同jar包中的方法。
Daemon模式介绍
jvm的启动很慢,需要load很多jar包。gradle3.0之后默认使用daemon模式,当我们执行gradle命令时,例如gradle compile 命令时,会先启动一个很小的jvm —— client jvm。这个jvm基本什么事情都不做,只负责连接查找,并和后台的一个叫做 daemon 的jvm 通信,client jvm 会把执行参数和任务发送给daemon,daemon执行完任务后再把参数发挥给client jvm,所以client只负责发送接收数据和任务请求。
连接结束时,daemon会一直存在,client会销毁。
可以搜索一下gradle daemon看下文档。 Daemon一般空闲超过三个小时就会自动销毁。 可以使用--no -daemon参数来控制daemon,现在daemon已经很稳定了,一般不需要设置这个参数。
每一个./gradlew 都和一个gradle版本绑定。
示例:
-
- ./gradlew compileJava
-
- 会启动一个很轻量的jvm来查找机器上有没有对应版本的gradle,安装了的话就执行下一步,否则会先去下载对应版本的gradle
-
- 查找版本为5.0(假设)的并且和当前构建所要求的相关参数所兼容的daemon。若没有找到,就会启动一个daemon,并通过socket和这个daemon连接。
命令: ./gradlew --stop 杀死所有的daemon
groovy基础
《groovy in aciton》 看这本书就够了, groovy是运行在jvm之上的脚本语言,所谓脚本语言,指的是groovy是强类型,但是是动态调用的。
快速执行groovy代码
这个很重要,可以在这里点进去groovy对应的类
List list = [1,2,3,4,5]
list.findAll{it % 2 != 0}
println(list)
groovy和java很像,但默认都是public
gradle脚本一般使用groovy语言写代码
groovy有很多语法糖,groovy也是可以用java的方式去写
//List l = []
//Map m = [a:1]
//
//class A{
// int a
//
// void printA(){
// println(a)
// }
//}
//
////会生成一个默认的构造函数 new A(a:3)是简写,本质是一个map,new A([a:3])
//new A(a:3).printA()
//看起来是一个静态调用
////正常的java调用是通过 invokeDynamic 去调用printA方法
////但是groovy的运行方法可以想象为纯反射调用,等效为先生成一个对象A,再通过反射调用
//def a = new A(a:5)
//invokeMethod(a,"printA",[])//最后的[]表示没有参数
//闭包{} 是groovy dsl的核心
//闭包可以理解为就是一个函数,没有return 默认返回最后一行,可以传递进去参数,不指定入参,会有隐式入参it
def closure = {return 1}
println(closure())//打印 1 这里注意这个closure是带()的,因为加括号才表示这个闭包的执行,才有返回值
def closure2 = {param ->
param+2
}
println(closure2(4))//打印6
def closure3 = {
it + 5
}
println(closure3(5))//打印10
//可以再
List list = [1,2,3,4,5]
list.findAll{it % 2 != 0}
println(list)
//groovy.lang.Closure
def test22(int i , Closure c){//这个不需要导入 记下来
return c(i)
}
//如果最后一个参数是闭包,可以这么写代码
println test22(3,{it * 3})//9
//groovy约定不影响歧义的情况下是可以不加括号的 重点
//上方的代码等同于 println(test22(3,{it * 3}))
println(test22(3,{it * 3}))//9
//闭包是最后一个参数的话,闭包可以写在外面
println test22(4){
it +2
}//6
---结束
DSL: 领域专属语言
搜索groovy dsl,学习groovy ,必看 docs.groovy-lang.org/docs/latest…
关于闭包的简单介绍
plugins {
id "com.diffplug.spotless" version "5.2.0"
}
//相当于有一个方法名为plugin的方法,我们调用这个方法,并且入参是一个闭包
plugins({
id "com.diffplug.spotless" version "5.2.0"
})
//闭包内可以转化为方法的调用,先调用id方法,用id方法返回的对象调用version方法
plugins({
id("com.diffplug.spotless").version("5.2.0")
})
grovvy重点理解的就是两点:一个是闭包,一个是动态调用
核心api一般会在
org.gradle.api.Porjet类中,比如可以在这里搜索buildscript方法
org.gradle.api.Porjet
void buildscript(Closure configureClosure);
org.gradle.api.Porjet
void repositories(Closure configureClosure);
这里的buildscript其实就是调用Project中的buildscript方法
可以在github中gradle的项目中看源码 org.gradle.api.Projcet
2.2 gradle 的构建
gradle lifecycle 生命周期
Gradle 构建具有三个不同的阶段。
-
初始化
Gradle 支持单项目和多项目构建。在初始化阶段,Gradle 确定哪些项目将参与构建,并为每个项目创建一个Project实例。
-
配置
在此阶段配置项目对象。执行作为构建一部分的所有项目的构建脚本。(指的是我不运行这个构建,只是进行一些配置,相当于从头到尾执行这个gradle文件)(可以理解为对一个东西进行配置,对于构建来说,你要对一个构建进行配置,意识是说这个构建中有哪些任务,它运用了哪些插件,它有哪些项目,它可以执行哪些逻辑)
配置阶段会把整个gradle从上到下的执行一遍,所谓的执行就是运行gradle文件中的groovy代码,就是把build.gradle当成groovy代码执行一边 -
执行
Gradle 确定在配置阶段创建和配置的要执行的任务子集。该子集由传递给
gradle命令和当前目录的任务名称参数确定。Gradle 然后执行每个选定的任务。
gradle中执行的最小单元是任务(task)
下方就属于一段非常简单的**构建脚本 **
调用的方法是,第一参数是task的名字,第二是具体执行的配置 Task task(String var1, Closure var2);
```
task("hello1",{
println 'lalalala----------------'
doLast {
println "doLast------------------"
}
})
```
配置阶段互创建一个新的名为hello的任务,使用
{
println 'lalalala----------------'
doLast {
println "doLast------------------"
}
}
这个函数去configure刚刚创建的hello这个task,就是groovy delegate机制(TODO 可以在官网搜索groovy delegate )
gradle DSL允许在不引起歧义的情况下,把括号省略掉
task("hello1") {
println 'lalalala----------------'
doLast {
println "doLast------------------"
}
}
看Task.java ,可以搜索到doLast,含义是把dolast中的这个函数放到这个任务的执行动作列表的末尾,但不真正的执行它,因此在同步代码的时候,doLast------------------不会被打印出来,只有执行hello任务的时候,才会打印。下方是Task中doLast方法官方注释
/**
* <p>Adds the given closure to the end of this task's action list. The closure is passed this task as a parameter
* when executed.</p>
*
* @param action The action closure to execute.
* @return This task.
*/
Task doLast(Closure action);
想要执行doLast中的代码需要执行hello1这个任务
./gradlew hello1
> Configure project :
lalalala----------------
> Task :hello1
doLast------------------
可以看到配置阶段(Configure project)只答应了lalala,任务的执行阶段才打印doLast
Gradle核心模型
gradle的本质就是把lifecycle和一堆api结合起来
Project
一个Projecet就代表一个项目在jvm中的一个实例,非常重要,因为在build.gradle中一切无主的方法(无主的方法),都会去Project上去查找
task 的api也是在Project类中。
projet是一个树状的,可以通过api来查找父project和子project
project.parent.childProjects
getProject().getParent().getChildProjects()
这两行代码是一致的,第一行代码点进去后发现实际调用的就是第二行的代码
get方法中的get是可以省略的
Task 任务
任务之间可以依赖
定义好的任务同步后可以在右侧gradle目录中看到
task定义时,名字可以不存在,可以动态创建task,比如在for循环中创建任务
${name}调用了Task实例中的getName方法。
执行任务可以选择双击右侧gradle中的task名称,也可以选择在下方的terminal中执行命令
groovy中有更方便的循环语句
10.times{i ->
task("supermanman_"+i){
def temp = i;
doLast{
println("Executing task ${temp}")
}
}
}
Task 方法介绍
dependsOn
task('fc'){
doLast{
println("i am first task")
}
}
10.times{i ->
task("supermanman_"+i){
dependsOn('fc')
def temp = i
doLast{
println("Executing task ${temp}")
}
}
}
执行 ./gradlew supermanman_1
输出
> Task :fc
i am first task
> Task :supermanman_1
Executing task 1
doFirst/doLast 只有在任务列表中真正执行了对应的task,才会执行
task('fc'){
println("执行在configuring 阶段")
doLast{
println("i am first task 执行在Execution阶段")
}
}
Lifecycle 与Hook
看如何调用钩子函数,我们之前说过如果遇到无主的函数,就先去看是不是Project类中的方法。Cofiguration阶段本质上就是把build.gradle文件从上到下跑一遍,这时候就称为这个project已经完成evaluate【评估】
afterEvaluate就是钩子函数,参数是一个闭包,执行到这的时候会把这个闭包放到执行列表的最后。表示gradle的执行引擎把build.gradle这个代码脚本从上到下跑一遍,在去执行这个钩子函数中的闭包,闭包就可以看做是函数。
gradle生命周期中的三个阶段中,初始化阶段我们一般不需要关心。
afterEvaluate {
println("after evaluate")
}
task('fc'){
println("执行在configuring 阶段")
doLast{
println("i am first task 执行在Execution阶段")
}
}
10.times{i ->
task("supermanman_"+i){
dependsOn('fc')
def temp = i
doLast{
println("Executing task ${temp}")
}
}
}
命令 ./gradlew supermanman_1
输出
> Configure project :
执行在configuring 阶段
after evaluate
> Task :fc
i am first task 执行在Execution阶段
> Task :supermanman_1
Executing task 1
执行./gradlew supermanman_1的时候还没有这个任务,gradle这时也不关心是否有这个任务。这时候会先进入到配置阶段,配置阶段结束后再去执行具体的task,因为我们在配置阶段的时候会动态的去生成这个task,所以执行阶段的时候就可以找得到
插件编写
所谓的插件,就是一定的业务逻辑的打包的集合
插件的API是Pluglin
/**
* 这个类不需要和执行的代码放在同一个单元中,也就是和apply 可以放在不同的文件中
* 声明完这个类之后,不需要创建这个类的实例,什么也不需要做
* */
class MyPlugin implements Plugin<Project>{
/**
* 对于绝大部分的使用场景来说,这个Object就是Project
* */
@Override
void apply(Project project) {
10.times{i ->
//这里的task是project的方法
project.task("supermanman_"+i){
dependsOn('fc')
def temp = i
doLast{
println("Executing task ${temp}")
}
}
}
}
}
apply plugin:MyPlugin
本质调用的是这个方法void apply(Map<String, ?> var1); 参数是个map
去语法糖就是 apply([plugin:MyPlugin])
apply中map的key还可以是其他的,比如:
apply from: 'maven_pub.gradle'
我们看下apply方法定义在哪里
public interface PluginAware {
PluginContainer getPlugins();
void apply(Closure var1);
void apply(Action<? super ObjectConfigurationAction> var1);
void apply(Map<String, ?> var1);
PluginManager getPluginManager();
}
public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {...
可以看到Project类中继承了这个接口,所以在gradle文件中可以调用到这个方法
下面我们梳理下上方插件的调用逻辑:
- apply plugin:MyPlugin 当我们这么写的时候,就相当于调用了apply这个方法,传进去了一个map参数。
- 当我们执行到这个apply时,就会查找这个plugin的java类,然后调用它的apply方法执行一遍
这个plugin可以放到公共的服务器,不放在这里,这样使用的时候就可以
apply plugin:"http://myserver.com/my-script"
gradle会去下载这个脚本。
如何引用类
如果想在构建中使用某个类,而不是在编译中使用应该怎么办:
第二个阶段:configuiation阶段会把build.gradle从上到下跑一边,相当于把build.gradle放在JVM中执行一遍,
第三个阶段:execution阶段,才会去执行任务---compileJava,这个阶段才会去使用src/main/java/.....java中的类。
所以classpath是分两种的,一部分是build.gradle / build script中需要的classpath,一部分是compile java所需要的classpath(上面讲述的在dependences中声明的依赖就是解决compile java这个问题的)。
如果相为构建脚本声明classpath ,需要在buildscript中去声明仓库和依赖,
如果我们想要在脚本中引入插件,apply plugin:xxxx ,如果插件在远端,则需要在buildscript中的dependencies中进行引用,后面才可以使用apply plugin:xxxx
buildscript {
repositories {
jcenter() {
}
maven {
}
jcenter()
maven { url "https://xxxxx" }
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.xxxx.xxx:xxx-aar:1.0.1'
classpath "com.xxx.xxx.gradle:gradle-xxxx-plugin:0.0.1"
}
}
先在repositories中声明插件所在的仓库地址
再在dependencies中声明依赖
如何搜索依赖库
mvnrepository.com/artifact/co…
apply plugin: 'com.android.application'
是如何被引入的呢
这样我们就可以去使用这个classpath下载下来的所有插件了。
我们可以去上方的仓库中把它下载下来
如何通过apply plugin: 'com.android.application'找到对应的插件呢
当我们调用apply plugin: 'com.android.application'时,我们会通知as我们要使用这个插件,as会去dependencise下的buildscirpt中去寻找里面的classpath里是否包含这个插件,
implementation-class=com.android.build.gradle.AppPlugin
表明要使用这个插件,请你去找AppPlugin这个类,所以gradle的jvm就成功的加载到了AppPlugin这个类并实例化,并调用这个类的实例方法的apply()方法 可以去google搜索这个类AppPlugin
相关网站:
【搜索依赖库的网站】
下载gradle
gradle官方文档
官方文档--Gradle生命周期
【github】gradle源代码
【必看】groovy语言学习(groovy dsl)
plugin官方文档