也許是史上最詳細的gradle教程

如果有類似疑問的同學可以好好閱讀該博客

  • defaultConfig{},android{},buildType{}等標籤帶便爲什麼這麼寫,代表什麼意思?
  • project.gradle與每個moudle的gradle的執行順序是怎麼樣的關係

Gradle是當下非常厲害的構建工具,那麼什麼叫構建工具呢,就是根據你的輸入的信息幹一堆事,最後給我幾個產物(Artifact),在gradle火爆之前,非常常見的構建工具室ANT,然後又進化到Maven。Ant和Maven這兩個構建工具其實還算方便,現在還有很多的項目都在使用,比如服務器開發的構建,但是二者都有一些缺點,所以讓懶得人覺得不是那麼的方便,比如,Maven編譯規則都是通過XML來編寫的,XML雖然是通俗易懂,但是很難在XML中描述if{某個條件成功,編譯某文件}else{編譯其他文件}這樣有不同條件的任務。

怎麼解決?

  • 如果解決既能讓程序員能看懂,且顯得很簡潔呢?gradle選擇了Groovy,Groovy基於java並且拓展了java,java程序員可以無縫切換到groovy開發程序,groovy說白了就是將寫java程序編程寫腳本程序一樣,寫完了就可以執行,Groovy內部會將其編譯成java.class然後啓動虛擬機來執行,當然這些底層具體怎麼執行不需要你管。
  • 除了可以使用靈活的語言來寫構建規則外,Gradle的另一個特點就是DSL,領域相關語言,什麼是DSL,可以看做是一種抽象的處理方式,用DSL有什麼好處呢?1:提高開發效率,通過DSL來抽象構建模型,抽取公共的代碼,減少重複的勞動;2:和領域專家溝通,領域專家可以通過DSL來構建系統的功能;3:執行環境的改變,可以彌補宿主語言的侷限性;
  • 爲啥Groovy能比較好的構建DSL,1:不需要class文件,可以直接執行腳本,2:閉包的特性和語法的簡介,使用非常的靈活,3:可以和java無縫的整合,4:Groovy自身不是DSL,Groovy官方已經發布了較多基於Groovy書寫的DSL,比如 GANT, GORM, XMLBuilder, HtmlBuilder等等;在gradle中類似於sourceSets代表源文件的集合等,您也可以實現一個專屬的DSL還是蠻簡單的。
  • 對使用者而言,這些行話也就是DSL的好處是什麼呢?這就是:一句行話可以包含很多意思,而且在這個行當裏的人一聽就懂,不用解釋。另外,基於行話,我們甚至可以建立一個模板,使用者只要往這個模板裏填必須要填的內容,Gradle就可以非常漂亮得完成工作,得到想要的東西。

到此,大家應該明白了要真正的學會Gradle恐怕離不開下面兩個基礎知識

  • Groovy,它是基於java,所以我們僅介紹java之外的東西,瞭解Grovvy語言是掌握Gradle的基礎
  • Gradle作爲一個工具,它的行話和它的爲人處世的原則

 

Groovy的介紹

Groovy是一種動態語言。這種語言比較有特點,它和Java一樣,也運行於Java虛擬機中。恩??對頭,簡單粗暴點兒看,你可以認爲Groovy擴展了Java語言。比如,Groovy對自己的定義就是:Groovy是在 java平臺上的、 具有像Python, Ruby 和 Smalltalk 語言特性的靈活動態語言, Groovy保證了這些特性像 Java語法一樣被 Java開發者使用。

除了語言和Java相通外,Groovy有時候又像一種腳本語言。前文也提到過,當我執行Groovy腳本時,Groovy會先將其編譯成Java類字節碼,然後通過Jvm來執行這個Java類。圖1展示了Java、Groovy和Jvm之間的關係。

 

實際上Groovy code真正執行的是已經變成了java字節碼,所以JVM根本不知道自己運行的是Groovy代碼

  • Groovy是依賴於JDK的,首先確保環境中配置了JDK
  • 下載Groovy(地址:http://groovy-lang.org/download.html
  • 配置Groovy環境變量,1:vim ~/.bash_profile,2:export PATH=$PATH:/Users/zew/mygit/groovy-2.5.2/bin。3:source~/.bash_profile,4:驗證終端輸入:groovy -v,如果出現:Groovy Version: 2.5.2 JVM: 1.8.0_51 Vendor: Oracle Corporation OS: Mac OS X。說明配置成功。

測試一下

我們新建一個test.groovy,文件在裏面添加:println "hello groovy"

然後我們切換到當前的目錄下,執行命令:groovy test.groovy

輸出:hello groovy

是不是感覺Groovy跟shell和python好類似。

另外Groovy除了直接使用JDK以外,Groovy還有一套GDK,網址是:http://www.groovy-lang.org/api.html

 


l Groovy註釋標記和Java一樣,支持//或者/**/
l Groovy語句可以不用分號結尾。Groovy爲了儘量減少代碼的輸入,確實煞費苦心
l Groovy中支持動態類型,即定義變量的時候可以不指定其類型。Groovy中,變量定義可以使用關鍵字def。注意,雖然def不是必須的,但是爲了代碼清晰,建議還是使用def關鍵字
   def variable1 = 1   //可以不使用分號結尾
   def varable2 = "I ama person"
  def  int x = 1  //變量定義時,也可以直接指定類型
l   函數定義時,參數的類型也可以不指定。比如
String testFunction(arg1,arg2){//無需指定參數類型
  ...
}
l 除了變量定義可以不指定類型外,Groovy中函數的返回值也可以是無類型的。比如:
//無類型的函數定義,必須使用def關鍵字
def  nonReturnTypeFunc(){
    last_line   //最後一行代碼的執行結果就是本函數的返回值
}
//如果指定了函數返回類型,則可不必加def關鍵字來定義函數
String getString(){
   return"I am a string"

 

其實,所謂的無返回類型的函數,我估計內部都是按返回Object類型來處理的。畢竟,Groovy是基於Java的,而且最終會轉成Java Code運行在JVM上

 


l 函數返回值:Groovy的函數裏,可以不使用returnxxx來設置xxx爲函數返回值。如果不使用return語句的話,則函數裏最後一句代碼的執行結果被設置成返回值。比如
//下面這個函數的返回值是字符串"getSomething return value"
def getSomething(){
     "getSomething return value" //如果這是最後一行代碼,則返回類型爲String
      1000//如果這是最後一行代碼,則返回類型爲Integer

注意,如果函數定義時候指明瞭返回值類型的話,函數中則必須返回正確的數據類型,否則運行時報錯。如果使用了動態類型的話,你就可以返回任何類型了。

l Groovy對字符串支持相當強大,充分吸收了一些腳本語言的優點:
1  單引號''中的內容嚴格對應Java中的String,不對$符號進行轉義
   defsingleQuote='I am $ dolloar'  //輸出就是I am $ dolloar
2  雙引號""的內容則和腳本語言的處理有點像,如果字符中有$號的話,則它會$表達式先求值。
   defdoubleQuoteWithoutDollar = "I am one dollar" //輸出 I am one dollar
   def x = 1
   defdoubleQuoteWithDollar = "I am $x dolloar" //輸出I am 1 dolloar
3 三個引號'''xxx'''中的字符串支持隨意換行 比如
   defmultieLines = ''' begin
     line  1
     line  2
     end '''
l 最後,除了每行代碼不用加分號外,Groovy中函數調用的時候還可以不加括號。比如:
println("test") ---> println"test"
注意,雖然寫代碼的時候,對於函數調用可以不帶括號,但是Groovy經常把屬性和函數調用混淆。比如
def getSomething(){
  "hello"
}

 

getSomething()  //如果不加括號的話,Groovy會誤認爲getSomething是一個變量。

 

 

 

 

所以,調用函數要不要帶括號,我個人意見是如果這個函數是Groovy API或者Gradle API中比較常用的,比如println,就可以不帶括號。否則還是帶括號。Groovy自己也沒有太好的辦法解決這個問題,只能兵來將擋水來土掩了。

好了,瞭解上面一些基礎知識後,我們再介紹點深入的內容。

 

Groovy中的數據類型

Groovy中的數據類型我們就介紹兩種和Java不太一樣的:

  • 一個是Java中的基本數據類型。
  • 另外一個是Groovy中的容器類。
  • 最後一個非常重要的是閉包。

基本數據類型

作爲動態語言,Groovy世界中的所有事物都是對象。所以,int,boolean這些Java中的基本數據類型,在Groovy代碼中其實對應的是它們的包裝數據類型。比如int對應爲Integer,boolean對應爲Boolean。比如下圖中的代碼執行結果:

打印結果:java.lang.Integer

 容器類

Groovy中的容器類很簡單,就三種:

  • List:鏈表,其底層對應Java中的List接口,一般用ArrayList作爲真正的實現類。
  • Map:鍵-值表,其底層對應Java中的LinkedHashMap。
  • Range:範圍,它其實是List的一種拓展。

對容器而言,我們最重要的是瞭解它們的用法。下面是一些簡單的例子:

變量定義:List變量由[]定義,比如
def aList = [5,'string',true] //List由[]定義,其元素可以是任何對象
變量存取:可以直接通過索引存取,而且不用擔心索引越界。如果索引超過當前鏈表長度,List會自動
往該索引添加元素
assert aList[1] == 'string'
assert aList[5] == null//第6個元素爲空
aList[100] = 100 //設置第101個元素的值爲100
assert aList[100] == 100

那麼,aList到現在爲止有多少個元素呢?
println aList.size ===>結果是101

map類

容器變量定義
變量定義:Map變量由[:]定義,比如
def aMap = ['key1':'value1','key2':true]

println aMap['key1']

Map由[:]定義,注意其中的冒號。冒號左邊是key,右邊是Value。key必須是字符串,value可以是任何對象。另外,key可以用''或""包起來,也可以不用引號包起來。比如
def aMap = [key1:"value",key2:true]//其中的key1和key2默認被處理成字符串"key1"和"key2"

println aMap['key1']

不過Key要是不使用引號包起來的話,也會帶來一定混淆,比如
def key1="zew"

def aMap=[key1:"who am i?"]
aMap中的key1到底是"key1"還是變量key1的值“zew”?顯然,答案是字符串"key1"。如果要是"zew"的話,則aMap的定義必須設置成:

def aMap=[(key1):"who am i?"]

Map中元素的存取更加方便,它支持多種方法:
println aMap.keyName    <==這種表達方法好像key就是aMap的一個成員變量一樣
println aMap['keyName'] <==這種表達方法更傳統一點
aMap.anotherkey = "i am map"  <==爲map添加新元素

 

 Range類

Range是Groovy對List的一種拓展,變量定義和大體的使用方法如下:

def aRange = 1..5 <==Range類型的變量 由begin值+兩個點+end值表示
                      左邊這個aRange包含1,2,3,4,5這5個值
如果不想包含最後一個元素,則
def aRangeWithoutEnd = 1..<5  <==包含1,2,3,4這4個元素
println aRange.from
println aRange.to

 

Groovy API的一些祕笈

前面講這些東西,主要是讓大家瞭解Groovy的語法。實際上在coding的時候,是離不開SDK的。由於Groovy是動態語言,所以要使用它的SDK也需要掌握一些小訣竅。

Groovy的API文檔位於http://www.groovy-lang.org/api.html

以上文介紹的Range爲例,我們該如何更好得使用它呢?

l 先定位到Range類。它位於groovy.lang包中:

                                                                                 Range類API文檔

有了API文檔,你就可以放心調用其中的函數了。不過,不過,不過:我們剛纔代碼中用到了Range.from/to屬性值,但翻看Range API文檔的時候,其實並沒有這兩個成員變量。圖6是Range的方法

 

 

文檔中並沒有說明Range有from和to這兩個屬性,但是卻有getFrom和getTo這兩個函數。What happened?原來:

根據Groovy的原則,如果一個類中有名爲xxyyzz這樣的屬性(其實就是成員變量),Groovy會自動爲它添加getXxyyzz和setXxyyzz兩個函數,用於獲取和設置xxyyzz屬性值。

注意,get和set後第一個字母是大寫的

所以,當你看到Range中有getFrom和getTo這兩個函數時候,就得知道潛規則下,Range有from和to這兩個屬性。當然,由於它們不可以被外界設置,所以沒有公開setFrom和setTo函數。

閉包

閉包的樣子

閉包,英文叫Closure,是Groovy中非常重要的一個數據類型或者說一種概念了。閉包的歷史來源,種種好處我就不說了。我們直接看怎麼使用它!

閉包,是一種數據類型,它代表了一段可執行的代碼。其外形如下


def aClosure = {//閉包是一段代碼,所以需要用花括號括起來..
    Stringparam1, int param2 ->  //這個箭頭很關鍵。箭頭前面是參數定義,箭頭後面是代碼
    println"this is code" //這是代碼,最後一句是返回值,
   //也可以使用return,和Groovy中普通函數一樣

簡而言之,Closure的定義格式是:

def xxx = {paramters -> code}  //或者  
def xxx = {無參數,純code}  這種case不需要->符號</code>  

閉包定義好後,要調用它的方法就是:

閉包對象.call(參數)  或者更像函數指針調用的方法:

閉包對象(參數) 

def aClosure={
	String param1,int param2->
	println "this is code" 
}

//aClosure.call("this is string",100) 
aClosure("this is string", 100)

比如:

aClosure.call("this is string",100) 

或者


aClosure("this is string", 100)

打印的結果都是

this is code

上面就是一個閉包的定義和使用。在閉包中,還需要注意一點:

如果閉包沒定義參數的話,則隱含有一個參數,這個參數名字叫it,和this的作用類似。it代表閉包的參數。

比如:
def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
等同於:
def greeting = { it -> "Hello, $it!"}
assert greeting('Patrick') == 'Hello, Patrick!'
但是,如果在閉包定義時,採用下面這種寫法,則表示閉包沒有參數!
def noParamClosure = { -> true }
這個時候,我們就不能給noParamClosure傳參數了!
noParamClosure ("test")  <==報錯喔!

Closure使用中的注意點

省略圓括號

閉包在Groovy中大量使用,比如很多類都定義了一些函數,這些函數最後一個參數都是一個閉包。比如:

public static <T> List<T>each(List<T> self, Closure closure)
上面這個函數表示針對List的每一個元素都會調用closure做一些處理。這裏的closure,就有點回調函數的感覺。但是,在使用這個each函數的時候,我們傳遞一個怎樣的Closure進去呢?比如:
def iamList = [1,2,3,4,5]  //定義一個List
iamList.each{ //調用它的each,這段代碼的格式看不懂了吧?each是個函數,圓括號去哪了?
      println it
}
上面代碼有兩個知識點:
l each函數調用的圓括號不見了!原來,Groovy中,當函數的最後一個參數是閉包的話,可以省略圓括號。比如
def testClosure(int a1,String b1, Closure closure){
      //dosomething
     closure() //調用閉包
}
那麼調用的時候,就可以免括號!
testClosure (4, "test", {
   println"i am in closure"
} )  //紅色的括號可以不寫..

注意,這個特點非常關鍵,因爲以後在Gradle中經常會出現圖7這樣的代碼:

經常碰見圖7這樣的沒有圓括號的代碼。省略圓括號雖然使得代碼簡潔,看起來更像腳本語言,但是它這經常會讓我confuse(不知道其他人是否有同感),以doLast爲例,完整的代碼應該按下面這種寫法:

doLast({
   println'Hello world!'
})

 

有了圓括號,你會知道 doLast只是把一個Closure對象傳了進去。很明顯,它不代表這段腳本解析到doLast的時候就會調用println 'Hello world!' 。

但是把圓括號去掉後,就感覺好像println 'Hello world!'立即就會被調用一樣!

如何確定Closure的參數

另外一個比較讓人頭疼的地方是,Closure的參數該怎麼搞?還是剛纔的each函數:

public static <T> List<T> each(List<T>self, Closure closure)

如何使用它呢?比如:

def iamList = [1,2,3,4,5]  //定義一個List變量
iamList.each{ //調用它的each函數,只要傳入一個Closure就可以了。
  println it
}

看起來很輕鬆,其實:

對於each所需要的Closure,它的參數是什麼?有多少個參數?返回值是什麼?

我們能寫成下面這樣嗎?

iamList.each{String name,int x ->
  return x
}  //運行的時候肯定報錯!

 

所以,Closure雖然很方便,但是它一定會和使用它的上下文有極強的關聯。要不,作爲類似回調這樣的東西,我如何知道調用者傳遞什麼參數給Closure呢?

 

此問題如何破解?只能通過查詢API文檔才能瞭解上下文語義。比如下圖:

 

圖中:

  • each函數說明中,將給指定的closure傳遞Set中的每一個item。所以,closure的參數只有一個。
  • findAll中,絕對抓瞎了。一個是沒說明往Closure裏傳什麼。另外沒說明Closure的返回值是什麼.....。

對Map的findAll而言,Closure可以有兩個參數。findAll會將Key和Value分別傳進去。並且,Closure返回true,表示該元素是自己想要的。返回false表示該元素不是自己要找的。示意代碼如圖所示:

 

 

Closure的使用有點坑,很大程度上依賴於你對API的熟悉程度,所以最初階段,SDK查詢是少不了的。

 

腳本類、文件I/O和XML操作

最後,我們來看一下Groovy中比較高級的用法。

腳本類

1: 腳本中import其他類

Groovy中可以像Java那樣寫package,然後寫類。比如在文件夾com/cmbc/groovy/目錄中放一個文件,叫Test.groovy,如圖10所示:

你看,圖10中的Test.groovy和Java類就很相似了。當然,如果不聲明public/private等訪問權限的話,Groovy中類及其變量默認都是public的。

現在,我們在測試的根目錄下建立一個test.groovy文件。其代碼如下所示:

你看,test.groovy先import了com.cmbc.groovy.Test類,然後創建了一個Test類型的對象,接着調用它的print函數。

這兩個groovy文件的目錄結構如圖12所示:

在groovy中,系統自帶會加載當前目錄/子目錄下的xxx.groovy文件。所以,當執行groovy test.groovy的時候,test.groovy import的Test類能被自動搜索並加載到。

腳本到底是什麼

Java中,我們最熟悉的是類。但是我們在Java的一個源碼文件中,不能不寫class(interface或者其他....),而Groovy可以像寫腳本一樣,把要做的事情都寫在xxx.groovy中,而且可以通過groovy xxx.groovy直接執行這個腳本。這到底是怎麼搞的?

既然是基於Java的,Groovy會先把xxx.groovy中的內容轉換成一個Java類。比如:

test.groovy的代碼是:

println 'Groovy world!'

Groovy把它轉換成這樣的Java類:

執行 groovyc -d classes test.groovy

groovyc是groovy的編譯命令,-dclasses用於將編譯得到的class文件拷貝到classes文件夾下

如圖是test.groovy腳本轉換得到的java class。用jd-gui反編譯它的代碼:

import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.BytecodeInterface8;
import org.codehaus.groovy.runtime.GeneratedClosure;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class test extends Script
{
  public test()
  {
  }

  public test(Binding context)
  {
    super(context);
  }

  public static void main(String[] args)
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    arrayOfCallSite[0].call(InvokerHelper.class, test.class, args);
  }

  public Object run()
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray(); return arrayOfCallSite[1].call(arrayOfCallSite[2].callGroovyObjectGetProperty(this), new test._run_closure1(this)); } 
  public final class _run_closure1 extends Closure implements GeneratedClosure { public _run_closure1(Object _thisObject) { super(_thisObject); } 
    public Object doCall(String name, int x) { CallSite[] arrayOfCallSite = $getCallSiteArray(); return Integer.valueOf(x);
    }

    public Object call(String name, int x)
    {
      CallSite[] arrayOfCallSite = $getCallSiteArray();
      if ((__$stMC) || (BytecodeInterface8.disabledStandardMetaClass()))
        return arrayOfCallSite[0].callCurrent(this, name, Integer.valueOf(x));
    }
  }
}
  • test.groovy被轉換成了一個test類,它從script派生。
  • 每一個腳本都會生成一個static main函數。這樣,當我們groovytest.groovy的時候,其實就是用java去執行這個main函數
  • 腳本中的所有代碼都會放到run函數中。比如,println 'Groovy world',這句代碼實際上是包含在run函數裏的。
  • 如果腳本中定義了函數,則函數會被定義在test類中。

groovyc是一個比較好的命令,讀者要掌握它的用法。然後利用jd-gui來查看對應class的Java源碼。

 腳本中的變量和作用域

前面說了,xxx.groovy只要不是和Java那樣的class,那麼它就是一個腳本。而且腳本的代碼其實都會被放到run函數中去執行。那麼,在Groovy的腳本中,很重要的一點就是腳本中定義的變量和它的作用域。舉例

def x = 1 <==注意,這個x有def(或者指明類型,比如 int x = 1)
def printx(){
   println x
}

printx() <==報錯,說x找不到

爲什麼?繼續來看反編譯後的class文件。

import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class test extends Script
{
  public test()
  {
  }

  public test(Binding context)
  {
    super(context);
  }

  public static void main(String[] args)
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    arrayOfCallSite[0].call(InvokerHelper.class, test.class, args);
  }

  public Object run()
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray(); Object x = Integer.valueOf(1); return x;
  }
  public Object printx() { CallSite[] arrayOfCallSite = $getCallSiteArray(); return arrayOfCallSite[1].callCurrent(this, arrayOfCallSite[2].callGroovyObjectGetProperty(this));
  }
}
l printx被定義成test類的成員函數
l def x = 1,這句話是在run中創建的。所以,x=1從代碼上看好像是在整個腳本中定義的,但實際上printx訪問不了它。printx是test成員函數,除非x也被定義成test的成員函數,否則printx不能訪問它。
那麼,如何使得printx能訪問x呢?很簡單,定義的時候不要加類型和def。即:
x = 1 <==注意,去掉def或者類型
def printx(){
   println x
}

printx() <==OK

這次Java源碼又變成什麼樣了呢?

import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class test extends Script
{
  public test()
  {
  }

  public test(Binding context)
  {
    super(context);
  }

  public static void main(String[] args)
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    arrayOfCallSite[0].call(InvokerHelper.class, test.class, args);
  }

  public Object run()
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray(); int i = 1; ScriptBytecodeAdapter.setGroovyObjectProperty(Integer.valueOf(i), test.class, this, (String)"x"); return Integer.valueOf(i);
  }
  public Object printx() { CallSite[] arrayOfCallSite = $getCallSiteArray(); return arrayOfCallSite[1].callCurrent(this, arrayOfCallSite[2].callGroovyObjectGetProperty(this));
  }
}

圖15中,x也沒有被定義成test的成員函數,而是在run的執行過程中,將x作爲一個屬性添加到test實例對象中了。然後在printx中,先獲取這個屬性。

注意,Groovy的文檔說 x = 1這種定義將使得x變成test的成員變量,但從反編譯情況看,這是不對得.....

雖然printx可以訪問x變量了,但是假如有其他腳本卻無法訪問x變量。因爲它不是test的成員變量。

比如,我在測試目錄下創建一個新的名爲test1.groovy。這個test1將訪問test.groovy中定義的printx函數:

 

