Gradle是一个灵活和高效自动化构建工具,它的构建脚本采用Groovy或kotlin语言编写,Groovy或Kotlin都是基于JVM的语言,它们的语法和java的语法有不少的相似而且兼容java的语法,因此对于java开发者,只需不多的学习成本就能快速上手Gradle开发,同时Gradle也是Android官方的构建工具,学习Gradle,可以帮助咱们更好的了解Android项目的构建过程,当项目构建出现问题时,咱们也能更好的排查问题,因此Gradle的学习能帮助咱们更好的管理Android项目,Gradle的官方地址以下:html
Gradle官网java
Github地址linux
一、Gradle构建脚本采用Groovy或Kotlin语言编写,若是采用Groovy编写,构建脚本后缀为.gradle,在里面可使用Groovy语法,若是采用Kotlin编写,构建脚本后缀为.gradle.kts,在里面可使用Kotlin语法;android
二、由于Groovy或Kotlin都是面向对象语言,因此在Gradle中到处皆对象,Gradle的.gradle或.gradle.kts脚本本质上是一个Project对象,在脚本中一些带名字的配置项如buildscript、allprojects等本质上就是对象中的方法,而配置项后面的闭包{}就是参数,因此咱们在使用这个配置项时本质上是在调用对象中的一个方法;git
三、在Groovy或Kotlin中,函数和类同样都是一等公民,它们都提供了很好的闭包{}支持,因此它们很容易的编写出具备DSL风格的代码,用DSL编写构建脚本的Gradle比其余采用xml编写构建脚本的构建工具如maven、Ant等的可读性更强,动态性更好,总体更简洁;github
四、Gradle中主要有Project和Task对象,Project是Gradle中构建脚本的表示,一个构建脚本对应一个Project对象,Task是Gradle中最小的执行单元,它表示一个独立的任务,Project为Task提供了执行的上下文。api
本文的全部示例都是采用Groovy语言编写,在阅读本文前先简单的入门Groovy:闭包
下面主要讲Groovy与java的主要区别:maven
一、Groovy语句后面的分号能够忽略
int num1 = 1
int num2 = 2
int result = add(num1, num2)
int add(int a, int b){
return a + b
}
复制代码
二、Groovy支持动态类型推导,使用def来定义变量和方法时能够不指定变量的类型和方法的返回值类型,同时定义方法时参数能够不指定类型
def num1 = 1
def num2 = 2
def result = add(num1, num2)
def add(a, b){
return a + b
}
复制代码
三、Groovy的方法调用传参时能够不添加括号,方法不指定return语句时,最后一行默认为返回值
def result = add 1, 2
def add(a, b){
a + b
}
复制代码
四、Groovy能够用单、双、三引号来表示字符串,其中单引号表示普通字符串,双引号表示的字符串可使用取值运算符${},而$在单引号只只是表示一个普通的字符,三引号表示的字符串又称为模版字符串,它能够保留文本的换行和缩进格式,三引号一样不支持$
def world = 'world'
def str1 = 'hello ${world}'
def str2 = "hello ${world}"
def str3 = '''hello &{world}'''
//打印输出:
//hello ${world}
//hello world
//hello
//&{world}
复制代码
五、Groovy会为类中每一个没有可见性修饰符的字段生成get/set方法,咱们访问这个字段实际上是调用它的get/set方法,同时若是类中的方法以get/set开头,咱们也能够像普通字段同样访问这个方法
class Person{
def name
private def country
def setNation(nation){
this.country = nation
}
def getNation(){
return country
}
}
def person = new Person()
//访问字段
person.name = 'rain9155'
println person.name
//像字段同样访问这个以get/set开头的方法
person.nation = "china"
println person.nation
复制代码
六、Groovy中的闭包是用{参数列表 -> 代码体}表示的代码块,当参数 <= 1个时,-> 箭头能够省略,同时当只有一个参数时,若是不指定参数名默认以it为参数名,在Groovy中闭包对应的类型是Closure,因此闭包能够做为参数传递,同时闭包有一个delegate字段, 经过delegate能够把闭包中的执行代码委托给任意对象来执行
class Person{
def name
def age
}
def person = new Person()
//定义一个闭包
def closure = {
name = 'rain9155'
age = 21
}
//把闭包委托给person对象执行
closure.delegate = person
//执行闭包,或者调用closure.call()也能够执行闭包
closure()
//以上代码把闭包委托给person对象执行,因此闭包就执行时就处于person对象上下文
//因此闭包就能够访问到person对象中的name和age字段,完成赋值
println person.name//输出:rain9155
println person.age//输出:21
复制代码
在Gradle中不少地方都使用了闭包的委托机制,经过闭包完成一些特定对象的配置,在Gradle中,若是你没有指定闭包的delegate,delegate默认为当前项目的Project对象。
以上Groovy知识,认为对于有java基础的人来讲,用于学习Gradle足够了,固然对于一些集合操做、文件操做等,等之后使用到时能够到Groovy官网来查漏补缺。
官方教程:Installing Gradle
安装Gradle前须要确保你的电脑已经配置好JDK,JDK的版本要求是8或更高,能够经过包管理器自动安装Gradle或手动下载Gradle安装两种方式,在Window平台下我推荐使用手动安装,在Mac平台下我推荐使用Homebrew包管理器自动安装:
gradle -v
校验是否配置成功,输出如下相似信息则配置成功.C:\Users\HY>gradle -v
------------------------------------------------------------
Gradle 6.5
------------------------------------------------------------
Build time: 2020-06-02 20:46:21 UTC
Revision: a27f41e4ae5e8a41ab9b19f8dd6d86d7b384dad4
Kotlin: 1.3.72
Groovy: 2.5.11
Ant: Apache Ant(TM) version 1.10.7 compiled on September 1 2019
JVM: 10.0.2 ("Oracle Corporation" 10.0.2+13)
OS: Windows 10 10.0 amd64
复制代码
brew install gradle
,它默认会下载安装binary-only版本;gradle -v
校验是否安装成功.Gradle项目可使用Android Studio、IntelliJ IDEA等IDE工具或文本编辑器来编写,这里我以Mac平台为例采用文本编辑器配合命令行来编写,Window平台相似。
新建一个目录,如我这里为:~/GradleDemo,打开命令行,输入cd ~/GradleDemo
切换到这个目录,而后输入gradle init
,接着gradle会执行init
这个task任务,它会让你选择生成的项目模版、编写脚本的语言、项目名称等,我选择了basic模版(即原始的Gradle项目)、Groovy语言、项目名称为GradleDemo,以下:
这样会init
任务就会自动替你生成相应的项目模版,以下:
忽略掉以.开头的隐藏文件或目录,gradle init为咱们自动生成了如下文件或目录:
它表示Gradle的项目构建脚本,在里面咱们能够经过Groovy来编写脚本,在Gradle中,一个build.gradle就对应一个项目,build.gradle放在Gradle项目的根目录下,表示它对应的是根项目,build.gradle放在Gradle项目的其余子目录下,表示它对应的是子项目,Gradle构建时会把build.gradle解析成Project对象,你在里面编写的DSL,其实就是Project接口中的方法。
它表示Gradle的多项目配置脚本,存放在Gradle项目的根目录下,在里面能够经过include来决定哪些子项目会参与构建,Gradle构建时会把settings.gradle解析成Settings对象,include也只是Settings接口中的一个方法。
gradle init
执行时会同时执行wrapper
任务,wrapper
任务会建立gradle/wrapper目录,并建立gradle/wrapper目录下的gradle-wrapper.jar、gradle-wrapper.properties这两个文件,还同时建立gradlew、gradlew.bat这两个脚本,它们统称为Gradle Wrapper,是对Gradle的一层包装。
Gradle Wrapper的做用就是可让你的电脑在不安装配置Gradle环境的前提下运行Gradle项目,例如当你的Gradle项目被用户A clone下来时,而用户A的电脑上没有安装配置Gradle环境,用户A经过Gradle构建项目时,Gradle Wrapper就会从指定下载位置下载Gradle,并解压到电脑的指定位置,而后用户A就能够在不配置Gradle系统变量的前提下在Gradle项目的命令行中运行gradlew或gradlew.bat脚原本使用gradle命令,假设用户A要运行gradle -v
命令,在linux平台下只须要运行./gradle -v
,在window平台下只须要运行gradlew -v
,只是把gradle
替换成gradlew
。
Gradle Wrapper的每一个文件含义以下:
一、gradlew:用于在linux平台下执行gradle命令的脚本;
二、gradlew.bat:用于在window平台下执行gradle命令的脚本;
三、gradle-wrapper.jar:包含Gradle Wrapper运行时的逻辑代码;
四、gradle-wrapper.properties:用于指定Gradle的下载位置和解压位置;
gradle-wrapper.properties中各个字段解释以下:
字段名 | 解释 |
---|---|
distributionBase | 下载的Gradle的压缩包解压后的主目录,为GRADLE_USER_HOME,在window中它表示C:/用户/你电脑登陆的用户名/.gradle/,在mac中它表示~/.gradle/ |
distributionPath | 相对于distributionBase的解压后的Gradle的路径,为wrapper/dists |
distributionUrl | Grade压缩包的下载地址,在这里能够修改下载的Gradle的版本和版本类型(binary或complete),例如gradle-6.5-all.zip表示Gradle 6.5的complete版本,gradle-6.5-bin.zip表示Gradle 6.5的binary版本 |
zipStoreBase | 同distributionBase,不过是表示存放下载的Gradle的压缩包的主目录 |
zipStorePath | 同distributionPath,不过是表示存放下载的Gradle的压缩包的路径 |
使用Gradle Wrapper后,就能够统一项目在不一样用户电脑上的Gradle版本,同时没必要让运行这个Gradle项目的人安装配置Gradle环境,提升了开发效率。
如今咱们建立的Gradle项目默认已经有一个根项目了,它的build.gradle文件就处于Gradle项目的根目录下,若是咱们想要添加多个子项目,这时就须要经过settings.gradle进行配置。
首先咱们在GradleDemo中建立多个文件夹,这里我建立了4个文件夹,分别为:subproject_一、subproject_2,subproject_3,subproject_4,而后在每一个新建的文件夹下建立build.gradle文件,以下:
接着打开settings.gradle,添加以下:
include ':subproject_1', ':subproject_2', ':subproject_3', ':subproject_4'
复制代码
这样就完成了子项目的添加,打开命令行,切换到GradleDemo目录处,输入gradle projects
,执行projects任务展现全部项目之间的依赖关系,以下:
# chenjianyu @ FVFCG2HJL414 in ~/GradleDemo
$ gradle projects
> Task :projects
Root project 'GradleDemo'
+--- Project ':subproject_1'
+--- Project ':subproject_2'
+--- Project ':subproject_3'
\--- Project ':subproject_4'
BUILD SUCCESSFUL in 540ms
1 actionable task: 1 executed
复制代码
能够看到,4个子项目依赖于根项目,接下来咱们来配置项目,配置项目通常在当前项目的build.gradle中进行,能够经过buildscript方法配置,可是若是有多个项目时,而每一个项目的某些配置又同样,那么在每一个项目的build.gradle进行相同的配置是很浪费时间,而Gradle的Project接口为咱们提供了allprojects和subprojects方法,在根项目的build.gradle中使用这两个方法能够全局的为全部子项目进行配置,allprojects和subprojects的区别是:allprojects的配置包括根项目而subprojects的配置不包括根项目,例如:
//根项目的build.gradle
//为全部项目添加maven repo地址
allprojects {
repositories {
mavenCentral()
}
}
//为全部子项目添加groovy插件
subprojects {
apply plugin: 'groovy'
}
复制代码
当在命令行输入gradle build
构建整个项目或gradle task名称
执行某个任务时就会进行Gradle的构建,它的构建过程分为3个阶段:
init(初始化阶段) -> configure(配置阶段) -> execute(执行阶段)
Gradle在上面3个阶段中每个阶段的开始和结束都会hook一些监听,暴露给开发者使用,方便开发者在Gradle的不一样生命周期阶段作一些事情。
settings.gradle和build.gradle分别表明Settings对象和Project对象,它们都有一个Gradle对象,咱们能够在Gradle项目根目录的settings.gradle或build.gradle中获取到Gradle对象,而后进行生命周期监听,以下:
//build.gradle或settings.gradle
this.gradle.buildStarted {
println "Gradle构建开始"
}
//---------------------init开始--------------------------------
this.gradle.settingsEvaluated {
println "settings.gradle解析完成"
}
this.gradle.projectsLoaded {
println "全部项目从settings加载完成"
}
//---------------------init结束--------------------------------
//-------------------configure开始-----------------------------
this.gradle.beforeProject {project ->
//每个项目构建以前被调用
println "${project.name}项目开始构建"
}
this.gradle.afterProject {project ->
//每个项目构建完成被调用
println "${project.name}项目构建完成"
}
this.gradle.projectsEvaluated {
println "全部项目构建完成"
}
this.gradle.taskGraph.whenReady {
println("task图构建完成")
}
//-------------------configure结束-----------------------------
//-------------------execute开始-----------------------------
this.gradle.taskGraph.beforeTask {task ->
//每一个task开始执行时会调用这个方法
println("${task.name}task开始执行")
}
this.gradle.taskGraph.afterTask {task ->
//每一个task执行结束时会调用这个方法
println("${task.name}task执行完成")
}
//-------------------execute结束-----------------------------
this.gradle.buildFinished {
println "Gradle构建结束"
}
复制代码
上面监听方法的放置顺序就是整个Gradle构建的顺序,可是要注意的是Gradle的buildStarted方法永远不会被回调,由于咱们注册监听的时机太晚了,当解析settings.gradle或build.gradle时,Gradle就已经构建开始了,因此这个方法也被Gradle标记为废弃的了,由于咱们没有机会监听到Gradle构建开始,同时若是你是在build.gradle中添加上面的全部监听,那么Gradle的settingsEvaluated和projectsLoaded方法也不会被回调,由于settings.gradle的解析是在build.gradle以前,在build.gradle中监听这两个方法的时机也太晚了。
也能够经过Gradle对象的addBuildListener方法添加BuildListener来监听Gradle的整个生命周期回调
上面是监听整个Gradle构建的生命周期回调监听,那么我怎么监听个人当前单个项目的构建开始和结束呢?只须要在你当前项目的build.gradle中添加:
//build.gradle
this.beforeEvaluate {
println '项目开始构建'
}
this.afterEvaluate {
println '项目构建结束'
}
复制代码
可是要注意的是在根项目的build.gradle添加上述方法,其beforeEvaluate方法是没法被回调的,由于注册时机太晚,解析根项目的的build.gradle时根项目已经开始构建了,可是子项目的build.gradle添加上述方法是能够监听到项目构建的开始和结束,由于根项目构建完成后才会轮到子项目的构建。
Task是Gradle中最小执行单元,它是一个接口,默认实现类为DefaultTask,在Project中提供了task方法来建立Task,因此Task的建立必需要处于Project上下文中,这里我在subproject_1/build.gradle中建立Task,以下:
//subproject_1/build.gradle
//经过Project的task方法建立一个Task
task task1{
doFirst{
println 'one'
}
doLast{
println 'two'
}
}
复制代码
上述代码经过task方法建立了一个名为task1的Task,在建立Task的同时能够经过闭包配置它的doFirst和doLast动做,doFirst和doLast都是Task中的方法,其中doFirst方法会在Task的action执行前执行,doLast方法会在Task的action执行后执行,而action就是Task的执行单元,在后面自定义Task会介绍到,除此以外还能够在建立Task以后再指定它的doFirst和doLast动做,以下:
//subproject_1/build.gradle
//经过Project的task方法建立一个Task
def t = task task2
t.doFirst {
println 'one'
}
t.doLast{
println 'two'
}
复制代码
上面经过Project的task方法建立的Task默认被放在Project的TaskContainer类型的容器中,咱们能够经过Project的getTasks方法获取到这个容器,而TaskContainer提供了create方法来建立Task,以下:
//subproject_1/build.gradle
//经过TaskContainer的create方法建立一个Task
tasks.create(name: 'task3'){
doFirst{
println 'one'
}
doLast{
println 'two'
}
}
复制代码
以上就是建立Task最经常使用的几种方式,建立Task以后,就能够执行它,执行一个Task只须要把task名称接在gradle
命令后,以下我在命令行输入gradle task1
执行了task1:
# chenjianyu @ FVFCG2HJL414 in ~/GradleDemo
$ gradle task1
> Task :subproject_1:task1
one
two
BUILD SUCCESSFUL in 463ms
1 actionable task: 1 executed
复制代码
若是你想精简输出的信息,只须要添加-q
参数,如:gradle -q task1
,这样输出就只包含task1的输出:
# chenjianyu @ FVFCG2HJL414 in ~/GradleDemo
$ gradle -q task1
one
two
复制代码
若是要执行多个Task,多个task名称接在gradle
命令后用空格隔开就行,如:gradle task1 task2 task3
。
Gradle为每一个Task定义了默认的属性(Property), 好比description、group、dependsOn、inputs、outputs等, 咱们能够配置这些Property,以下配置Task的描述和分组:
//subproject_2/build.gradle
//咱们能够在定义Task时对这些Property进行赋值
task task1{
group = 'MyGroup'
description = 'Hello World'
doLast{
println "task分组:${group}"
println "task描述:${description}"
}
}
复制代码
Gradle在执行一个Task以前,会先配置这个Task的Property,而后再执行这个Task的执行代码块,因此配置Task的代码块放在哪里都无所谓,以下:
//subproject_2/build.gradle
//在定义Task以后才对Task进行配置
task task2{
doLast{
println "task分组:${group}"
println "task描述:${description}"
}
}
task2{
group = 'MyGroup'
description = 'Hello World'
}
//等效于task2
task task3{
doLast{
println "task分组:${group}"
println "task描述:${description}"
}
}
task3.description = 'Hello World!'
task3.group = "MyGroup"
//等效于task3
task task4{
doLast{
println "task分组:${group}"
println "task描述:${description}"
}
}
task4.configure{
group = 'MyGroup'
description = 'Hello World'
}
复制代码
咱们能够经过Task的dependsOn属性指定Task之间的依赖关系,以下:
//subproject_2/build.gradle
//建立Task时经过dependsOn声明Task之间的依赖关系
task task5(dependsOn: task4){
doLast{
println 'Hello World'
}
}
//或者在建立Task以后再声明task之间的依赖关系
task4.dependsOn task3
复制代码
上述的依赖关系是task3 -> task4 -> task5,依赖的Task先执行,因此当我在命令行输入gradle task5
执行task5时,输出:
# chenjianyu @ FVFCG2HJL414 in ~/GradleDemo
$ gradle task5
> Task :subproject_2:task3
task分组:MyGroup
task描述:Hello World!
> Task :subproject_2:task4
task分组:MyGroup
task描述:Hello World
> Task :subproject_2:task5
Hello World
task5task执行完成
BUILD SUCCESSFUL in 2s
3 actionable tasks: 3 executed
复制代码
依次执行task三、 task4 、task5。
前面建立的Task默认都是DefaultTask类型,咱们能够经过继承DefaultTask来自定义Task类型,Gradle中也内置了不少具备特定功能的Task,它们都间接继承自DefaultTask,如Copy(复制文件)、Delete(文件清理)等,咱们能够直接在build.gradle中自定义Task,以下:
//subproject_3/build.gradle
class MyTask extends DefaultTask{
def message = 'hello world from myCustomTask'
@TaskAction
def println1(){
println "println1: $message"
}
@TaskAction
def println2(){
println "println2: $message"
}
}
复制代码
在MyTask中,经过@TaskAction注解的方法就是该Task的action,action是Task最主要的组成,它表示Task的一个执行动做,当Task中有多个action时,多个action的执行顺序按照@TaskAction注解的方法的放置顺序,因此执行一个Task的过程就是:doFirst方法 -> action方法 -> doLast方法,在MyTask中定义了两个action,接下来咱们使用这个Task,以下:
//subproject_3/build.gradle
//在定义Task时经过type指定Task的类型
task myTask(type: MyTask){
message = 'custom message'
}
复制代码
在定义Task时经过type指定Task的类型,同时还能够经过闭包配置MyTask中的message参数,在命令行输入gradle myTask
执行这个Task,以下:
# chenjianyu @ FVFCG2HJL414 in ~
$ gradle myTask
> Task :subproject_3:myTask
println2: custom message
println1: custom message
BUILD SUCCESSFUL in 611ms
1 actionable task: 1 executed
复制代码
咱们自定义的Task本质上就是一个类,除了直接在build.gradle文件中编写自定义Task,还能够在Gradle项目的根目录下新建一个buildSrc目录,在buildSrc/src/main/[java/kotlin/groovy]目录中定义编写自定义Task,能够采用java、kotlin、groovy三种语句之一,或者在一个独立的项目中编写自定义Task,在后面自定义Plugin时会讲到这几种方式。
Plugin能够理解为一系列Task的集合,经过实现Plugin接口的apply方法就能够自定义Plugin,自定义的Plugin本质上也是一个类,因此和Task相似,在Gradle中也提供了3种方式来编写自定义Plugin:
因为在上面自定义Task的介绍中已经讲过了如何在build.gradle中直接编写,自定义Plugin也相似,因此下面就主要介绍二、3两种方式,并且这两种方式也是平时开发中自定义Plugin最经常使用的方式。
在GradleDemo中新建一个buildSrc目录,而后在buildSrc目录新建src/main/groovy目录,若是你要使用java或kotlin,则新建src/main/java或src/main/kotlin,src/main/groovy目录下你还能够继续建立package,这里个人package为com.example.plugin,而后在该package下新建一个类MyPlugin.groovy,该类继承自Plugin接口,以下:
class MyPlugin implements Plugin<Project>{
@Override
void apply(Project project){}
}
复制代码
如今MyPlugin中没有任何逻辑,咱们平时是在build.gradle中经过apply plugin: 'Plugin名'来引用一个Plugin,而apply plugin中的apply就是指apply方法中的逻辑,而apply方法的参数project指的就是引用该Plugin的build.gradle对应的Project对象,接下来咱们让咱们在apply方法中编写逻辑,以下:
package com.example.plugin
import org.gradle.api.*
class MyPlugin implements Plugin<Project>{
@Override
void apply(Project project){
//经过project的ExtensionContainer的create方法建立一个名为outerExt的扩展,扩展对应的类为OuterExt
//create方法返回OuterExt实例,咱们能够在apply方法中使用OuterExt实例
def outerExt = project.extensions.create('outerExt', OuterExt.class)
//经过project的task方法建立一个名为showExt的Task
project.task('showExt'){
doLast{
//使用OuterExt实例
println "outerExt = $outerExt"
}
}
}
/** * 自定义插件的扩展对应的类 */
static class OuterExt{
def name
def message
@Override
String toString(){
return "[name = $name, message = $message]"
}
}
}
复制代码
上述我在apply方法中建立了一个扩展和一个Task,其中Task好理解,那么扩展是什么?咱们平时引用android插件时,必定见过这样相似于android这样的命名空间,以下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
//...
}
复制代码
它并非一个名为android的方法,它而是android插件中名为android的扩展,该扩展对应一个bean类,该bean类中有compileSdkVersion、buildToolsVersion等方法,因此配置android就是在配置andorid对应的bean类,如今回到咱们的MyPlugin中,MyPlugin也定义了一个bean类:OuterExt,该bean类有name和messag两个字段,Groovy会自动为咱们生成get/set方法,而apply方法中经过project实例的ExtensionContainer的create方法建立一个名为outerExt的扩展,扩展对应的bean类为OuterExt,扩展的名字能够随便起,其中ExtensionContainer相似于TaskContainer,它也是Project中的一个容器,这个容器存放Project中全部的扩展,经过ExtensionContainer的create方法能够建立一个扩展,create方法返回的是扩展对应的类的实例,这样咱们使用MyPlugin就能够这样使用,以下:
//subproject_4/build.gradle
apply plugin: com.example.plugin.MyPlugin
outerExt {
name 'rain9155'
message 'hello'
}
//执行gradle showExt, 输出:
//outerExt = [name = rain9155, message = hello]
复制代码
扩展的特色就是能够经过闭包来配置扩展对应的类,这样就能够经过扩展outerExt来配置咱们的Plugin,不少自定义Plugin都是都经过添加扩展这种方式来配置自定义的Plugin,不少人就问了,那么相似于android的嵌套DSL如何实现,以下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.example.myapplication"
minSdkVersion 16
targetSdkVersion 29
//...
}
//...
}
复制代码
android{}中有嵌套了一个defaultConfig{},可是defaultConfig并非一个扩展,而是一个名为defaultConfig的方法,参数为Action类型,它是一个接口,里面只有一个execute方法,这里就我参考android 插件的内部实现实现了嵌套DSL,原理就不深刻探究了,嵌套DSL能够简单的理解为扩展对应的类中再定义一个类,MyPlugin的实现以下:
package com.example.plugin
import org.gradle.api.*
import org.gradle.api.model.*//新引入
class MyPlugin implements Plugin<Project>{
@Override
void apply(Project project){
//建立名为outerExt的扩展,扩展对应的类为OuterExt,create方法返回的是OuterExt实例
def outerExt = project.extensions.create('outerExt', OuterExt.class)
project.task('showExt'){
doLast{
//使用OuterExt实例和InnerExt实例
println "outerExt = $outerExt, innerExt = ${outerExt.innerExt}"
}
}
}
static class OuterExt{
def name
def message
//再定义一个类
InnerExt innerExt
//使用@Inject注解带ObjectFactory类型参数的构造
@javax.inject.Inject
public OuterExt(ObjectFactory objectFactory){
//经过objectFactory的newInstance方法建立InnerExt类实例
innerExt = objectFactory.newInstance(InnerExt.class)
}
//定义一个方法,该方法的参数类型为Action,泛型类型为InnerExt
//其中方法名为能够随意起,它会在build.gradle中使用到
void inner(Action<InnerExt> action){
//调用Action的execute方法,传入InnerExt实例
action.execute(innerExt)
}
@Override
String toString(){
return "[name = $name, message = $message]"
}
static class InnerExt{
def name
def message
@Override
String toString(){
return "[name = $name, message = $message]"
}
}
}
}
复制代码
使用MyPlugin就能够这样使用,以下:
//subproject_4/build.gradle
apply plugin: com.example.plugin.MyPlugin
//嵌套DSL
outerExt {
name 'rain'
message 'hello'
//使用inner方法
inner{
name '9155'
message 'word'
}
}
//执行gradle showExt, 输出:
//outerExt = [name = rain, message = hello], innerExt = [name = 9155, message = word]
复制代码
outerExt {}中嵌套了inner{},其中inner是一个方法,参数类型为Action,Gradle内部会把inner方法后面的闭包配置给InnerExt类,总的来讲,定义嵌套DSL的大概步骤以下:
上面4步就是嵌套DSL时须要在自定义Plugin中作的事,Gradle还为咱们提供了更灵活的命名嵌套DSL,经过NamedDomainObjectContainer实现,它相似于android中buildType{}, 以下:
android {
//...
buildTypes {
release {
//..
}
debug {
//...
}
}
}
复制代码
上面buildTypes中定义了2个命名空间,分别为:release、debug,每一个命名空间都会生成一个 BuildType 配置,在不一样的场景下使用,而且我还能够根据使用场景定义更多的命名空间如:test、testDebug等,buildTypes{}中的命名空间数量不定,命名空间的名字不定,这是由于buildTypes内部是经过NamedDomainObjectContainer容器实现的,你们有兴趣能够本身查阅相关实现,这里就不展开了。
还有一点要注意的是,对于扩展对应的bean类,若是你把它定义在自定义的Plugin的类文件中,必定要用static修饰,如这里的OuterExt类、InnerExt类使用了static修饰,或者把它们定义在单独的类文件中。
在独立项目中编写和在buildSrc目录下编写是同样的,只是多了一个发布过程,这里我为了方便就不新建一个独立项目了,而是在GradleDemo中新增一个名为gradle_plugin的子项目,而后在gradle_plugin下新建一个src/main/groovy和src/main/resources目录,接着把刚刚在buildSrc编写的com.example.plugin.MyPlugin复制到src/main/groovy下,最后在GradleDemo新建一个repo目录,看成待会发布插件时的仓库,此时GradleDemo结构以下:
由于gradle_plugin项目中的MyPlugin.groovy使用了Gradle的相关api,如Project等,因此你要在gradle_plugin/build.gradle中引进Gradle api,打开gradle_plugin/build.gradle,添加以下:
//gradle_plugin/build.gradle
//应用groovy插件
apply plugin: 'groovy'
dependencies {
//这样gradle_plugin/src/main/groovy/中就可使用Gradle和Groovy语法
implementation gradleApi()
}
复制代码
如今MyPlugin已经有了,咱们须要给插件起一个名字,在gradle_plugin/src/main/resources目录下新建META-INF/gradle-plugins目录,而后在该目录下新建一个XX.properties文件,XX就是你想要给插件起的名字,就是apply plugin后填写的插件名字,例如andorid 插件名叫com.android.application,因此它的properties文件为com.android.application.properties,这里我给个人MyPlugin插件起名为myplugin,因此我新建myplugin.properties,以下:
打开myplugin.properties文件,添加以下:
#在这里定义自定义插件的实现类,而插件的名字为properties文件的名字,如这里为:myplugin, 则引用插件时为apply plugin:'myplugin'
implementation-class=com.example.plugin.MyPlugin
复制代码
经过implementation-class指明你要发布的插件的实现类,如这里为com.example.plugin.MyPlugin,接下来咱们就来发布插件。
发布插件你能够选择你要发布到的仓库,如maven、lvy,咱们最经常使用的就是maven了,因此这里我选择maven,Gradle提供了maven插件来帮助咱们发布到maven,而maven又有本地仓库和远端仓库这两种类型的仓库,因此maven插件提供了install和uploadArchives这两种任务来帮助咱们发布插件到maven的本地仓库和maven的远端仓库,在你的gradle/build.gradle添加以下:
//gradle_plugin/build.gradle
//应用maven插件
apply plugin: 'maven'
//经过install任务上传自定义插件的jar文件和pom文件到maven本地repo, maven本地repo路径在mac下为~/.m/repository/,在window下为:C:/用户/用户名/.m/repository/
install {
repositories.mavenInstaller {
//经过pom配置自定义插件的group、artifact和version,经过classpath引用自定义插件时为:groupId:artifactId:version
pom{
groupId = 'com.example.customplugin'
artifactId = 'myplugin'
version = '1.0'
}
}
}
//经过uploadArchives任务上传自定义插件的jar文件和pom文件, 能够上传到本地指定目录地址或远端repo地址
uploadArchives{
repositories.mavenDeployer{
pom{
groupId = 'com.example.customplugin'
artifactId = 'myplugin'
version = '2.0'
}
//上传到本地
repository(url: uri('../repo'))
//上传到远端
//repository(url: uri('http://remote/repo')){
// authentication(userName: "name", password: "***")
//}
}
}
复制代码
其中pom{}经过groupId、artifactId和version配置的是插件的classpath路径,即引用插件的路径,咱们会在dependencies{}使用**classpath 'com.example.customplugin:myplugin:1.0'**来引用咱们发布的插件。
接着在Gradle项目所处目录的命令行输入gradle install
来执行install任务,任务执行成功后,在maven本地repo(mac下为~/.m/repository/,在window下为:C:/用户/用户名/.m/repository/)中会看到上传的插件jar和pom文件,以下:
接着在Gradle项目所处目录的命令行输入gradle uploadArchives
来执行uploadArchives任务,这里我指定uploadArchives上传的地址为本地GradleDemo/repo目录处,你也能够替换为你的远端maven地址,uploadArchives任务执行成功后,在GradleDemo/repo/中会看到上传的插件jar和pom文件,以下:
如今MyPlugin插件已经发布成功了,我发布MyPlugin的1.0和2.0两个版本到两个不一样的本地目录中,接下来让咱们使用这个插件。
在subproject_4/build.gradle中添加以下:
//subproject_4/build.gradle
buildscript {
repositories {
//添加maven本地repo
mavenLocal()
//添加maven远端repo
//mavenCentral()
//添加uploadArchives上传时指定的本地路径
maven {
url uri('../repo')
}
}
dependencies {
//定义插件jar的classpath路径,gradle编译时会扫描该classpath下的全部jar文件
//classpath 'com.example.customplugin:myplugin:1.0'
classpath 'com.example.customplugin:myplugin:2.0'
}
}
复制代码
这里使用了Project的buildscript方法,在该方法中可使用repositories方法和dependencies方法指定当前项目构建时依赖的仓库和类路径,项目在构建时会去repositories定义的仓库地址寻找classpath指定路径下的全部jar文件,如我这里repositories方法中指定了mavenLocal()和maven {url uri('../repo')},其中mavenLocal()对应maven的本地仓库即/.m/repository,若是你经过uploadArchives上传到了远端,则能够新增mavenCentral(),它表明maven的远端地址,而dependencies方法中则经过classpath指定了插件在仓库下的类路径,这里我指定了MyPlugin 2.0的类路径: com.example.customplugin:myplugin:2.0,它在maven {url uri('../repo')}仓库下。
如今咱们能够经过apply plugin使用MyPlugin了,在subproject_4/build.gradle中添加以下:
//引用插件
apply plugin: 'myplugin'
//使用DSL配置插件的属性
outerExt{
name 'rain'
message 'hello'
inner{
name '9155'
message 'word'
}
}
//执行gradle showExt,输出:
//outerExt = [name = rain, message = hello], innerExt = [name = 9155, message = word]
复制代码
其中apply plugin后面的插件名就是咱们在gradle_plugin/src/main/resources/META-INF/gradle-plugins目录下编写的properties文件的名称。
在最新版的Gradle中,本文所使用的maven插件已经被废弃了,Gradle提供了maven-publish插件来替代,可是它的总体发布过程是相似。
本文经过Gradle的特色、项目结构、生命周期、Task、自定义Plugin等来全面的学习Gradle,掌握上面的知识已经足够让你入门Gradle,可是若是你想要更进一步的学习Gradle,掌握本文的知识点是彻底不足的,你可能还须要熟悉掌握Project中各个api的使用、能独立的自定义一个有完整功能的Plugin、能熟练地编写Gradle脚原本管理项目等,下面有一份Gradle DSL Reference,你能够在须要来查找Gradle相关配置项的含义和用法:
对于android开发者,咱们引入的android插件中也有不少配置项,下面的Android Plugin DSL Reference,你能够在须要来查找android 插件相关配置项的含义和用法:
本文的源码位置:
以上就是本文的所有内容,但愿你们有所收获!
参考内容: