Gradle
1.基本元素
Project
每个项目的编译至少有一个 Project,一个 build.gradle就代表一个project,每个project里面包含了多个task,task里面又包含很多action,action是一个代码块,里面包含了需要被执行的代码。
Script
gradle的脚本文件,通过脚本,我们可以定义一个Project
Task
Project中的具体执行的原子性工作,以构建一个工程为例,它可以是 编译,执行单元测试,发布 等。
2.Script元素
Init Script
似乎从来没有使用过,但是在每一次构建开始之前,都会执行init script,用来设置一些全局变量,有多个位置可以存放init script如下:
USER_HOME/.gradle/ USER_HOME/.gradle/init.d/ GRADLE_HOME/init.d/
Settings Script
用来在组织多工程的构建,存在于root工程下,settings.gradle,用于生命该工程都包含哪些project
上述script在运行时都会被编译成一个实现了Script接口的class,同时每一个script都有一个委托对象
Build Script -> Project Init Script -> Gradle Settings Script -> Settings
Build Script
每一个build.gradle都是一个Build Scrpit,它由两种元素组成。
statement
可以包含方法调用,属性赋值,局部变量定义等.
script blocks
block的概念稍微复杂一点,首先我们先要理解一个groovy的元素,闭包.
有了闭包的概念,那么理解script block就没有障碍了,直接看文档中的定义:
A script block is a method call which takes a closure as a parameter. The closure is treated as a configuration closure which configures some delegate object as it executes.
翻译一下就是
一个脚本块是一个接受一个闭包作为参数的方法,这个闭包在执行的时候配置它的委托对象。
举个例子
def buildVersion = '1.2.0'def author = 'liuboyu'allprojects { repositories { jcenter() } setVersion(buildVersion) println "this project_${name}_${getVersion()} is created by ${author}"}
首先我们定义了两个变量分别是buildVersion和author,在执行时这个两个变量会成为Script Class的属性。然后,我们使用了一个script block,根据定义,这个block对应着一个同名方法allprojects,可是我们并没有在脚本中定义这样一个方法,那它如何执行呢?回想一下我们刚刚看到的build script的委托对象,没错,这个方法被委托给了Project对象执行,查看文档,我们确实在Project中找到了这个同名方法.
接下来,我们在块中写了两行代码,这就是这个闭包需要执行的代码,首先打印一行文字,其次setVersion()。同样的,我们没有定义setVersion这个方法,这就涉及到闭包的一些概念,我们换一种写法
def buildVersion = '1.2.0'def author = 'liuboyu'allprojects { repositories { jcenter() } delegate.setVersion(buildVersion) println "this project_${delegate.name}_${delegate.getVersion()} is created by ${author}"}
setVersion 这个方法实际上是由闭包的委托对象执行的,那委托对象是什么呢?我们查阅一下allprojects这个方法的Api,如[api文档](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:allprojects(groovy.lang.Closure)
[图片上传失败...(image-76ac25-1537328769306)]
这个闭包的委托对象是当前的project和它的子project,也就是对于一个包含子工程的工程,这个闭包会执行多次,我们实验一下
this project_GradleDeepTest_1.2.0 is created by liuboyuthis project_app_1.2.0 is created by liuboyuthis project_testlibrary_1.2.0 is created by liuboyu
闭包中的 Owner,delegate,this
闭包内部通常会定义一下3种类型:
this corresponds to the enclosing class where the closure is defined
this 对应于闭包定义处的封闭类
owner corresponds to the enclosing object where the closure is defined, which may be either a class or a closure
owner 对应于闭包定义处的封闭对象(可能是一个类或者闭包)
delegate corresponds to a third party object where methods calls or properties are resolved whenever the receiver of the message is not defined
delegate 对应于方法调用或属性处的第三方对象,无论消息接收者是否定义。
this
在闭包中,调用getThisObject将会返回闭包定义处所处的类。等价于使用显示的this:
class Enclosing { void run() { // 定义在Enclosing类中的闭包,并且返回getThisObject def whatIsThisObject = { getThisObject() } // 调用闭包将会返回一个 闭包定义处的类的Enclosing的实例 assert whatIsThisObject() == this // 可以使用简洁的this符号 def whatIsThis = { this } // 返回同一个对象 assert whatIsThis() == this println("Enclosing success " + this) } }
class EnclosedInInnerClass { class Inner { // 内部类中的闭包 Closure cl = { this } } void run() { def inner = new Inner() // 在内部类中的this将会返回内部类,而不是顶层的那个类。 assert inner.cl() == inner println("EnclosedInInnerClass success") } }
class NestedClosures { void run() { def nestedClosures = { // 闭包内定义闭包 def cl = { this } cl() } // this对应于最近的外部类,而不是封闭的闭包! assert nestedClosures() == this } }
class Person { String name int age String toString() { "$name is $age years old" } String dump() { def cl = { String msg = this.toString()//在闭包中使用this调用toString方法,将会调用闭包所在封闭类对象的toString方法,也就是Person的实例 println msg } cl() } } def p = new Person(name:'Janice', age:74)assert p.dump() == 'Janice is 74 years old'
Owner
闭包中的owner和闭包中的this的定义非常的像,只不过有一点微妙的不同:它将返回它最直接的封闭的对象,可以是一个闭包也可以是一个类的:
class Enclosing2 { void run() { // 定义在Enclosing类中的闭包,getOwner def whatIsThisObject = { getOwner() } // 调用闭包将会返回一个 闭包定义处的类的Enclosing的实例 assert whatIsThisObject() == this // 使用简洁的owner符号 def whatIsThis = { owner } // 返回同一个对象 assert whatIsThis() == this println("Enclosing2 success " + this) } }
class EnclosedInInnerClass2 { class Inner { // 内部类中的闭包 Closure cl = { owner } } void run() { def inner = new Inner() // 在内部类中的owner将会返回内部类,而不是顶层的那个类。 assert inner.cl() == inner println("EnclosedInInnerClass success") } }
class NestedClosures2 { void run() { def nestedClosures = { // 闭包内定义闭包 def cl = { owner } cl() } // owner对应的是封闭的闭包,这是不同于this的地方 assert nestedClosures() == nestedClosures } }
Delegate
对于delegate来讲,它的含义大多数情况下是跟owner的含义一样,除非它被显示的修改(通过Closure.setDelegate()方法进行修改)。
class Enclosing3 { void run() { // 获得闭包的delegate可以通过调用getDelegate方法 def cl = { getDelegate() } // 使用delegate属性 def cl2 = { delegate } // 二者返回同样的对象 assert cl() == cl2() // 是封闭的类或这闭包 assert cl() == this // 特别是在闭包的内部的闭包 def closure = { // 闭包内定义闭包 def cl3 = { delegate } cl3() } // delegate对应于owner返回同样的对象或者闭包 assert closure() == closure } }
def scriptClosure={ println "scriptClosure this:"+this println "scriptClosure owner:"+owner println "scriptClosure delegate:"+delegate } println "before setDelegate()" scriptClosure.call() scriptClosure.setDelegate ("abc") println "after setDelegate()" scriptClosure.call()
结果:
before setDelegate() scriptClosure this:class Client2scriptClosure owner:class Client2scriptClosure delegate:class Client2after setDelegate() scriptClosure this:class Client2scriptClosure owner:class Client2scriptClosure delegate:abc
闭包的delegate可以被更改为任意的对象。先定义两个相互之间没有继承关系的类,二者都定义了一个名为name的属性:
class Person { String name def upperCasedName = { delegate.name.toUpperCase() } }def p1 = new Person(name:'Janice', age:74)def p2 = new Person(name:'liuboYu', age:18)
然后,定义一个闭包通过delegate获取一下name属性:
p1.upperCasedName.delegate = p2 println(p1.upperCasedName())
然后,通过改变闭包的delegate,你可以看到目标对象发生了改变:
JANICE LIUBOYU
委托机制
无论何时,在闭包中,访问一个属性,不需要指定接收对象,这时使用的是delegation strategy:
class Person { String name } def person = new Person(name:'Igor') def cl = { name.toUpperCase() } //name不是闭包括号内的一个变量的索引cl.delegate = person //改变闭包的delegate为Person的实例assert cl() == 'IGOR'//调用成功
之所以可以这样调用的原因是name属性将会自然而然的被delegate的对象征用。这样很好的解决了闭包内部属性或者方法的调用。不需要显示的设置(delegate.)作为接收者:调用成功是因为默认的闭包的delegation strategy使然。闭包提供了多种策略方案你可以选择:
Closure.OWNER_FIRST 是默认的策略。如果一个方法存在于owner,然后他将会被owner调用。如果不是,然后delegate将会被使用
Closure.Delegate_FIRST 使用这样的逻辑:delegate首先使用,其次是owner
Closure.OWNER_ONLY 只会使用owner:delegate会被忽略
Closure.DELEGATE_ONLY 只用delegate:忽略owner
Closure.TO_SELF can be used by developers who need advanced meta-programming techniques and wish to implement a custom resolution strategy: the resolution will not be made on the owner or the delegate but only on the closure class itself. It makes only sense to use this if you implement your own subclass of Closure.
使用下面的代码来描绘一下”owner first”
class Person { String name def pretty = { "My name is $name" } //定义一个执行name的闭包成员 String toString() { pretty() } }class Thing { String name //类和Person和Thing都定义了一个name属性} def p = new Person(name: 'Sarah') def t = new Thing(name: 'Teapot')assert p.toString() == 'My name is Sarah'//使用默认的机制,name属性首先被owner调用p.pretty.delegate = t //设置delegate为Thing的实例对象tassert p.toString() == 'My name is Sarah'//结果没有改变:name被闭包的owner调用
然而,改变closure的解决方案的策略改变结果是可以的:
p.pretty.resolveStrategy = Closure.DELEGATE_FIRSTassert p.toString() == 'My name is Teapot'
通过改变resolveStrategy,我们可以改变Groovy”显式this”的指向:在这种情况下,name将会首先在delegate中找到,如果没有发现则是在owner中寻找。name被定义在delegate中,Thing的实例将会被使用。
“delegate first”和”delegate only”或者”owner first”和”owner only”之间的区别可以被下面的这个其中一个delegate没有某个属性/方法的例子来描述:
class Person { String name int age def fetchAge = { age } }class Thing { String name } def p = new Person(name:'Jessica', age:42) def t = new Things(name:'Printer') def cl = p.fetchAge cl.delegate = p assert cl() == 42 cl.delegate = t assert cl() == 42 cl.resolveStrategy = Closure.DELEGATE_ONLY cl.delegate = p assert cl() == 42 cl.delegate = t try { cl() } catch (MissingPropertyException ex) { println(" \"age\" is not defined on the delegate") }
在这个例子中,我们定义了两个都有name属性的类,但只有Person具有age属性。Person类同时声明了一个指向age的闭包。我们改变默认的方案策略,从”owner first”到”delegate only”。由于闭包的owner是Person类,如果delegate是Person的实例,将会成功调用这个闭包,但是如果我们调用它,且它的delegate是Thing的实例,将会调用失败,并抛出groovy.lang.MissingPropertyException。尽管这个闭包定义在Person类中,但owner没有被使用。
image
subprojects、dependencies、repositories 都是 script blocks,后面都需要跟一个花括号,通过查阅文档可以发现,其实就是个闭包。
我们通过源码可以查看
/** * <p>Configures the build script classpath for this project. * * <p>The given closure is executed against this project's {@link ScriptHandler}. The {@link ScriptHandler} is * passed to the closure as the closure's delegate. * * @param configureClosure the closure to use to configure the build script classpath. */ void buildscript(@DelegatesTo(value = ScriptHandler.class, strategy = Closure.DELEGATE_FIRST) @ClosureParams(value = SimpleType.class, options = {"org.gradle.api.initialization.dsl.ScriptHandler"}) Closure configureClosure);
它的 closure 是在一个类型为 ScriptHandler 的对象上执行的。主意用来所依赖的 classpath 等信息。通过查看 ScriptHandler API 可知,在 buildscript SB 中,你可以调用 ScriptHandler 提供的 repositories(Closure )、dependencies(Closure)函数。这也是为什么 repositories 和 dependencies 两个 script blocks 为什么要放在 buildscript 的花括号中的原因。
repositories 表示代码仓库的下载来源,默认的来源是jcenter。
Gradle支持的代码仓库有几种类型:
Maven中央仓库,不支持https访问,声明方法为mavenCentral()
JCenter中央仓库,实际上也是用Maven搭建,通过CDN分发,并且支持https访问,也就是我们上面默认的声明方法:jcenter
Maven本地仓库,可以通过本地配置文件配置,通过USER_HOME/.m2/下的settings.xml配置文件修改默认路径位置,声明方法为mavenLocal()
常规的第三方maven库,需要设置访问url,声明方法为maven,这个一般是有自己的maven私服
Ivy仓库,可以是本地仓库,也可以是远程仓库
直接使用本地文件夹作为仓库
dependencies 表明项目依赖对应版本的Gradle构建工具,但更加具体的版本信息却是在gradle-wrapper.properties这个文件中,具体如:
#Tue Oct 31 15:31:02 CST 2017distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
allprojects 指定所有参与构建的项目使用的仓库的来源。
这里我们有一个疑问:buildscript和allprojects都指定使用的仓库的来源,它们的真正区别在哪里呢?
buildScript块的repositories主要是为了Gradle脚本自身的执行,获取脚本依赖插件。
根级别的repositories主要是为了当前项目提供所需依赖包,但在Android中,这个跟allprojects的repositories的作用是一样的。同样dependencies也可以是根级别的。
allprojects块的repositories用于多项目构建,为所有项目提供共同所需依赖包。而子项目可以配置自己的repositories以获取自己独需的依赖包。
实际上,allprojects是用于多项目构建,在Android中,使用多项目构建,其实就是多Module构建。
我们看settings.gradle这个文件:
include ':app', ':testlibrary'
通过include将app这个module添加进来。从根目录开始,一直到include进来的所有module,都会执行allprojects的内容。
我们也可以通过subprojects来指定module和根目录不同的行为。
例如:
subprojects { println " ---${delegate.name}---subprojects------ "}
上面的例子,只有子project会执行,root project并不会执行
或者我们也可以单独指定某个project自己的行为。
我们是否可以指定某个module执行它特有的行为呢?
例如:
project(':testlibrary'){ println " ---testlibrary---subprojects------ "} project(':app'){ println " ---app---subprojects------ "}
Android自己也定义了好多ScriptBlock,请参考DSL参考文档
image
下图为 buildToolsVersion 和 compileSdkVersion 的说明:
image
接下来看一下
defaultConfig { applicationId "test.project.com.gradledeeptest" minSdkVersion 15 targetSdkVersion 22 versionCode 1 versionName "1.0" }
[图片上传失败...(image-1b57f5-1537328769307)]
3.Gradle 组成
Gradle 主要有三种对象,这三种对象和三种不同的脚本文件对应,在 gradle 执行的时候,会将脚本转换成对应的对端:
Gradle 对象:当我们执行 gradle xxx 或者什么的时候,gradle 会从默认的配置脚本中构造出一个 Gradle 对象。在整个执行过程中,只有这么一个对象。Gradle 对象的数据类型就是 Gradle。我们一般很少去定制这个默认的配置脚本。
Project 对象:每一个 build.gradle 会转换成一个 Project 对象。
Settings 对象:显然,每一个 settings.gradle 都会转换成一个 Settings 对象。
4.Gradle 对象
[图片上传失败...(image-2f9871-1537328769307)]
我们写个例子,验证只有全局只有一个gradle,build.gradle 中和 settings.gradle 中分别加了如下输出:
println "setting In posdevice, gradle id is " + gradle.hashCode() println "setting version: " + gradle.gradleVersion
得到结果如下所示:
setting In posdevice, gradle id is 1466991549setting version: 4.1library In posdevice, gradle id is 1466991549library version: 4.1app In posdevice, gradle id is 1466991549app version: 4.1
在 settings.gradle 和 posdevice build.gradle 中,我们得到的 gradle 实例对象的 hashCode 是一样的(都是 791279786)
gradleVersion输出当前 gradle 的版本
5.Project 对象
每一个 build.gradle 文件都会转换成一个 Project 对象。在 Gradle 术语中,Project 对象对应的是 Build Script。
Project 包含若干 Tasks。另外,由于 Project 对应具体的工程,所以需要为 Project 加载所需要的插件,比如为 Android 工程加载 android 插件。一个 Project 包含多少 Task 往往是插件决定的。
创建一个Settings对象,
根据settings.gradle文件配置它
根据Settings对象中定义的工程的父子关系创建Project对象
执行每一个工程的build.gradle文件配置上一步中创建的Project对
加载插件
image
apply plugin: 'com.android.library' <==如果是编译 Library,则加载此插件 apply plugin: 'com.android.application' <==如果是编译 Android APP,则加载此插件
除了加载二进制的插件(上面的插件其实都是下载了对应的 jar 包,这也是通常意义上我们所理解的插件),还可以加载一个 gradle 文件
apply from: "utils.gradle"
6.全局变量 ext
我们前面讲解了gradle的生命周期,在配置的过程中,整个项目会生成一个gradle 对象,每个build.gradle的文档都会生成一个project对象。这两个对象都有一个ext,这个ext的属性就类似于我们的钱包一样,独立属于gradle与project对象。我们可以往这个ext对象里面放置属性。
6.1 gradle 的ext对象
我们可以使用这样的方法存储一个变量,这个变量属于gradle,整个工程都能使用
//第一次定义或者设置它的时候需要 ext 前缀 gradle.ext.api = properties.getProperty('sdk.api') println gradle.api //再次存取 api 的时候,就不需要 ext 前缀了
或者直接在 gradle.properties 文件中添加属性,作用域也是全局的
读取方式如下
task A{ doLast{ println(gradle.api) } }
6.2 project 的ext对象
保存值
ext{ myName ='sohu' age = 18 }
获取值,可以直接获取
println(myName)
上面这个代码 println(myName) 就等于println (project.ext.myName)
我们一般在ext内存储一些通用的变量,除此以外,我们也使用这个ext来做一些很酷的功能,比如说我们的gradle文件很大了,我们可以好像代码一下,进行抽取。
project 的ext对象 的作用域是当前的project
7.生命周期
1212336-5ab3ae4bc237b401.png
初始化阶段:
主要是解析 setting.gradle 文件,gradle支持单工程和多工程构建,在初始化的过程中,gradle决定了这次构建包含哪些工程,并且为每一个工程创建一个Project对象。并且,所有在Settings script中包含的工程的build script都会执行,因为gradle需要为每一个Project对象配置完整的信息。
读取配置阶段:
主要是解析所有的 projects 下的 build.gradle 文件,在配置的过程中,本次构建包含的所有工程的build script 都会执行一次,同时每个工程的Project对象都会被配置,运行时需要的信息在这个过程中被配置到Projec对象中。最重要的是,在build script中定义的task将在这个过程创建,并被初始化。需要注意的是,在一般情况下,只要在初始化阶段创建的Project对象都会被配置,即使这个工程没有参与本次构建。
执行阶段:
按照 2 中建立的有向无循环图来执行每一个 task ,整个编译过程中,这一步基本会占去 9 成以上的时间,尤其是对于 Android 项目来讲,将 java 转为 class.
8.项目应用
a. Gradle生命周期回调
gradle提供了对project状态配置监听的接口回调,以方便我们来配置一些Project的配置属性,监听主要分为两大类,一种是通过project进行回调,一种是通过gradle进行回调。
作用域:
project是只针对当前project实现进行的监听
gradle监听是针对于所有的project而言的
下面我们通过一个例子来看看gradle的构建生命周期究竟是怎么样的。
项目结构
root Project:GradleDeepTest -- app build.gradle -- testlibrary build.gradle build.gradle settings.gradle
root/settings.gradle
println "#### setting srcipt execute "include ':app', ':testlibrary'
root/build.gradle
println "#### root build.gradle execute "def buildVersion = '1.2.0'def author = 'liuboyu'buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.0.0' } } allprojects { repositories { jcenter() }// it.setVersion(buildVersion)// println "this project_${delegate.name}_${delegate.getVersion()} is created by ${author}"} gradle.addProjectEvaluationListener(new ProjectEvaluationListener() { @Override void beforeEvaluate(Project project) { println "ROOT gradle_Project lifecycle : beforeEvaluate ${project.name} evaluate " } @Override void afterEvaluate(Project project, ProjectState state) { println "ROOT gradle_Project lifecycle : afterEvaluate ${project.name} evaluate " } }) gradle.addBuildListener(new BuildListener() { @Override void buildStarted(Gradle gradle) { println "==ROOT== gradle Build lifecycle : buildStarted ${project.name} evaluate " } @Override void settingsEvaluated(Settings settings) { println "==ROOT== gradle Build lifecycle : settingsEvaluated ${project.name} evaluate " } @Override void projectsLoaded(Gradle gradle) { println "==ROOT== gradle Build lifecycle : projectsLoaded ${project.name} evaluate " } @Override void projectsEvaluated(Gradle gradle) { println "==ROOT== gradle Build lifecycle : projectsEvaluated ${project.name} evaluate " } @Override void buildFinished(BuildResult result) { println "==ROOT== gradle Build lifecycle : buildFinished ${project.name} evaluate " } })
root/app/build.gradle
println "#### app build.gradle execute "apply plugin: 'com.android.application'android { compileSdkVersion 27 buildToolsVersion "26.0.2" defaultConfig { applicationId "test.project.com.gradledeeptest" minSdkVersion 15 targetSdkVersion 25 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } project.afterEvaluate { println "%%%% app lifecycle : afterEvaluate ${project.name} evaluate "} project.beforeEvaluate { println "%%%% app lifecycle : beforeEvaluate ${project.name} evaluate "} dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:22.2.0' compile 'com.android.support:design:22.2.0'}
root/testlibrary/build.gradle
println "#### library build.gradle execute "apply plugin: 'com.android.library'android { compileSdkVersion 27 buildToolsVersion "26.0.2" defaultConfig { minSdkVersion 15 targetSdkVersion 25 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } project.afterEvaluate { println "@@@@ testlibrary lifecycle : afterEvaluate ${project.name} evaluate "} project.beforeEvaluate { println "@@@@ testlibrary lifecycle : beforeEvaluate ${project.name} evaluate "} dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:22.2.0'}
运行结果
#### setting srcipt execute // settings.gradle 加载#### root build.gradle execute // root build.gradle 加载ROOT gradle_Project lifecycle : afterEvaluate GradleDeepTest evaluate // root project 配置完成ROOT gradle_Project lifecycle : beforeEvaluate testlibrary evaluate // testlibrary project 开始配置#### library build.gradle execute // library build.gradle 加载ROOT gradle_Project lifecycle : afterEvaluate testlibrary evaluate // testlibrary project 配置完成@@@@ testlibrary lifecycle : afterEvaluate testlibrary evaluate // testlibrary project 配置完成ROOT gradle_Project lifecycle : beforeEvaluate app evaluate // app project 开始配置#### app build.gradle execute // app build.gradle 加载ROOT gradle_Project lifecycle : afterEvaluate app evaluate // app project 配置完成%%%% app lifecycle : afterEvaluate app evaluate // app project 配置完成==ROOT== gradle Build lifecycle : projectsEvaluated GradleDeepTest evaluate // root project 配置完成... BUILD SUCCESSFUL in 2s ==ROOT== gradle Build lifecycle : buildFinished GradleDeepTest evaluate // root project build完
作者:伯羽君
链接:https://www.jianshu.com/p/fbd27a1212c8
共同学习,写下你的评论
评论加载中...
作者其他优质文章