這種方法使得我們可以將代碼分成模塊來編寫,比如將公共的功能放到test.groovy中,然後使用公共功能的代碼放到test1.groovy中

執行groovy test1.groovy,報錯。說x找不到。這是因爲x是在test的run函數動態加進去的。怎麼辦?

Caught: groovy.lang.MissingPropertyException: No such property: x for class: test
groovy.lang.MissingPropertyException: No such property: x for class: test
        at test.printx(test.groovy:3)
        at test$printx.call(Unknown Source)
        at test1.run(test1.groovy:2)
 

import groovy.transform.Field;   //必須要先import
@Field x = 1  <==在x前面加上@Field標註,這樣,x就徹徹底底是test的成員變量了。

查看編譯後的test.class文件,得到:

import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.CallSite;

public class test extends Script
{
  Object x;

  public test()
  {
    int i = 1;
    this.x = Integer.valueOf(i);
  }

  public test(Binding context)
  {
    super(context);
    int i = 1;
    this.x = Integer.valueOf(i);
  }

  public static void main(String[] args)
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    arrayOfCallSite[0].call(InvokerHelper.class, test.class, args);
  }

  public Object run()
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray();
    return null;
  }

  public Object printx()
  {
    CallSite[] arrayOfCallSite = $getCallSiteArray(); return arrayOfCallSite[1].callCurrent(this, this.x);
  }
}

