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 插件相關配置項的含義和用法:
本文的源碼位置:
以上就是本文的所有內容,但願你們有所收穫!
參考內容: