歡迎你們加入QQ羣一塊兒討論: 489873144(android格調小窩) 個人github地址:https://github.com/jeasonlzy javascript
0x01 Groovy 概述
Groovy 是一個基於 JVM 的語言,代碼最終編譯成字節碼(bytecode),並在 JVM 上運行。它具備相似於 Java 的語法風格,可是語法又比 Java 要靈活和方便,同時具備動態語言(如 ruby 和 Python)的一些特性。html
正由於如此,因此Groovy適合用來定義DSL(Domain Specific Language)。java
簡單的來說 DSL 是一個面向特定小領域的語言,如常見的 HTML、CSS 都是 DSL,它一般是以配置的方式進行編程,與之相對的是通用語言(General Purpose Language),如 Java 等。python
0x02 groovy 基本知識
1)首先須要安裝groovy環境,具體的環境安裝就不說了,網上不少,安裝完成後配置環境變量,出現如下結果,即安裝成功
2)groovy與java
由於Groovy是基於JVM的語言,因此咱們來看看最後生成的字節碼文件。咱們寫一個類: hello.groovyandroid
name =
"lzy"
def
say (){
"my name is $name "
}
println
say ()
在命令行輸入groovy hello.groovy
,運行腳本,輸出如下結果: 上面的操做作完後,有什麼感受和體會? 最大的感受可能就是groovy和shell腳本,或者python好相似。 另外,除了能夠直接使用JDK以外,Groovy還有本身的一套GDK。 c++
咱們看一下編譯成jvm字節碼後的結果git
咱們輸入groovyc -d classes hello.groovy
命令將當前文件生成字節碼文件,-d參數表示在classes文件夾下,最終結果以下: hello.classgithub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.BytecodeInterface8;
import org.codehaus.groovy.runtime.GStringImpl;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
public class hello extends Script {
public hello () {
CallSite[] var1 = $getCallSiteArray();
}
public hello (Binding context) {
CallSite[] var2 = $getCallSiteArray();
super (context);
}
public static void main (String... args) {
CallSite[] var1 = $getCallSiteArray();
var1[
0 ].call(InvokerHelper.class, hello.class, args);
}
public Object
run () {
CallSite[] var1 = $getCallSiteArray();
String var2 =
"lzy" ;
ScriptBytecodeAdapter.setGroovyObjectProperty(var2, hello.class,
this , (String)
"name" );
return !__$stMC && !BytecodeInterface8.disabledStandardMetaClass()?var1[
3 ].callCurrent(
this ,
this .say()):var1[
1 ].callCurrent(
this , var1[
2 ].callCurrent(
this ));
}
public Object
say () {
CallSite[] var1 = $getCallSiteArray();
return new GStringImpl(
new Object[]{var1[
4 ].callGroovyObjectGetProperty(
this )},
new String[]{
"my name is " ,
"" });
}
}
到這裏咱們能夠發現,其實groovy腳本本質就是java,他與java幾乎沒有區別,只是在java語言在語法上的擴展,支持DSL,加強了可讀性。而且咱們得出如下結論: 1. hello.groovy被轉換成了一個hello類,它從Script派生。 2. 每個腳本都會生成一個static main函數。這樣,當咱們groovy hello.groovy去執行的時候,其實就是用java去執行了這個main函數 3. 腳本中的全部代碼都會放到run函數中。好比,say()方法的調用,這句代碼其實是包含在run()方法裏的。 4. 若是腳本中定義了方法,則方法會被定義在hello類中。 5. 腳本中定義的變量是有它的做用域的,name = 「lzy」
,這句話是在run()中建立的。因此,name看起來好像是在整個腳本中定義的,但實際 上say()方法沒法直接訪問它。web
接着咱們把上述的hello.groovy
文件修改,在定義name
前的加上def
修飾符,其他不作任何修改,咱們再次運行代碼,發現如下錯誤: shell
咱們將修改後的代碼編譯成class文件後,與以前的正常結果作對比,發現如下不一樣: 左邊是正確的,右邊是錯誤的,相比下來就是多調用了一個方法,這個方法看起來就是將你定義的屬性保存到了某個全局的環境中,確保下面的say()
方法在調用的時候,能從全局取到這個屬性。
ScriptBytecodeAdapter.setGroovyObjectProperty(var2, hello.class,
this , (String)
"name" );
可是這樣仍是與咱們的想象有差距,name並無在成員位置,那如何才能才能讓咱們定義的屬性就生成在成員變量的位置呢?這時候須要@Field
註解,以下:
import groovy.transform.Field
@Field name =
"lzy"
def say(){
"my name is $name"
}
println say()
加上這行註解後,生成的字節碼以下: name
屬性確實變成了成員變量,而且是在構造方法中被初始化了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.BytecodeInterface8;
import org.codehaus.groovy.runtime.GStringImpl;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;
public class hello extends Script {
Object name;
public hello () {
CallSite[] var1 = $getCallSiteArray();
String var2 =
"lzy" ;
this .name = var2;
}
public hello (Binding context) {
CallSite[] var2 = $getCallSiteArray();
super (context);
String var3 =
"lzy" ;
this .name = var3;
}
public static void main (String... args) {
CallSite[] var1 = $getCallSiteArray();
var1[
0 ].call(InvokerHelper.class, hello.class, args);
}
public Object
run () {
CallSite[] var1 = $getCallSiteArray();
Object var10000 =
null ;
return !__$stMC && !BytecodeInterface8.disabledStandardMetaClass()?var1[
3 ].callCurrent(
this ,
this .say()):var1[
1 ].callCurrent(
this , var1[
2 ].callCurrent(
this ));
}
public Object
say () {
CallSite[] var1 = $getCallSiteArray();
return new GStringImpl(
new Object[]{
this .name},
new String[]{
"my name is " ,
"" });
}
}
0x03 Groovy 語法
這裏只講一些比較重要的特性,其他比較基本的語法比較簡單,能夠參考這裏過一遍: 工匠若水的博客:Groovy腳本基礎全攻略
1)方法的輸入參數優化
groovy中定義的函數,若是至少有一個參數,在調用的時候能夠省略括號。若是某個函數沒有參數,那就不能省略括號,不然會當成一個變量使用。
def func(String a){
println(a)
}
func
'hello'
在android項目中,好比build.gradle
android {
compileSdkVersion
25
buildToolsVersion
"25.0.0"
}
好比這裏compileSdkVersion 和 buildToolsVersion 其實就是調用了一樣名字的兩個函數,在AndroidStudio裏面能夠點進去查看函數實現
2)閉包
閉包的概念也許咱們稍微陌生一點,可是實際上,咱們能夠簡單把它當作一個匿名類,只是編譯器提供了更加簡單的語法來實現它的功能。 閉包(Closure)是groovy中一個很重要的概念,並且在gradle中普遍使用。簡而言之,閉包就是一個可執行的代碼塊,相似於C語言中的函數指針。在不少動態類型語言中都有普遍的使用,java8 中也有相似的概念:lambda expression,可是groovy中的閉包和java8中的lambda表達式相比又有不少的不一樣之處。
咱們能夠把閉包當作一個匿名內部類,只是編譯器提供了更加簡單的語法來實現它的功能。在Groovy中閉包也是對象,能夠像方法同樣傳遞參數,而且能夠在須要的地方執行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def clos = {
params ->
println
"Hello ${params}"
}
clos(
"World" )
Closure clos1 = {
a , def b, int c =
2 ->
a + b + c
}
println clos1(
5 ,
3 )
Closure<String> clos2 = {
println
it
return "clos2"
}
閉包有三個很重要的屬性分別是:this
,owner
,delegate
,分別表明如下概念:
this: 對應於定義閉包時包含他的class,能夠經過getThisObject或者直接this獲取
owner: 對應於定義閉包時包含他的對象,能夠經過getOwner或者直接owner獲取
delegate: 閉包對象能夠指定一個第三方對象做爲其代理,用於函數調用或者屬性的指定,能夠經過getDelgate或者delegate屬性獲取
咱們編寫以下代碼:test1.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class A {
def closure1 = {
println
"--------------closure1--------------"
println
"this:" +
this .
class .name
println
"owner:" + owner.
class .name
println
"delegate:" + delegate.
class .name
def closure2 = {
println
"-------------closure2---------------"
println
"this:" +
this .
class .name
println
"owner:" + owner.
class .name
println
"delegate:" + delegate.
class .name
def closure3 = {
println
"-------------closure3---------------"
println
"this:" +
this .
class .name
println
"owner:" + owner.
class .name
println
"delegate:" + delegate.
class .name
}
closure3()
}
closure2()
}
}
def a =
new A()
def closure1 = a.closure1
closure1()
運行後獲得以下結果:
1
2
3
4
5
6
7
8
9
10
11
12
--------------closure1--------------
this: com .lzy .A
owner: com .lzy .A
delegate: com .lzy .A
-------------closure2---------------
this: com .lzy .A
owner: com .lzy .A $_closure1
delegate: com .lzy .A $_closure1
-------------closure3---------------
this: com .lzy .A
owner: com .lzy .A $_closure1$_closure2
delegate: com .lzy .A $_closure1$_closure2
3)代理策略
若是在閉包內,沒有明確指定屬性或者方法的調用是發生在this, owner,delegate上時,就須要根據代理策略來判斷到底該發生在誰身上。有以下幾種代理策略:
Closure.OWNER_FIRST 默認的策略,若是屬性或者方法在owner中存在,調用就發生在owner身上,不然發生在delegate上
Closure.DELEGATE_FIRST 跟owner_first正好相反
Closure.OWNER_ONLY 忽略delegate
Closure.DELEGATE_ONLY 忽略owner
Closure.TO_SELF 調用不發生在owner或delegate上,只發生在閉包內
咱們編寫以下代碼:test2.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import groovy.transform.Field
@Field String name =
"abc"
class P {
String name
def pretty = {
"my name is $name"
}
}
class T {
String name
}
def upper = {
name.toUpperCase()
}
println upper()
def p =
new P(name:
'ppp' )
def t =
new T(name:
'ttt' )
upper.delegate = t
upper.resolveStrategy = Closure.DELEGATE_FIRST
println upper()
p.pretty.delegate =
this
println p.pretty()
p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
println p.pretty()
運行後獲得以下結果:
ABC
TTT
my name is ppp
my name is abc
這裏重點強調一下,成員變量name必定要加上@Field註解,否者出現的結果必定不是上述結果,緣由以前已經分析過,不加這個註解,name屬性將不會是成員變量
4)類的Property
Groovy中的class和java中的Class區別不大,值得咱們關注的區別是,若是類的成員變量沒有加任何權限訪問,則稱爲Property, 不然是Field,filed和Java中的成員變量相同,可是Property的話,它是一個private field和getter setter的集合,也就是說groovy會自動生成getter setter方法,所以在類外面的代碼,都是會透明的調用getter和setter方法
咱們在上述的test1.groovy
的類A
中加入如下幾行代碼:
String name =
"aaa"
public String name1 =
"bbb"
private String name2 =
"ccc"
而後對這個test1.groovy
進行編譯groovyc -d classes test1.groovy
,生成的文件結構以下: 點開A.class
發現如下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class A implements GroovyObject {
private String name;
public String name1;
private String name2;
private Object closure1;
···
public String
getName () {
return this .name;
}
public void setName (String var1) {
this .name = var1;
}
···
}
5)操做符重載
咱們常常在gradle中看見如下代碼:
task hello << {
println
"hello world"
}
實際上他的原始調用含義是:
task ("hello" ) .leftShift (closure)
由於task重載了leftShift,因此可使用 << 操做符,這和c++的特性是同樣的
6)Command Chains
這個特性不只能夠省略函數調用中的括號,並且能夠省略,連續函數調用中的. 點號, 好比 a(b).c(d) 這裏a c是函數, b d是函數參數, 就能夠縮寫爲a b c d。這個特性強大之處在於不只適用於單個參數類型函數,並且適用於多個參數類型的函數,當參數類型爲閉包時一樣適用。
task(
"task1" ).doLast({
println "111"
}).doLast({
println (
"222" )
})
task task1 doLast {
println "111" } doLast {
println (
"222" ) }
7)DSL
藉助閉包的特性,咱們能夠嘗試寫一個簡單的DSL。下面的代碼展現瞭如何藉助groovy的語法特性來實現一個DSL,這些特性咱們稍後會在gradle的腳本中看到。
test3.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Book {
def _name =
''
def _price =
0.0
def shop = []
def static config(config){
Book book =
new Book(shop:[
'A' ,
'B' ])
config.delegate = book
config()
}
def name(name){
this ._name = name
}
def price(price){
this ._price = price
}
def getDetail(){
println
"name : ${_name}"
println
"price : ${_price}"
println
"shop : ${shop}"
}
}
Book.config {
name
'test'
price
1.2
detail
}
上面所提到的這些groovy的語法特性,構成了Gradle中DSL的基礎
0x04 Gradle 基本概念
咱們在AndroidStudio中建立基於Gradle的project時,會默認生成一個多項目結構的Gradle工程,他有以下結構:
├── app
│ └── build
.gradle
├── lib
│ └── build
.gradle
├── build
.gradle
└── settings
.gradle
若是是單工程結構,這個Setting.gradle其實能夠省略
Gradle中,每個待編譯的工程,或者叫每個Library和App都是單獨的Project。根據Gradle的要求,每個Project在其根目錄下都須要有一個build.gradle。build.gradle文件就是該Project的編譯腳本,相似於Makefile。每個Project在構建的時候都包含一系列的Task。好比一個Android APK的編譯可能包含:Java源碼編譯Task、資源編譯Task、JNI編譯Task、lint檢查Task、打包生成APK的Task、簽名Task等。具體一個Project到底包含多少個Task,實際上是由編譯腳本指定的插件決定。插件是什麼呢?插件就是用來定義Task,並具體執行這些Task的東西。
apply plugin:
'com.android.application' //app插件
apply plugin:
'com.android.library' //lib插件
android{
...
}
dependencies{
....
}
若是咱們把以上代碼在build.gradle中刪掉,執行gradle tasks
命令列出全部可執行的task,會發現不少task都不見了,由此咱們能得出結論,
這些task都是android application插件生成的。咱們能使用Gradle構建Android 工程,一切都基於這個插件。這個插件從android這個擴展中讀取了咱們的配置,生成了一些列構建android 所須要的任務。
咱們執行gradle projects
列出全部的工程:
這個圖片的最後幾句話也告訴了咱們,若是你在根目錄下,想查看或者運行某個項目下的任務,能夠用gradle :app:tasks
這種語法,若是你cd到子項目的根目錄下,是不須要加:app
這樣的前綴的。
對Android來講,gradle assemble
這個Task會生成最終的產物Apk,因此若是一個工程包含5個Model,那麼須要分別編譯這5個,他們可能還有一些依賴關係,這樣就很麻煩了,而在Gradle中,是支持多工程編譯的(Multi-Projects Build),咱們在根目錄下直接執行gradle assemble
,就能按照依賴關係把這5個Model所有編譯出來生成最終的Apk,可是爲何能夠呢?
咱們須要在根目錄下也添加一個build.gradle。這個build.gradle通常幹得活是:配置其餘子Project的。好比爲子Project添加一些屬性。這個build.gradle有沒有都無所謂。
繼續在根目錄下添加一個名爲settings.gradle。這個文件很重要,名字必須是settings.gradle。它裏邊用來告訴Gradle,這個multi-projects包含多少個子Project,內部通常就是一個include指令。根據groovy的語法,他就是在gradle生成的settings對象調用函數 include(‘app’),include接受的參數是一個string數組,所以include後能夠加不少參數,這個函數的意義就是:指明那些子project參與此次gradle構建
因此對於一個工程,咱們能對構建過程作出改變的,就只能發生在這些.gradle文件中,這些文件稱爲Build Script構建腳本。對於Gradle中的構建腳本,一方面能夠理解爲配置文件,每一種類型腳本文件都是對某一種類型的構建對象進行配置。另外一方面也能夠把每一個腳本理解爲一個Groovy閉包,這樣咱們在執行構建腳本時,就是在執行每個閉包函數,只不過每一個閉包所設置的delegate不同。
如下來自於文檔:Gradle Build Language Reference ,這個文檔很重要,後面會常用!!!
Project對象:每一個build.gradle會轉換成一個Project對象,或者說代理對象就是Project。
Gradle對象:當咱們執行gradle xxx或者什麼的時候,gradle會從默認的配置腳本中構造出一個Gradle對象。在整個執行過程當中,只有這麼一個對象。Gradle對象的數據類型就是Gradle。咱們通常不多去定製這個默認的配置腳本。
Settings對象:每一個settings.gradle會轉換成一個Settings對象,或者說代理對象是Setting。
補充一點:Init Script 其實就是配置gradle運行環境。彷佛曆來沒有使用過,可是在每一次構建開始以前,都會執行init script,咱們能夠對當前的build作出一些全局配置,好比全局依賴,何處尋找插件等。有多個位置能夠存放init script以下: 1. 經過在命令行裏指定gradle參數 -I 或者–init-script
1)Build生命週期
Gradle的構建腳本生命週期具有三大步,以下:
上圖告訴咱們如下信息,
Gradle工做包含三個階段:
首先是初始化階段。對咱們前面的multi-project build而言,就是執行settings.gradle
Initiliazation phase的下一個階段是Configration階段。
Configration階段的目標是解析每一個project中的build.gradle。好比multi-project build例子中, 解析每一個子目錄中的build.gradle。在這兩個階段之間,咱們能夠加一些定製化的Hook。這固然是經過 API來添加的,須要特別注意:每一個Project都會被解析。
Configuration階段完了後,整個build的project以及內部的Task關係就肯定了。一個 Project包含不少Task,每一個Task之間有依賴關係。Configuration會創建一個有向圖來描述Task之間的 依賴關係,是一個有向圖,因此,咱們能夠添加一個HOOK,即當Task關係圖創建好後,執行一些操做.
最後一個階段就是執行任務了。你在gradle xxx中指定什麼任務,gradle就會將這個xxx任務鏈上的全部任務所有按依賴順序執行一遍!固然,任務執行完後,咱們還能夠加Hook。
gradle具備如下幾個經常使用的命令: gradle tasks //列出全部的任務 gradle projects //列出全部的項目 gradle properties //列出全部的屬性
2)Setting對象
先看文檔,方法文檔都在文檔中:Settings 其中有這麼句話比較重要:
In addition to the properties of this interface, the Settings object makes some additional read-only properties available to the settings script. This includes properties from the following sources:
Defined in the gradle.properties file located in the settings directory of the build.
Defined the gradle.properties file located in the user’s .gradle directory.
Provided on the command-line using the -P option.
翻譯後就是,除了Setting這個接口本身提供的屬性方法外,你還能夠在如下位置添加本身的額外屬性: - setting.gradle 平級目錄下的 gradle.properties 文件 - 用戶.gradle目錄下的 gradle.properties 文件 - 使用命令行 -P 屬性
其他的文檔中比較詳細。
3)Project對象
如下內容均來自與文檔:Project
每個build.gradle文件和一個Project對象一一對應,在執行構建的時候,gradle經過如下方式爲每個工程建立一個Project對象:
建立一個Settings對象,
根據settings.gradle文件配置它
根據Settings對象中定義的工程的父子關係建立Project對象
執行每個工程的build.gradle文件配置上一步中建立的Project對像
其中有不少有用的方法:
void apply(Map<
String , ?> options);
void dependencies(Closure configureClosure);
void buildscript(Closure configureClosure);
在build.gradle文件中定義的屬性和方法會委託給Project對象執行,每個project對象在尋找一個屬性的時候有5個做用域做爲範圍,分別是:
屬性可見範圍
1.Project 自己
2.Project的ext 屬性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
project
.ext .prop 1 =
"prop1"
ext
.prop 2 =
"prop2"
project
.ext {
prop3 =
"prop3"
prop4 =
"prop4"
}
ext {
prop5 =
"prop5"
prop6 =
"prop6"
}
println project
.ext .prop 1
println project
.ext .prop 2
println project
.prop 3
println project
.prop 4
println prop5
println prop6
3.經過plugin 添加的extension ,就是插件定義本身的特有擴展屬性,每個extension經過一個和extension同名的只讀屬性訪問
4.經過plugin 添加的屬性。一個plugin 能夠經過Project的Convention對象爲project添加屬性和方法。
5.project中的task,一個task對象能夠經過project中的同名屬性訪問,可是它是隻讀的
6.當前project的父工程的extra 屬性和convention 屬性,是隻讀的
當獲取或者設置這些屬性的時候,按照上述的順序依次尋找,若是都沒找到,則拋出異常。
方法可見範圍
Project對象自己
build.gradle文件中定義的方法
經過plugin 添加的extension,每一個extensions 均可以做爲一個方法訪問,它接受一個閉包或Action做爲參數
經過plugin 添加的方法。一個plugin 能夠經過Project的Convention對象爲project添加屬性和方法。
project中的task ,每個task 都會在當前project中存在一個接受一個閉包或者Action做爲參數的方法,這個閉包會在task的configure(closure)方法中調用。
當前工程的父工程中的方法
當前工程的屬性可見範圍中全部的閉包屬性均可以做爲方法訪問
4) Task對象
先來文檔 Task
A Task is made up of a sequence of Action objects. When the task is executed, each of the actions is executed in turn, by calling Action.execute(T). You can add actions to a task by calling Task.doFirst(org.gradle.api.Action) or Task.doLast(org.gradle.api.Action).
Groovy closures can also be used to provide a task action. When the action is executed, the closure is called with the task as parameter. You can add action closures to a task by calling Task.doFirst(groovy.lang.Closure) or Task.doLast(groovy.lang.Closure).
There are 2 special exceptions which a task action can throw to abort execution and continue without failing the build. A task action can abort execution of the action and continue to the next action of the task by throwing a StopActionException. A task action can abort execution of the task and continue to the next task by throwing a StopExecutionException. Using these exceptions allows you to have precondition actions which skip execution of the task, or part of the task, if not true.
建立一個簡單的task的語法爲:
task <taskName> << {
}
這句話應該這麼理解:
首先調用project的task方法,傳入一個taskName,返回一個task
調用task的leftShift 方法 傳入一個closure,根據leftShift的解釋,咱們知道這個閉包將添加到task的action list裏去,在任務執行的時候運行
有時候,咱們可能會錯寫成
task <taskName> {
}
少了這個<< 操做符,意思就徹底不同了,這個時候調用的函數爲
Task
task (String name, Closure configureClosure);
這時,第二個參數closure用來配置task,在task建立的時候,也就是構建整個任務有向圖的時候執行,而不是在task執行的時候運行。不過咱們能夠在這個閉包內配置task的一些屬性。
task copyDocs(type: Copy) {
from 'src/main/doc'
into 'build/target/doc'
}
固然咱們還能夠這樣指定task的行爲:
1
2
3
4
5
6
7
8
9
10
11
12
13
task exampleTask {
doLast{
}
}
task exampleTask doLast{
}
task exampleTask << {
}
還能夠指定task的類型
task name(
type : Type ){ doLast { }
}
gradle內置爲咱們生成了不少task類型,好比Copy,Delete,能夠點擊連接 查看gradle內置的task類型列表,若是建立task時沒有指定type,則他默認是DefaultTask類型。咱們還能夠建立本身的task類型,咱們在稍後就會講到。
咱們還能夠能夠指定task之間的依賴關係, 經過dependsOn, mustRunAfter, shouldRunAfter來指定。 還能夠指定task的分組group, 若是不指定,將會出如今other裏面。
5) 構建的生命週期測試
如下各個方法參考文檔: Gradle相關 Task相關
├── app
│ └── build
.gradle
├── lib
│ └── build
.gradle
├── build
.gradle
└── settings
.gradle
root/settings.gradle
println
'------setting.gradle execute------'
include ':app' ,
':lib' ,
':helloPlugin' ,
':simplePlugin'
root/build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
println '------root build.gradle execute------'
apply from: uri(
'./build_1.gradle' )
apply from: uri(
'./build_2.gradle' )
println "all project size : ${allprojects.size()}"
gradle.settingsEvaluated { settings ->
println "settingsEvaluated"
}
gradle.projectsLoaded { gradle ->
println "projectsLoaded"
}
gradle.beforeProject { project ->
println "beforeProject: ${project.name} "
}
gradle.afterProject { project ->
println "afterProject: ${project.name}"
}
gradle.projectsEvaluated { gradle ->
println "projectsEvaluated"
}
gradle.buildFinished { buildResult ->
println "buildFinished"
}
gradle.taskGraph.whenReady { graph ->
println "============task graph is ready============"
graph.getAllTasks().each {
println "task ${it.name} will execute"
}
println "============task graph is over============="
}
gradle.taskGraph.beforeTask { task ->
println "before ${task.name} execute"
}
gradle.taskGraph.afterTask { task ->
println "after ${task.name} execute"
}
tasks.whenTaskAdded { task ->
println "taskAdded:" + task.name
}
task subTask1 {
group
"hello"
doLast {
println "${name} execute"
}
}
task subTask2(dependsOn:
'subTask1' ) {
group
"hello"
doLast {
println "${name} execute"
}
}
app/build.gradle
- - - - - - . - - - - - -
- >
. - - - -
- >
. - - - -
lib/build.gradle
- - - - - - . - - - - - -
在根目錄下執行 gradle libTask2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
------setting
.gradle execute------
------root build
.gradle execute------
all project size :
5
taskAdded: subTask1
taskAdded: subTask2
afterProject: GradlePlugin
afterEvaluate: GradlePlugin
beforeProject: app
beforeEvaluate: app
------app build
.gradle execute------
afterProject: app
afterEvaluate: app
Incremental java compilation is an incubating feature.
beforeEvaluate: app --
in --
beforeProject: lib
beforeEvaluate: lib
------lib build
.gradle execute------
afterProject: lib
afterEvaluate: lib
projectsEvaluated
============task graph is ready============
task subTask1 will execute
task subTask2 will execute
============task graph is over=============
:subTask1
before subTask1 execute
subTask1 execute
after subTask1 execute
:subTask2
before subTask2 execute
subTask2 execute
after subTask2 execute
從上述例子中咱們驗證瞭如下結果:
若是一個task在build.gradle中定義,可是在構建中不會執行,那麼它的Task對象會建立,可是不會在任務圖中出現。
咱們能夠經過Gradle或者Project對象中定義的方法獲取生命週期中每個過程在執行中的回調。這裏注意一下,咱們定義的一些回調在實際執行中彷佛並無被觸發,例如,settingsEvaluated
,projectsLoaded
。具體緣由須要細看。
0x05 自定義一個插件
首先看一下工程結構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
├── app //root工程
├── repo //本地maven目錄
├── helloPlugin //plugin工程
│ ├── build
.gradle
│ └── src
│ └── main
│ ├── groovy
│ │ └──
com .lzy .plugin
│ │ ├── HelloPlugin
.groovy
│ │ ├── Person
.groovy
│ │ └── PersonExt
.groovy
│ └── resources
│ └── META-INF
│ └── gradle-plugins
│ └── helloPlugin
.properties //插件名
│
├── build
.gradle
└── settings
.gradle
首先,插件工程能夠用任意的jvm語言編寫,例如,scala,groovy,java等,最終每個插件都會打包成一個jar包,其中META-INF文件下中每個.properties文件表明一個Plugin,最後使用的時候以下:
apply plugin:
'helloPlugin'
這個文件裏面內容指明瞭插件類的全類名,以下:
implementation-class=
com .lzy .plugin .HelloPlugin
HelloPlugin.groovy:很簡單,就定義一個任務,打印一個字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.lzy.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
class HelloPlugin implements Plugin <Project > {
public void apply(Project project) {
project.task(
"sayHello" ) {
group "hello"
doLast {
println
"Hello Plugin"
}
}
}
}
首先咱們必須說明的是,插件能夠以三種形式存在: 1. 在咱們構建項目的build.gradle腳本中直接編寫 2. 在咱們構建項目的rootProjectDir/buildSrc/src/main/groovy 目錄下 3. 以單獨的project存在
這裏採用第三種方式:在插件目錄下編寫
這種編寫方式只能發佈到本地 build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apply plugin:
'groovy'
apply plugin:
'maven'
version =
'0.1.1'
group =
'com.lzy.plugin'
repositories {
mavenCentral()
}
dependencies {
compile gradleApi()
compile localGroovy()
}
uploadArchives {
repositories.mavenDeployer {
repository(url:
'file:../repo' )
}
}
有時咱們更想開源出去給其餘人用,像Small這樣,咱們就能夠這麼寫 build.gradle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
apply plugin:
'maven-publish'
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
artifact sourcesJar
artifact javadocJar
groupId
'com.lzy.plugin'
artifactId
'helloPlugin'
version '0.1.1'
}
}
repositories {
maven {
url
"../repo"
}
}
}
task javadocJar(type: Jar, dependsOn: groovydoc) {
classifier =
'javadoc'
from "${buildDir}/javadoc"
}
task sourcesJar(type: Jar) {
from sourceSets.main.allSource
classifier =
'sources'
}
groupId、artifactId、version,這三者組成了插件使用者在聲明依賴時的完整語句 groupId:artifactId:version
對於第一種方式:在helloPlugin的根目錄下執行,gralde uploadArchives
,編譯插件工程,併發布到../repo
目錄。
對於第二種方式:有兩種publish任務,publish 和 publishToMavenLocal, - publish:任務依賴於全部的mavenPublication的generatePomFileFor任務和publishxxxPublicationToMavenRepository,意思是將全部的mavenPublication發佈到指定的repository, - publishToMavenLocal依賴於全部的mavenPublication的generatePomFileFor和publishxxxTomavenLocal任務,意思是將全部的mavenPublication發佈到本地的m2 repository。
以上,咱們就建立好了一個gradle plugin,那麼如何使用它呢?
首先,在root工程下的build.gradle
中,咱們經過buildscript
引入插件
buildscript
{ repositories{ mavenCentral() maven { url uri('./repo' ) }
}
dependencies
{ classpath 'com.lzy.plugin:helloPlugin:0.1.1' }
}
而後,在app工程下,咱們應用這個插件,
apply plugin:
'helloPlugin'
最後,在app或者根目錄下執行,gradle sayHello
,這樣就打印出了咱們插件中定義的文字。
以上就是一個自定義插件的建立和應用過程,雖然很簡單,可是能夠幫助咱們理解gradle是如何經過plugin完成不少複雜的工做的。
Extension
1)狀況一:
有時候咱們但願在使用插件的時候,額外配置一些參數,這時候就須要額外寫一個Ext類,以下: PersonExt.groovy
public class PersonExt {
String name
int age
boolean boy
@Override
public String
toString () {
return "I am $name, $age years old, " + (boy ?
"I am a boy" :
"I am a girl" )
}
}
接着修改 HelloPlugin.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class HelloPlugin implements Plugin <Project > {
public void apply(Project project) {
project.extensions.add(
"person" , PersonExt)
project.task(
"sayHello" ) {
group "hello"
doLast {
def personExt = project.person
def personExt1 = project.extensions.getByName(
'person' )
println personExt
println personExt1
}
}
}
}
最後咱們再使用插件的地方添加如下屬性:
person {
name =
"abc"
age =
18
boy =
true
}
這裏使用=號進行復制,實際上可加可不加,本質是利用了grooy特性,調用了setName方法,而且省略了方法調用的括號。
執行 gradle sayHello
獲得以下結果:
:sayHello
I am abc,
18 years old,
I am a boy
I am abc,
18 years old,
I am a boy
到這裏說明咱們定義的Extension正確設置並讀取成功了。
2)狀況二
若是咱們但願設置的Extension是一個集合列表,而且該列表長度未知,又該怎麼寫呢?
咱們須要使用NamedDomainObjectContainer,咱們後面都簡稱NDOC 這是一個容納object的容器,它的特色是它的內部使用SortedSet實現的,內部對象的name是unique的,並且是按name進行排序的。一般建立NDOC的方法就是調用Project裏的方法: 這裏type有一個要求:必須有一個public的構造函數,接受string做爲一個參數,必須有一個叫作name 的property。
新增一個類: HobbyExt.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HobbyExt {
String name
int level
String school
HobbyExt(name) {
this .name = name;
}
@Override
public String
toString () {
return "My hobby is $name, level $level, School $school"
}
}
修改HelloPlugin.groovy代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class HelloPlugin implements Plugin <Project > {
public void apply(Project project) {
NamedDomainObjectContainer<HobbyExt> hobbies = project.
container (HobbyExt.class)
project.extensions.add(
'hobbies' , hobbies)
project.task(
"sayHello" ) {
group "hello"
doLast {
def hobbiesExt = project.hobbies
def hobbiesExt1 = project.extensions.getByName(
'hobbies' )
println hobbiesExt
println hobbiesExt1
}
}
}
}
在使用插件的地方添加如下代碼:
hobbies {
basketball {
level =
4
school =
"beijing"
}
football {
level =
6
school =
"qinghua"
}
}
執行 gradle sayHello
獲得以下結果:
:sayHello
[My hobby
is basketball, level
4 , School beijing, My hobby
is football, level
6 , School qinghua]
[My hobby
is basketball, level
4 , School beijing, My hobby
is football, level
6 , School qinghua]
到這裏說明咱們定義的Extension正確設置並讀取成功了。
3)狀況三
咱們也能夠混合列表和單個屬性,就像android{…}同樣 新建一個類 Team.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TeamExt {
NamedDomainObjectContainer<HobbyExt> hobbies
String name
int count
public TeamExt (NamedDomainObjectContainer<HobbyExt> hobbies) {
this .hobbies = hobbies
}
def hobbies(Closure closure) {
hobbies.configure(closure)
}
String toString() {
"this is a team, name: $name, count $count, hobbies: $hobbies"
}
}
這裏的hobbies(closure)函數是必須的,只有實現了這個函數,Gradle在解析team的extension,遇到hobbies配置時,才能經過調用函數,調用 NamedDomainObjectContainer的configure方法,往裏面添加對象。 接着咱們修改 HelloPlugin.groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class HelloPlugin implements Plugin <Project > {
public void apply(Project project) {
NamedDomainObjectContainer<HobbyExt> hobbyExt = project.
container (HobbyExt)
def team =
new TeamExt(hobbyExt)
project.extensions.add(
"team" , team)
project.task(
"sayHello" ) {
group "hello"
doLast {
def teamExt = project.team
def teamExt1 = project.extensions.getByName(
'team' )
println teamExt
println teamExt1
}
}
}
}
在使用插件的地方添加如下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
team {
name =
"android"
count =
10
hobbies {
basketball {
level =
4
school =
"beijing"
}
football {
level =
6
school =
"qinghua"
}
}
}
執行 gradle sayHello
獲得以下結果:
:sayHello
this
is a team,
name : android,
count 10 , hobbies: [My hobby
is basketball, level
4 , School beijing, My hobby
is football, level
6 , School qinghua]
this
is a team,
name : android,
count 10 , hobbies: [My hobby
is basketball, level
4 , School beijing, My hobby
is football, level
6 , School qinghua]
到這裏說明咱們定義的Extension正確設置並讀取成功了。
以上的寫法是否是特別像build.gradle文件中的android標籤,他的內部能夠配置不少屬性,原理都和這個同樣。
到這裏,咱們就經過一個簡單的例子就熟悉了Gradle插件的編寫規則,並且經過對groovy語法的瞭解,讓咱們對gradle的DSL再也不陌生。
0x06 Groovy和Gradle的調試
主要參考 Small 中 Debug gradle on android studio
核心就是下面兩個命令:
export GRADLE_OPTS=
"-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005"
./gradlew someTask -Dorg.gradle.daemon=
false
其中有如下幾個注意事項: - 對於項目根目錄下的gradle.properties
文件須要作修改,必定要將org.gradle.jvmargs
這個參數給註釋掉,緣由是上述在配置remote調試參數的時候已經配置了jvmargs,若是此時配置文件中仍然配置,會致使remote失效,從而不能監聽端口 - 在執行./gradlew someTask -Dorg.gradle.daemon=false
這行命令時,爲了方即可以省略後面的-D
參數,改成在配置文件中增長上述配置。這是-D
參數的描述
若是你以爲好,對你有過幫助,請給我一點打賞鼓勵吧,一分也是愛呀!