這個時候,test.groovy中的x就成了test類的成員函數了。如此,我們可以在script中定義那些需要輸出給外部腳本或類使用的變量了!

讀文件

Groovy中,文件讀操作簡單到令人髮指:

def targetFile = new File(文件名) <==File對象還是要創建的。

然後打開http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/File.html

看看Groovy定義的API:

1:讀該文件中的每一行:eachLine的唯一參數是一個Closure。Closure的參數是文件每一行的內容

   其內部實現肯定是Groovy打開這個文件,然後讀取文件的一行,然後調用Closure...

targetFile.eachLine{ 
   StringoneLine ->
    printlnoneLine    
}  <==是不是令人髮指??!

2:直接得到文件內容

targetFile.getBytes()  <==文件內容一次性讀出,返回類型爲byte[]

注意前面提到的getter和setter函數,這裏可以直接使用targetFile.bytes    //....

3:使用InputStream.InputStream的SDK在

  http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.html


def ism =  targetFile.newInputStream()
//操作ism,最後記得關掉
ism.close

4:使用閉包操作inputStream,以後在Gradle裏會常看到這種搞法

 targetFile.withInputStream{ ism ->
   操作ism. 不用close。Groovy會自動替你close
}

 

確實夠簡單,令人髮指。我當年死活也沒找到withInputStream是個啥意思。所以,請各位開發者牢記Groovy I/O操作相關類的SDK地址:

 

  • java.io.File: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/File.html
  • java.io.InputStream: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/InputStream.html      
  • java.io.OutputStream: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/OutputStream.html
  • java.io.Reader: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Reader.html
  • java.io.Writer: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/Writer.html
  • java.nio.file.Path: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/nio/file/Path.html

 

寫文件

和讀文件差不多。不再囉嗦。這裏給個例子,告訴大家如何copy文件

def srcFile = new File(源文件名)
def targetFile = new File(目標文件名)
targetFile.withOutputStream{ os->
  srcFile.withInputStream{ ins->
      os << ins   //利用OutputStream的<<操作符重載,完成從inputstream到OutputStream
       //的輸出
   }
}

尼瑪....關於OutputStream的<<操作符重載,查看SDK文檔後可知:

 

再一次向極致簡單致敬。但是,SDK恐怕是離不開手了...

 XML操作

 

除了I/O異常簡單之外,Groovy中的XML操作也極致得很。Groovy中,XML的解析提供了和XPath類似的方法,名爲GPath。這是一個類,提供相應API。關於XPath,請腦補https://en.wikipedia.org/wiki/XPath。

GPath功能包括:給個例子好了,來自Groovy官方文檔。

test.xml文件:
<response version-api="2.0">
       <value>
           <books>
               <book available="20" id="1">
                   <title>Don Xijote</title>
                   <author id="1">Manuel De Cervantes</author>
               </book>
               <book available="14" id="2">
                   <title>Catcher in the Rye</title>
                  <author id="2">JD Salinger</author>
              </book>
              <book available="13" id="3">
                  <title>Alice in Wonderland</title>
                  <author id="3">Lewis Carroll</author>
              </book>
              <book available="5" id="4">
                  <title>Don Xijote</title>
                  <author id="4">Manuel De Cervantes</author>
              </book>
           </books>
      </value>
   </response>

現在來看怎麼玩轉GPath:

//第一步,創建XmlSlurper類
def xparser = new XmlSlurper()
def targetFile = new File("test.xml")
//轟轟的GPath出場
GPathResult gpathResult =xparser.parse(targetFile)
 
//開始玩test.xml。現在我要訪問id=4的book元素。
//下面這種搞法,gpathResult代表根元素response。通過e1.e2.e3這種
//格式就能訪問到各級子元素....
def book4 = gpathResult.value.books.book[3]
//得到book4的author元素
def author = book4.author
//再來獲取元素的屬性和textvalue
assert author.text() == ' Manuel De Cervantes '
獲取屬性更直觀
author.@id == '4' 或者 author['@id'] == '4'
屬性一般是字符串,可通過toInteger轉換成整數
[email protected]() == 4
好了。GPath就說到這。再看個例子。我在使用Gradle的時候有個需求,就是獲取AndroidManifest.xml版本號(versionName)。有了GPath,一行代碼搞定,請看:
def androidManifest = newXmlSlurper().parse("AndroidManifest.xml")
println androidManifest['@android:versionName']
或者
println androidManifest.@'android:versionName'

更多

 

作爲一門語言,Groovy是複雜的,是需要深入學習和鑽研的。一本厚書甚至都無法描述Groovy的方方面面。

Anyway,從使用角度看,尤其是又限定在Gradle這個領域內,能用到的都是Groovy中一些簡單的知識。

 

Gradle介紹

現在正式進入Gradle。Gradle是一個工具,同時它也是一個編程框架。前面也提到過,使用這個工具可以完成app的編譯打包等工作。當然你也可以用它幹其他的事情。

Gradle是什麼?學習它到什麼地步就可以了?

----------------------------------------------------------------------------------------------------------

=====>看待問題的時候,所站的角度非常重要。

-->當你把Gradle當工具看的時候,我們只想着如何用好它。會寫、寫好配置腳本就OK

-->當你把它當做編程框架看的時候,你可能需要學習很多更深入的內容。

另外,今天我們把它當工具看,明天因爲需求發生變化,我們可能又得把它當編程框架看。

4.1  Gradle開發環境部署

Gradle的官網:http://gradle.org/

文檔位置:https://docs.gradle.org/current/release-notes。其中的UserGuideDSL Reference很關鍵。User Guide就是介紹Gradle的一本書,而DSLReference是Gradle API的說明。

以Ubuntu爲例,下載Gradle:http://gradle.org/gradle-download/  選擇Completedistribution和Binary only distribution都行。然後解壓到指定目錄。

最後,設置~/.bashrc,把Gradle加到PATH裏

執行source ~/.bashrc,初始化環境。

執行gradle --version,如果成功運行就OK了。

注意,爲什麼說Gradle是一個編程框架?來看它提供的API文檔:

https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html

 

原來,我們編寫所謂的編譯腳本,其實就是玩Gradle的API....所以它從更底層意義上看,是一個編程框架!

既然是編程框架,我在講解Gradle的時候,儘量會從API的角度來介紹。有些讀者肯定會不耐煩,爲嘛這麼費事?

從我個人的經歷來看:因爲我從網上學習到的資料來看,幾乎全是從腳本的角度來介紹Gradle,結果學習一通下來,只記住參數怎麼配置,卻不知道它們都是函數調用,都是嚴格對應相關API的。

而從API角度來看待Gradle的話,有了SDK文檔,你就可以編程。編程是靠記住一行行代碼來實現的嗎?不是,是在你掌握大體流程,然後根據SDK+API來完成的!

其實,Gradle自己的User Guide也明確說了:

Buildscripts are code

 基本組件

Gradle是一個框架,它定義一套自己的遊戲規則。我們要玩轉Gradle,必須要遵守它設計的規則。下面我們來講講Gradle的基本組件:

Gradle中,每一個待編譯的工程都叫一個Project。每一個Project在構建的時候都包含一系列的Task。比如一個Android APK的編譯可能包含:Java源碼編譯Task、資源編譯Task、JNI編譯Task、lint檢查Task、打包生成APK的Task、簽名Task等

一個Project到底包含多少個Task,其實是由編譯腳本指定的插件決定。插件是什麼呢?插件就是用來定義Task,並具體執行這些Task的東西。

剛纔說了,Gradle是一個框架,作爲框架,它負責定義流程和規則。而具體的編譯工作則是通過插件的方式來完成的。比如編譯Java有Java插件,編譯Groovy有Groovy插件,編譯Android APP有Android APP插件,編譯Android Library有Android Library插件

好了。到現在爲止,你知道Gradle中每一個待編譯的工程都是一個Project,一個具體的編譯過程是由一個一個的Task來定義和執行的。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章