深刻理解Android之Gradle
格式更加精美的PDF版請到:https://pan.baidu.com/s/1boG2cLD下載html
weibo分享失效,請各位到百度雲盤下載java
Gradle是當前很是「勁爆」得構建工具。本篇文章就是專爲講解Gradle而來。介紹Gradle以前,先說點題外話。python
1、題外話
說實話,我在大法工做的時候,就見過Gradle。可是當時我一直不知道這是什麼東西。並且大法工具組的工程師還將其和Android Studio大法版一塊兒推送,偶一看就更沒興趣了。爲何那個時候如此不待見Gradle呢?由於我此前一直是作ROM開發。在這個層面上,咱們用make,mm或者mmm就能夠了。並且,編譯耗時對咱們來講也不是啥痛點,由於用組內吊炸天的神機服務器完整編譯大法的image也要耗費1個小時左右。因此,那個時侯Gradle徹底不是咱們的菜。android
如今,搞APP開發居多,編譯/打包等問題當即就成痛點了。好比:程序員
一個APP有多個版本,Release版、Debug版、Test版。甚至針對不一樣APP Store都有不一樣的版本。在之前ROM的環境下,雖然能夠配置Android.mk,可是須要依賴整個Android源碼,並且還不能徹底作到知足條件,不少事情須要手動搞。一個app若是涉及到多個開發者,手動操做必然會帶來混亂。
library工程咱們須要編譯成jar包,而後發佈給其餘開發者使用。之前是用eclipse的export,作一堆選擇。要是能自動編譯成jar包就爽了。
上述問題對絕大部分APP開發者而言都不陌生,而Gradle做爲一種很方便的的構建工具,能夠很是輕鬆得解決構建過程當中的各類問題。shell
2、閒言構建
構建,叫build也好,叫make也行。反正就是根據輸入信息而後幹一堆事情,最後獲得幾個產出物(Artifact)。編程
最最簡單的構建工具就是make了。make就是根據Makefile文件中寫的規則,執行對應的命令,而後獲得目標產物。api
平常生活中,和構建最相似的一個場景就是作菜。輸入各類食材,而後按固定的工序,最後獲得一盤菜。固然,作一樣一道菜,因爲需求不一樣,作出來的東西也不盡相同。好比,宮保雞丁這道菜,回民要求不能放大油、口淡的要求少放鹽和各類油、辣不怕的男女漢子們能夠要求多放辣子....總之,作菜包含固定的工序,可是對於不一樣條件或需求,須要作不一樣的處理。緩存
在Gradle爆紅以前,經常使用的構建工具是ANT,而後又進化到Maven。ANT和Maven這兩個工具其實也還算方便,如今還有不少地方在使用。可是兩者都有一些缺點,因此讓更懶得人以爲不是那麼方便。好比,Maven編譯規則是用XML來編寫的。XML雖然通俗易懂,可是很難在xml中描述if{某條件成立,編譯某文件}/else{編譯其餘文件}這樣有不一樣條件的任務。bash
怎麼解決?怎麼解決好?對程序員而言,天然是編程解決,可是有幾個小要求:
這種「編程」不要搞得和程序員理解的編程那樣複雜。寥寥幾筆,輕輕鬆鬆把要作的事情描述出來就最好不過。因此,Gradle選擇了Groovy。Groovy基於Java並拓展了Java。 Java程序員能夠無縫切換到使用Groovy開發程序。Groovy說白了就是把寫Java程序變得像寫腳本同樣簡單。寫完就能夠執行,Groovy內部會將其編譯成Javaclass而後啓動虛擬機來執行。固然,這些底層的渣活不須要你管。
除了能夠用很靈活的語言來寫構建規則外,Gradle另一個特色就是它是一種DSL,即Domain Specific Language,領域相關語言。什麼是DSL,說白了它是某個行業中的行話。仍是不明白?徐克導演得《智取威虎山》中就有很典型的DSL使用描述,好比:
------------------------------------------------------------------------------
土匪:蘑菇,你哪路?什麼價?(什麼人?到哪裏去?)
楊子榮:哈!想啥來啥,想吃奶來了媽媽,想孃家的人,孩子他舅舅來了。(找同行)
楊子榮:拜見三爺!
土匪:天王蓋地虎!(你好大的膽!敢來氣你的祖宗?)
楊子榮:寶塔鎮河妖!(要是那樣,叫我從山上摔死,掉河裏淹死。)
土匪:野雞悶頭鑽,哪能上天王山!(你不是正牌的。)
楊子榮:地上有的是米,喂呀,有根底!(老子是正牌的,老牌的。)
------------------------------------------------------------------------------
Gradle中也有相似的行話,好比sourceSets表明源文件的集合等.....太多了,記不住。之後咱們都會接觸到這些行話。那麼,對使用者而言,這些行話的好處是什麼呢?這就是:
一句行話能夠包含不少意思,並且在這個行當裏的人一聽就懂,不用解釋。另外,基於行話,咱們甚至能夠創建一個模板,使用者只要往這個模板裏填必需要填的內容,Gradle就能夠很是漂亮得完成工做,獲得想要的東西。
這就和如今的智能炒菜機器似的,只要選擇菜譜,把食材準備好,剩下的事情就不用你操心了。吃貨們對這種作菜方式確定是以反感爲主,太沒有特點了。可是程序員對Gradle相似作法卻熱烈擁抱。
到此,你們應該明白要真正學會Gradle恐怕是離不開下面兩個基礎知識:
Groovy,因爲它基於Java,因此咱們僅介紹Java以外的東西。瞭解Groovy語言是掌握Gradle的基礎。
Gradle做爲一個工具,它的行話和它「爲人處事」的原則。
3、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之間的關係。
圖1 Java、Groovy和JVM的關係
實際上,因爲Groovy Code在真正執行的時候已經變成了Java字節碼,因此JVM根本不知道本身運行的是Groovy代碼。
下面咱們將介紹Groovy。因爲此文的主要目的是Gradle,因此咱們不會過多討論Groovy中細枝末節的東西,而是把知識點集中在之後和Gradle打交道時一些經常使用的地方上。
3.1 Groovy開發環境
在學習本節的時候,最好部署一下Groovy開發環境。根據Groovy官網的介紹(http://www.groovy-lang.org/download.html#gvm),部署Groovy開發環境很是簡單,在Ubuntu或者cygwin之類的地方:
curl -s get.gvmtool.net | bash
source"$HOME/.gvm/bin/gvm-init.sh"
gvm install groovy
執行完最後一步,Groovy就下載並安裝了。圖1是安裝時候的示意圖
圖1 Groovy安裝示意圖
而後,建立一個test.groovy文件,裏邊只有一行代碼:
println "hello groovy"
執行groovy test.groovy,輸出結果如圖2所示:
圖2 執行groovy腳本
親們,必需要完成上面的操做啊。作完後,有什麼感受和體會?
最大的感受可能就是groovy和shell腳本,或者python好相似。
另外,除了能夠直接使用JDK以外,Groovy還有一套GDK,網址是http://www.groovy-lang.org/api.html。
說實話,看了這麼多家API文檔,仍是Google的Android API文檔作得好。其頁面中右上角有一個搜索欄,在裏邊輸入一些關鍵字,瞬間就能列出候選類,相關文檔,方便得不得了啊.....
3.2 一些前提知識
爲了後面講述方面,這裏先介紹一些前提知識。初期接觸可能有些彆扭,看習慣就行了。
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是一個變量。好比:
圖3 錯誤示意
因此,調用函數要不要帶括號,我我的意見是若是這個函數是Groovy API或者Gradle API中比較經常使用的,好比println,就能夠不帶括號。不然仍是帶括號。Groovy本身也沒有太好的辦法解決這個問題,只能兵來將擋水來土掩了。
好了,瞭解上面一些基礎知識後,咱們再介紹點深刻的內容。
3.3 Groovy中的數據類型
Groovy中的數據類型咱們就介紹兩種和Java不太同樣的:
一個是Java中的基本數據類型。
另一個是Groovy中的容器類。
最後一個很是重要的是閉包。
放心,這裏介紹的東西都很簡單
3.3.1 基本數據類型
做爲動態語言,Groovy世界中的全部事物都是對象。因此,int,boolean這些Java中的基本數據類型,在Groovy代碼中其實對應的是它們的包裝數據類型。好比int對應爲Integer,boolean對應爲Boolean。好比下圖中的代碼執行結果:
圖4 int其實是Integer
3.3.2 容器類
Groovy中的容器類很簡單,就三種:
l List:鏈表,其底層對應Java中的List接口,通常用ArrayList做爲真正的實現類。
l Map:鍵-值表,其底層對應Java中的LinkedHashMap。
l Range:範圍,它實際上是List的一種拓展。
對容器而言,咱們最重要的是瞭解它們的用法。下面是一些簡單的例子:
1. List類
變量定義:List變量由[]定義,好比
def aList = [5,'string',true] //List由[]定義,其元素能夠是任何對象
變量存取:能夠直接經過索引存取,並且不用擔憂索引越界。若是索引超過當前鏈表長度,List會自動
往該索引添加元素
assert aList[1] == 'string'
assert aList[5] == null //第6個元素爲空
aList[100] = 100 //設置第101個元素的值爲10
assert aList[100] == 100
那麼,aList到如今爲止有多少個元素呢?
println aList.size ===>結果是101
2. Map類
容器變量定義
變量定義:Map變量由[:]定義,好比
def aMap = ['key1':'value1','key2':true]
Map由[:]定義,注意其中的冒號。冒號左邊是key,右邊是Value。key必須是字符串,value能夠是任何對象。另外,key能夠用''或""包起來,也能夠不用引號包起來。好比
def aNewMap = [key1:"value",key2:true]//其中的key1和key2默認被
處理成字符串"key1"和"key2"
不過Key要是不使用引號包起來的話,也會帶來必定混淆,好比
def key1="wowo"
def aConfusedMap=[key1:"who am i?"]
aConfuseMap中的key1究竟是"key1"仍是變量key1的值「wowo」?顯然,答案是字符串"key1"。若是要是"wowo"的話,則aConfusedMap的定義必須設置成:
def aConfusedMap=[(key1):"who am i?"]
Map中元素的存取更加方便,它支持多種方法:
println aMap.keyName <==這種表達方法好像key就是aMap的一個成員變量同樣
println aMap['keyName'] <==這種表達方法更傳統一點
aMap.anotherkey = "i am map" <==爲map添加新元素
3. 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
3.3.4 Groovy API的一些祕笈
前面講這些東西,主要是讓你們瞭解Groovy的語法。實際上在coding的時候,是離不開SDK的。因爲Groovy是動態語言,因此要使用它的SDK也須要掌握一些小訣竅。
Groovy的API文檔位於http://www.groovy-lang.org/api.html
以上文介紹的Range爲例,咱們該如何更好得使用它呢?
l 先定位到Range類。它位於groovy.lang包中:
圖5 Range類API文檔
有了API文檔,你就能夠放心調用其中的函數了。不過,不過,不過:咱們剛纔代碼中用到了Range.from/to屬性值,但翻看Range API文檔的時候,其實並無這兩個成員變量。圖6是Range的方法
圖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函數。
3.4 閉包
3.4.1 閉包的樣子
閉包,英文叫Closure,是Groovy中很是重要的一個數據類型或者說一種概念了。閉包的歷史來源,種種好處我就不說了。咱們直接看怎麼使用它!
閉包,是一種數據類型,它表明了一段可執行的代碼。其外形以下:
def aClosure = {//閉包是一段代碼,因此須要用花括號括起來..
Stringparam1, int param2 -> //這個箭頭很關鍵。箭頭前面是參數定義,箭頭後面是代碼
println"this is code" //這是代碼,最後一句是返回值,
//也可使用return,和Groovy中普通函數同樣
}
簡而言之,Closure的定義格式是:
[java] view plain copy
<code class="language-java">def xxx = {paramters -> code} //或者
def xxx = {無參數,純code} 這種case不須要->符號</code>
說實話,從C/C++語言的角度看,閉包和函數指針很像。閉包定義好後,要調用它的方法就是:
閉包對象.call(參數) 或者更像函數指針調用的方法:
閉包對象(參數)
好比:
aClosure.call("this is string",100) 或者
aClosure("this is string", 100)
上面就是一個閉包的定義和使用。在閉包中,還須要注意一點:
若是閉包沒定義參數的話,則隱含有一個參數,這個參數名字叫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") <==報錯喔!
3.4.2 Closure使用中的注意點
1. 省略圓括號
閉包在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 閉包調用
常常遇見圖7這樣的沒有圓括號的代碼。省略圓括號雖然使得代碼簡潔,看起來更像腳本語言,可是它這常常會讓我confuse(不知道其餘人是否有同感),以doLast爲例,完整的代碼應該按下面這種寫法:
doLast({
println'Hello world!'
})
有了圓括號,你會知道 doLast只是把一個Closure對象傳了進去。很明顯,它不表明這段腳本解析到doLast的時候就會調用println 'Hello world!' 。
可是把圓括號去掉後,就感受好像println 'Hello world!'當即就會被調用同樣!
2. 如何肯定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
}
看起來很輕鬆,其實:
l 對於each所須要的Closure,它的參數是什麼?有多少個參數?返回值是什麼?
咱們能寫成下面這樣嗎?
iamList.each{String name,int x ->
return x
} //運行的時候確定報錯!
因此,Closure雖然很方便,可是它必定會和使用它的上下文有極強的關聯。要不,做爲相似回調這樣的東西,我如何知道調用者傳遞什麼參數給Closure呢?
此問題如何破解?只能經過查詢API文檔才能瞭解上下文語義。好比下圖8:
圖8 文檔說明
圖8中:
each函數說明中,將給指定的closure傳遞Set中的每個item。因此,closure的參數只有一個。
findAll中,絕對抓瞎了。一個是沒說明往Closure裏傳什麼。另外沒說明Closure的返回值是什麼.....。
對Map的findAll而言,Closure能夠有兩個參數。findAll會將Key和Value分別傳進去。而且,Closure返回true,表示該元素是本身想要的。返回false表示該元素不是本身要找的。示意代碼如圖9所示:
圖9 Closure調用示例
Closure的使用有點坑,很大程度上依賴於你對API的熟悉程度,因此最初階段,SDK查詢是少不了的。
3.5 腳本類、文件I/O和XML操做
最後,咱們來看一下Groovy中比較高級的用法。
3.5.1 腳本類
1. 腳本中import其餘類
Groovy中能夠像Java那樣寫package,而後寫類。好比在文件夾com/cmbc/groovy/目錄中放一個文件,叫Test.groovy,如圖10所示:
圖10 com/cmbc/groovy/Test.groovy文件
你看,圖10中的Test.groovy和Java類就很類似了。固然,若是不聲明public/private等訪問權限的話,Groovy中類及其變量默認都是public的。
如今,咱們在測試的根目錄下創建一個test.groovy文件。其代碼以下所示:
圖11 test.groovy訪問com/cmbc/groovy包
你看,test.groovy先import了com.cmbc.groovy.Test類,而後建立了一個Test類型的對象,接着調用它的print函數。
這兩個groovy文件的目錄結構如圖12所示:
圖12 Test.groovy和test.groovy目錄結構
在groovy中,系統自帶會加載當前目錄/子目錄下的xxx.groovy文件。因此,當執行groovy test.groovy的時候,test.groovy import的Test類能被自動搜索並加載到。
2. 腳本究竟是什麼
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文件夾下
圖13是test.groovy腳本轉換獲得的java class。用jd-gui反編譯它的代碼:
圖13 groovy腳本反編譯獲得的Java類源碼
圖13中:
test.groovy被轉換成了一個test類,它從script派生。
每個腳本都會生成一個static main函數。這樣,當咱們groovytest.groovy的時候,其實就是用java去執行這個main函數
腳本中的全部代碼都會放到run函數中。好比,println 'Groovy world',這句代碼其實是包含在run函數裏的。
若是腳本中定義了函數,則函數會被定義在test類中。
groovyc是一個比較好的命令,讀者要掌握它的用法。而後利用jd-gui來查看對應class的Java源碼。
3. 腳本中的變量和做用域
前面說了,xxx.groovy只要不是和Java那樣的class,那麼它就是一個腳本。並且腳本的代碼其實都會被放到run函數中去執行。那麼,在Groovy的腳本中,很重要的一點就是腳本中定義的變量和它的做用域。舉例:
def x = 1 <==注意,這個x有def(或者指明類型,好比 int x = 1)
def printx(){
println x
}
printx() <==報錯,說x找不到
爲何?繼續來看反編譯後的class文件。
圖14 反編譯後的test.class文件
圖14中:
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源碼又變成什麼樣了呢?
圖15 進化版的test.groovy
圖15中,x也沒有被定義成test的成員函數,而是在run的執行過程當中,將x做爲一個屬性添加到test實例對象中了。而後在printx中,先獲取這個屬性。
注意,Groovy的文檔說 x = 1這種定義將使得x變成test的成員變量,但從反編譯狀況看,這是不對得.....
雖然printx能夠訪問x變量了,可是假若有其餘腳本卻沒法訪問x變量。由於它不是test的成員變量。
好比,我在測試目錄下建立一個新的名爲test1.groovy。這個test1將訪問test.groovy中定義的printx函數:
圖16 test1.groovy使用test.groovy中的函數
這種方法使得咱們能夠將代碼分紅模塊來編寫,好比將公共的功能放到test.groovy中,而後使用公共功能的代碼放到test1.groovy中。
執行groovy test1.groovy,報錯。說x找不到。這是由於x是在test的run函數動態加進去的。怎麼辦?
import groovy.transform.Field; //必需要先import
@Field x = 1 <==在x前面加上@Field標註,這樣,x就不折不扣是test的成員變量了。
查看編譯後的test.class文件,獲得:
圖17 x如今是test類的成員變量了
這個時候,test.groovy中的x就成了test類的成員函數了。如此,咱們能夠在script中定義那些須要輸出給外部腳本或類使用的變量了!
3.5.2 文件I/O操做
本節介紹下Groovy的文件I/O操做。直接來看例子吧,雖然比Java看起來簡單,但要理解起來其實比較難。尤爲是當你要本身查SDK並編寫代碼的時候。
總體說來,Groovy的I/O操做是在原有Java I/O操做上進行了更爲簡單方便的封裝,而且使用Closure來簡化代碼編寫。主要封裝了以下一些了類:
圖18 Groovy File I/o經常使用類和SDK文檔位置
1. 讀文件
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
2. 寫文件
和讀文件差很少。再也不囉嗦。這裏給個例子,告訴你們如何copy文件。
def srcFile = new File(源文件名)
def targetFile = new File(目標文件名)
targetFile.withOutputStream{ os->
srcFile.withInputStream{ ins->
os << ins //利用OutputStream的<<操做符重載,完成從inputstream到OutputStream
//的輸出
}
}
尼瑪....關於OutputStream的<<操做符重載,查看SDK文檔後可知:
圖19 OutputStream的<<操做符重載
再一次向極致簡單致敬。可是,SDK恐怕是離不開手了...
3.5.3 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>
l 如今來看怎麼玩轉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轉換成整數
author.@id.toInteger() == 4
好了。GPath就說到這。再看個例子。我在使用Gradle的時候有個需求,就是獲取AndroidManifest.xml版本號(versionName)。有了GPath,一行代碼搞定,請看:
def androidManifest = newXmlSlurper().parse("AndroidManifest.xml")
println androidManifest['@android:versionName']
或者
println androidManifest.@'android:versionName'
3.6 更多
做爲一門語言,Groovy是複雜的,是須要深刻學習和鑽研的。一本厚書甚至都沒法描述Groovy的方方面面。
Anyway,從使用角度看,尤爲是又限定在Gradle這個領域內,能用到的都是Groovy中一些簡單的知識。
4、Gradle介紹
如今正式進入Gradle。Gradle是一個工具,同時它也是一個編程框架。前面也提到過,使用這個工具能夠完成app的編譯打包等工做。固然你也能夠用它幹其餘的事情。
Gradle是什麼?學習它到什麼地步就能夠了?
----------------------------------------------------------------------------------------------------------
=====>看待問題的時候,所站的角度很是重要。
-->當你把Gradle當工具看的時候,咱們只想着如何用好它。會寫、寫好配置腳本就OK
-->當你把它當作編程框架看的時候,你可能須要學習不少更深刻的內容。
另外,今天咱們把它當工具看,明天由於需求發生變化,咱們可能又得把它當編程框架看。
----------------------------------------------------------------------------------------------------------
4.1 Gradle開發環境部署
Gradle的官網:http://gradle.org/
文檔位置:https://docs.gradle.org/current/release-notes。其中的UserGuide和DSL Reference很關鍵。User Guide就是介紹Gradle的一本書,而DSLReference是Gradle API的說明。
以Ubuntu爲例,下載Gradle:http://gradle.org/gradle-download/ 選擇Completedistribution和Binary only distribution都行。而後解壓到指定目錄。
最後,設置~/.bashrc,把Gradle加到PATH裏,如圖20所示:
圖20 配置Gradle到bashrc
執行source ~/.bashrc,初始化環境。
執行gradle --version,若是成功運行就OK了。
注意,爲何說Gradle是一個編程框架?來看它提供的API文檔:
https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html
圖21 Project接口說明
原來,咱們編寫所謂的編譯腳本,其實就是玩Gradle的API....因此它從更底層意義上看,是一個編程框架!
既然是編程框架,我在講解Gradle的時候,儘可能會從API的角度來介紹。有些讀者確定會不耐煩,爲嘛這麼費事?
從我我的的經從來看:由於我從網上學習到的資料來看,幾乎全是從腳本的角度來介紹Gradle,結果學習一通下來,只記住參數怎麼配置,殊不知道它們都是函數調用,都是嚴格對應相關API的。
而從API角度來看待Gradle的話,有了SDK文檔,你就能夠編程。編程是靠記住一行行代碼來實現的嗎?不是,是在你掌握大致流程,而後根據SDK+API來完成的!
其實,Gradle本身的User Guide也明確說了:
Buildscripts are code
4.2 基本組件
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來定義和執行的。
4.2.1 一個重要的例子
下面咱們來看一個實際的例子。這個例子很是有表明意義。圖22是一個名爲posdevice的目錄。這個目錄裏包含3個Android Library工程,2個Android APP工程。
圖22 重要例子
在圖22的例子中:
l CPosDeviceSdk、CPosSystemSdk、CPosSystemSdkxxxImpl是Android Library。其中,CPosSystemSdkxxxImpl依賴CPosSystemSdk
l CPosDeviceServerApk和CPosSdkDemo是Android APP。這些App和SDK有依賴關係。CPosDeviceServerApk依賴CPosDeviceSdk,而CPosSdkDemo依賴全部的Sdk Library。
請回答問題,在上面這個例子中,有多少個Project?
請回答問題,在上面這個例子中,有多少個Project?
請回答問題,在上面這個例子中,有多少個Project?
答案是:每個Library和每個App都是單獨的Project。根據Gradle的要求,每個Project在其根目錄下都須要有一個build.gradle。build.gradle文件就是該Project的編譯腳本,相似於Makefile。
看起來好像很簡單,可是請注意:posdevice雖然包含5個獨立的Project,可是要獨立編譯他們的話,得:
l cd 某個Project的目錄。好比 cd CPosDeviceSdk
l 而後執行 gradle xxxx(xxx是任務的名字。對Android來講,assemble這個Task會生成最終的產物,因此gradleassemble)
這很麻煩啊,有10個獨立Project,就得重複執行10次這樣的命令。更有甚者,所謂的獨立Project其實有依賴關係的。好比咱們這個例子。
那麼,我想在posdevice目錄下,直接執行gradle assemble,是否能把這5個Project的東西都編譯出來呢?
答案天然是能夠。在Gradle中,這叫Multi-Projects Build。把posdevice改形成支持Gradle的Multi-Projects Build很容易,須要:
l 在posdevice下也添加一個build.gradle。這個build.gradle通常幹得活是:配置其餘子Project的。好比爲子Project添加一些屬性。這個build.gradle有沒有都無所屬。
l 在posdevice下添加一個名爲settings.gradle。這個文件很重要,名字必須是settings.gradle。它裏邊用來告訴Gradle,這個multiprojects包含多少個子Project。
來看settings.gradle的內容,最關鍵的內容就是告訴Gradle這個multiprojects包含哪些子projects:
[settings.gradle]
//經過include函數,將子Project的名字(其文件夾名)包含進來
include 'CPosSystemSdk' ,'CPosDeviceSdk' ,
'CPosSdkDemo','CPosDeviceServerApk','CPosSystemSdkWizarPosImpl'
強烈建議:
若是你確實只有一個Project須要編譯,我也建議你在目錄下添加一個settings.gradle。咱們團隊內部的全部單個Project都已經改爲支持Multiple-Project Build了。改得方法就是添加settings.gradle,而後include對應的project名字。
另外,settings.gradle除了能夠include外,還能夠設置一些函數。這些函數會在gradle構建整個工程任務的時候執行,因此,能夠在settings作一些初始化的工做。好比:個人settings.gradle的內容:
//定義一個名爲initMinshengGradleEnvironment的函數。該函數內部完成一些初始化操做
//好比建立特定的目錄,設置特定的參數等
def initMinshengGradleEnvironment(){
println"initialize Minsheng Gradle Environment ....."
......//幹一些special的私活....
println"initialize Minsheng Gradle Environment completes..."
}
//settings.gradle加載的時候,會執行initMinshengGradleEnvironment
initMinshengGradleEnvironment()
//include也是一個函數:
include 'CPosSystemSdk' , 'CPosDeviceSdk' ,
'CPosSdkDemo','CPosDeviceServerApk','CPosSystemSdkWizarPosImpl'
4.2.2 gradle命令介紹
1. gradle projects查看工程信息
到目前爲止,咱們瞭解了Gradle什麼呢?
l 每個Project都必須設置一個build.gradle文件。至於其內容,咱們留到後面再說。
l 對於multi-projects build,須要在根目錄下也放一個build.gradle,和一個settings.gradle。
l 一個Project是由若干tasks來組成的,當gradlexxx的時候,其實是要求gradle執行xxx任務。這個任務就能完成具體的工做。
l 固然,具體的工做和不一樣的插件有關係。編譯Java要使用Java插件,編譯Android APP須要使用Android APP插件。這些咱們都留待後續討論
gradle提供一些方便命令來查看和Project,Task相關的信息。好比在posdevice中,我想看這個multi projects到底包含多少個子Project:
執行gradle projects,獲得圖23:
圖23 gradle projects
你看,multi projects的狀況下,posdevice這個目錄對應的build.gradle叫Root
Project,它包含5個子Project。
若是你修改settings.gradle,使得include只有一個參數,則gradle projects的子project也會變少,好比圖24:
圖24 修改settings.gradle,使得只包含CPosSystemSdk工程
2. gradle tasks查看任務信息
查看了Project信息,這個還比較簡單,直接看settings.gradle也知道。那麼Project包含哪些Task信息,怎麼看呢?圖23,24中最後的輸出也告訴你了,想看某個Project包含哪些Task信息,只要執行:
gradleproject-path:tasks 就行。注意,project-path是目錄名,後面必須跟冒號。
對於Multi-project,在根目錄中,須要指定你想看哪一個poject的任務。不過你要是已經cd到某個Project的目錄了,則不需指定Project-path。
來看圖25:
圖25 gradle CPosSystemSdk:tasks
圖25是gradleCPosSystemSdk:tasks的結果。
cd CPossystemSdk
gradle tasks 獲得一樣的結果
CPosSystemSdk是一個Android Library工程,Android Library對應的插件定義了好多Task。每種插件定義的Task都不盡相同,這就是所謂的Domain Specific,須要咱們對相關領域有比較多的瞭解。
這些都是後話,咱們之後會詳細介紹。
3. gradle task-name執行任務
圖25中列出了好多任務,這時候就能夠經過 gradle 任務名來執行某個任務。這和make xxx很像。好比:
l gradle clean是執行清理任務,和make clean相似。
l gradle properites用來查看全部屬性信息。
gradle tasks會列出每一個任務的描述,經過描述,咱們大概能知道這些任務是幹什麼的.....。而後gradletask-name執行它就好。
這裏要強調一點:Task和Task之間每每是有關係的,這就是所謂的依賴關係。好比,assemble task就依賴其餘task先執行,assemble才能完成最終的輸出。
依賴關係對咱們使用gradle有什麼意義呢?
若是知道Task之間的依賴關係,那麼開發者就能夠添加一些定製化的Task。好比我爲assemble添加一個SpecialTest任務,並指定assemble依賴於SpecialTest。當assemble執行的時候,就會先處理完它依賴的task。天然,SpecialTest就會獲得執行了...
你們先了解這麼多,等後面介紹如何寫gradle腳本的時候,這就是調用幾個函數的事情,Nothing Special!
4.3 Gradle工做流程
Gradle的工做流程其實蠻簡單,用一個圖26來表達:
圖26 Gradle工做流程
圖26告訴咱們,Gradle工做包含三個階段:
l 首先是初始化階段。對咱們前面的multi-project build而言,就是執行settings.gradle
l Initiliazation phase的下一個階段是Configration階段。
l Configration階段的目標是解析每一個project中的build.gradle。好比multi-project build例子中,解析每一個子目錄中的build.gradle。在這兩個階段之間,咱們能夠加一些定製化的Hook。這固然是經過API來添加的。
l Configuration階段完了後,整個build的project以及內部的Task關係就肯定了。恩?前面說過,一個Project包含不少Task,每一個Task之間有依賴關係。Configuration會創建一個有向圖來描述Task之間的依賴關係。因此,咱們能夠添加一個HOOK,即當Task關係圖創建好後,執行一些操做。
l 最後一個階段就是執行任務了。固然,任務執行完後,咱們還能夠加Hook。
下面展現一下我按圖26爲posdevice項目添加的Hook,它的執行結果:
圖26 加了Hook後的執行結果
我在:
l settings.gradle加了一個輸出。
l 在posdevice的build.gradle加了圖25中的beforeProject函數。
l 在CPosSystemSdk加了taskGraph whenReady函數和buidFinished函數。
好了,Hook的代碼怎麼寫,估計你很好奇,並且確定會埋汰,搞毛這麼就還沒告訴我怎麼寫Gradle。立刻了!
最後,關於Gradle的工做流程,你只要記住:
l Gradle有一個初始化流程,這個時候settings.gradle會執行。
l 在配置階段,每一個Project都會被解析,其內部的任務也會被添加到一個有向圖裏,用於解決執行過程當中的依賴關係。
l 而後纔是執行階段。你在gradle xxx中指定什麼任務,gradle就會將這個xxx任務鏈上的全部任務所有按依賴順序執行一遍!
下面來告訴你怎麼寫代碼!
4.4 Gradle編程模型及API實例詳解
但願你在進入此節以前,必定花時間把前面內容看一遍!!!
https://docs.gradle.org/current/dsl/ <==這個文檔很重要
Gradle基於Groovy,Groovy又基於Java。因此,Gradle執行的時候和Groovy同樣,會把腳本轉換成Java對象。Gradle主要有三種對象,這三種對象和三種不一樣的腳本文件對應,在gradle執行的時候,會將腳本轉換成對應的對端:
l Gradle對象:當咱們執行gradle xxx或者什麼的時候,gradle會從默認的配置腳本中構造出一個Gradle對象。在整個執行過程當中,只有這麼一個對象。Gradle對象的數據類型就是Gradle。咱們通常不多去定製這個默認的配置腳本。
l Project對象:每個build.gradle會轉換成一個Project對象。
l Settings對象:顯然,每個settings.gradle都會轉換成一個Settings對象。
注意,對於其餘gradle文件,除非定義了class,不然會轉換成一個實現了Script接口的對象。這一點和3.5節中Groovy的腳本類類似
當咱們執行gradle的時候,gradle首先是按順序解析各個gradle文件。這裏邊就有所所謂的生命週期的問題,即先解析誰,後解析誰。圖27是Gradle文檔中對生命週期的介紹:結合上一節的內容,相信你們都能看明白了。如今只須要看紅框裏的內容:
圖27 Gradle對LifeCycle的介紹
4.4.1 Gradle對象
咱們先來看Gradle對象,它有哪些屬性呢?如圖28所示:
圖28 Gradle的屬性
我在posdevice build.gradle中和settings.gradle中分別加了以下輸出:
//在settings.gradle中,則輸出"In settings,gradle id is"
println "In posdevice, gradle id is " +gradle.hashCode()
println "Home Dir:" + gradle.gradleHomeDir
println "User Home Dir:" + gradle.gradleUserHomeDir
println "Parent: " + gradle.parent
獲得結果如圖29所示:
圖29 gradle示例
l 你看,在settings.gradle和posdevice build.gradle中,咱們獲得的gradle實例對象的hashCode是同樣的(都是791279786)。
l HomeDir是我在哪一個目錄存儲的gradle可執行程序。
l User Home Dir:是gradle本身設置的目錄,裏邊存儲了一些配置文件,以及編譯過程當中的緩存文件,生成的類文件,編譯中依賴的插件等等。
Gradle的函數接口在文檔中也有。
4.4.2 Project對象
每個build.gradle文件都會轉換成一個Project對象。在Gradle術語中,Project對象對應的是BuildScript。
Project包含若干Tasks。另外,因爲Project對應具體的工程,因此須要爲Project加載所須要的插件,好比爲Java工程加載Java插件。其實,一個Project包含多少Task每每是插件決定的。
因此,在Project中,咱們要:
l 加載插件。
l 不一樣插件有不一樣的行話,即不一樣的配置。咱們要在Project中配置好,這樣插件就知道從哪裏讀取源文件等
l 設置屬性。
1. 加載插件
Project的API位於https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html。加載插件是調用它的apply函數.apply實際上是Project實現的PluginAware接口定義的:
圖30 apply函數
來看代碼:
[apply函數的用法]
apply是一個函數,此處調用的是圖30中最後一個apply函數。注意,Groovy支持
函數調用的時候經過 參數名1:參數值2,參數名2:參數值2 的方式來傳遞參數
apply plugin: 'com.android.library' <==若是是編譯Library,則加載此插件
apply plugin: 'com.android.application' <==若是是編譯Android APP,則加載此插件
除了加載二進制的插件(上面的插件其實都是下載了對應的jar包,這也是一般意義上咱們所理解的插件),還能夠加載一個gradle文件。爲何要加載gradle文件呢?
其實這和代碼的模塊劃分有關。通常而言,我會把一些通用的函數放到一個名叫utils.gradle文件裏。而後在其餘工程的build.gradle來加載這個utils.gradle。這樣,經過一些處理,我就能夠調用utils.gradle中定義的函數了。
加載utils.gradle插件的代碼以下:
utils.gradle是我封裝的一個gradle腳本,裏邊定義了一些方便函數,好比讀取AndroidManifest.xml中
的versionName,或者是copy jar包/APK包到指定的目錄
apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
也是使用apply的最後一個函數。那麼,apply最後一個函數到底支持哪些參數呢?仍是得看圖31中的API說明:
圖31 apply API說明
我這裏竭盡全力的列出API圖片,就是但願你們在寫腳本的時候,碰到不會的,必定要去查看API文檔!
2. 設置屬性
若是是單個腳本,則不須要考慮屬性的跨腳本傳播,可是Gradle每每包含不止一個build.gradle文件,好比我設置的utils.gradle,settings.gradle。如何在多個腳本中設置屬性呢?
Gradle提供了一種名爲extra property的方法。extra property是額外屬性的意思,在第一次定義該屬性的時候須要經過ext前綴來標示它是一個額外的屬性。定義好以後,後面的存取就不須要ext前綴了。ext屬性支持Project和Gradle對象。即Project和Gradle對象均可以設置ext屬性
舉個例子:
我在settings.gradle中想爲Gradle對象設置一些外置屬性,因此在initMinshengGradleEnvironment函數中
def initMinshengGradleEnvironment(){
//屬性值從local.properites中讀取
Propertiesproperties = new Properties()
FilepropertyFile = new File(rootDir.getAbsolutePath() +"/local.properties")
properties.load(propertyFile.newDataInputStream())
//gradle就是gradle對象。它默認是Settings和Project的成員變量。可直接獲取
//ext前綴,代表操做的是外置屬性。api是一個新的屬性名。前面說過,只在
//第一次定義或者設置它的時候須要ext前綴
gradle.ext.api =properties.getProperty('sdk.api')
println gradle.api //再次存取api的時候,就不須要ext前綴了
......
}
再來一個例子強化一下:
我在utils.gradle中定義了一些函數,而後想在其餘build.gradle中調用這些函數。那該怎麼作呢?
[utils.gradle]
//utils.gradle中定義了一個獲取AndroidManifests.xmlversionName的函數
def getVersionNameAdvanced(){
下面這行代碼中的project是誰?
defxmlFile = project.file("AndroidManifest.xml")
defrootManifest = new XmlSlurper().parse(xmlFile)
returnrootManifest['@android:versionName']
}
//如今,想把這個API輸出到各個Project。因爲這個utils.gradle會被每個Project Apply,因此
//我能夠把getVersionNameAdvanced定義成一個closure,而後賦值到一個外部屬性
下面的ext是誰的ext?
ext{ //此段花括號中代碼是閉包
//除了ext.xxx=value這種定義方法外,還可使用ext{}這種書寫方法。
//ext{}不是ext(Closure)對應的函數調用。可是ext{}中的{}確實是閉包。
getVersionNameAdvanced = this.&getVersionNameAdvanced
}
上面代碼中有兩個問題:
project是誰?
ext是誰的ext?
上面兩個問題比較關鍵,我也是花了很長時間才搞清楚。這兩個問題歸結到一塊兒,其實就是:
加載utils.gradle的Project對象和utils.gradle自己所表明的Script對象到底有什麼關係?
咱們在Groovy中也講過怎麼在一個Script中import另一個Script中定義的類或者函數(見3.5 腳本類、文件I/O和XML操做一節)。在Gradle中,這一塊的處理比Groovy要複雜,具體怎麼搞我還沒徹底弄清楚,可是Project和utils.gradle對於的Script的對象的關係是:
l 當一個Project apply一個gradle文件的時候,這個gradle文件會轉換成一個Script對象。這個,相信你們都已經知道了。
l Script中有一個delegate對象,這個delegate默認是加載(即調用apply)它的Project對象。可是,在apply函數中,有一個from參數,還有一個to參數(參考圖31)。經過to參數,你能夠把delegate對象指定爲別的東西。
l delegate對象是什麼意思?當你在Script中操做一些不是Script本身定義的變量,或者函數時候,gradle會到Script的delegate對象去找,看看有沒有定義這些變量或函數。
如今你知道問題1,2和答案了:
問題1:project就是加載utils.gradle的project。因爲posdevice有5個project,因此utils.gradle會分別加載到5個project中。因此,getVersionNameAdvanced纔不用區分究竟是哪一個project。反正一個project有一個utils.gradle對應的Script。
問題2:ext:天然就是Project對應的ext了。此處爲Project添加了一些closure。那麼,在Project中就能夠調用getVersionNameAdvanced函數了
好比:我在posdevice每一個build.gradle中都有以下的代碼:
tasks.getByName("assemble"){
it.doLast{
println "$project.name: After assemble, jar libs are copied tolocal repository"
copyOutput(true) //copyOutput是utils.gradle輸出的closure
}
}
經過這種方式,我將一些經常使用的函數放到utils.gradle中,而後爲加載它的Project設置ext屬性。最後,Project中就能夠調用這種賦值函數了!
注意:此處我研究的還不是很深,並且我我的感受:
1 在Java和Groovy中:咱們會把經常使用的函數放到一個輔助類和公共類中,而後在別的地方import並調用它們。
2 可是在Gradle,更正規的方法是在xxx.gradle中定義插件。而後經過添加Task的方式來完成工做。gradle的user guide有詳細介紹如何實現本身的插件。
3. Task介紹
Task是Gradle中的一種數據類型,它表明了一些要執行或者要乾的工做。不一樣的插件能夠添加不一樣的Task。每個Task都須要和一個Project關聯。
Task的API文檔位於https://docs.gradle.org/current/dsl/org.gradle.api.Task.html。關於Task,我這裏簡單介紹下build.gradle中怎麼寫它,以及Task中一些常見的類型
關於Task。來看下面的例子:
[build.gradle]
/
/Task是和Project關聯的,因此,咱們要利用Project的task函數來建立一個Task
task myTask <==myTask是新建Task的名字
task myTask { configure closure }
task myType << { task action } <==注意,<<符號是doLast的縮寫
task myTask(type: SomeType)
task myTask(type: SomeType) { configure closure }
上述代碼中都用了Project的一個函數,名爲task,注意:
l 一個Task包含若干Action。因此,Task有doFirst和doLast兩個函數,用於添加須要最早執行的Action和須要和須要最後執行的Action。Action就是一個閉包。
l Task建立的時候能夠指定Type,經過type:名字表達。這是什麼意思呢?其實就是告訴Gradle,這個新建的Task對象會從哪一個基類Task派生。好比,Gradle自己提供了一些通用的Task,最多見的有Copy 任務。Copy是Gradle中的一個類。當咱們:task myTask(type:Copy)的時候,建立的Task就是一個Copy Task。
l 當咱們使用 taskmyTask{ xxx}的時候。花括號是一個closure。這會致使gradle在建立這個Task以後,返回給用戶以前,會先執行closure的內容。
l 當咱們使用taskmyTask << {xxx}的時候,咱們建立了一個Task對象,同時把closure作爲一個action加到這個Task的action隊列中,而且告訴它「最後才執行這個closure」(注意,<<符號是doLast的表明)。
圖32是Project中關於task函數說明:
圖32 Project中task函數
陸陸續續講了這麼些內容,我本身感受都有點煩了。是得,Gradle用一整本書來說都嫌不夠呢。
anyway,到目前爲止,我介紹的都是一些比較基礎的東西,還不是特別多。可是後續例子該涉及到的知識點都有了。下面咱們直接上例子。這裏有兩個例子:
l posdevice的例子
l 另一個是單個project的例子
4.4.3 posdevice實例
如今正是開始經過例子來介紹怎麼玩gradle。這裏要特別強調一點,根據Gradle的哲學。gradle文件中包含一些所謂的Script Block(姑且這麼稱它)。ScriptBlock做用是讓咱們來配置相關的信息。不一樣的SB有不一樣的須要配置的東西。這也是我最先說的行話。好比,源碼對應的SB,就須要咱們配置源碼在哪一個文件夾裏。關於SB,咱們後面將見識到!
posdevice是一個multi project。下面包含5個Project。對於這種Project,請你們回想下咱們該建立哪些文件?
l settings.gradle是必不可少的
l 根目錄下的build.gradle。這個咱們沒講過,由於posdevice的根目錄自己不包含代碼,而是包含其餘5個子project。
l 每一個project目錄下包含對於的build.gradle
l 另外,我把經常使用的函數封裝到一個名爲utils.gradle的腳本里了。
立刻一個一個來看它們。
1. utils.gradle
utils.gradle是我本身加的,爲咱們團隊特地加了一些常見函數。主要代碼以下:
[utils.gradle]
import groovy.util.XmlSlurper //解析XML時候要引入這個groovy的package
def copyFile(String srcFile,dstFile){
......//拷貝文件函數,用於將最後的生成物拷貝到指定的目錄
}
def rmFile(String targetFile){
.....//刪除指定目錄中的文件
}
def cleanOutput(boolean bJar = true){
....//clean的時候清理
}
def copyOutput(boolean bJar = true){
....//copyOutput內部會調用copyFile完成一次build的產出物拷貝
}
def getVersionNameAdvanced(){//老朋友
defxmlFile = project.file("AndroidManifest.xml")
defrootManifest = new XmlSlurper().parse(xmlFile)
returnrootManifest['@android:versionName']
}
//對於android library編譯,我會disable全部的debug編譯任務
def disableDebugBuild(){
//project.tasks包含了全部的tasks,下面的findAll是尋找那些名字中帶debug的Task。
//返回值保存到targetTasks容器中
def targetTasks = project.tasks.findAll{task ->
task.name.contains("Debug")
}
//對知足條件的task,設置它爲disable。如此這般,這個Task就不會被執行
targetTasks.each{
println"disable debug task :${it.name}"
it.setEnabled false
}
}
//將函數設置爲extra屬性中去,這樣,加載utils.gradle的Project就能調用此文件中定義的函數了
ext{
copyFile= this.©File
rmFile =this.&rmFile
cleanOutput = this.&cleanOutput
copyOutput = this.©Output
getVersionNameAdvanced = this.&getVersionNameAdvanced
disableDebugBuild = this.&disableDebugBuild
}
圖33展現了被disable的Debug任務的部分信息:
圖33 disable的Debug Task信息
2. settings.gradle
這個文件中咱們該幹什麼?調用include把須要包含的子Project加進來。代碼以下:
[settings.gradle]
/*咱們團隊內部創建的編譯環境初始化函數
這個函數的目的是
1 解析一個名爲local.properties的文件,讀取AndroidSDK和NDK的路徑
2 獲取最終產出物目錄的路徑。這樣,編譯完的apk或者jar包將拷貝到這個最終產出物目錄中
3 獲取Android SDK指定編譯的版本
*/
def initMinshengGradleEnvironment(){
println"initialize Minsheng Gradle Environment ....."
Properties properties = new Properties()
//local.properites也放在posdevice目錄下
FilepropertyFile = new File(rootDir.getAbsolutePath()+ "/local.properties")
properties.load(propertyFile.newDataInputStream())
/*
根據Project、Gradle生命週期的介紹,settings對象的建立位於具體Project建立以前
而Gradle底對象已經建立好了。因此,咱們把local.properties的信息讀出來後,經過
extra屬性的方式設置到gradle對象中
而具體Project在執行的時候,就能夠直接從gradle對象中獲得這些屬性了!
*/
gradle.ext.api =properties.getProperty('sdk.api')
gradle.ext.sdkDir =properties.getProperty('sdk.dir')
gradle.ext.ndkDir =properties.getProperty('ndk.dir')
gradle.ext.localDir =properties.getProperty('local.dir')
//指定debugkeystore文件的位置,debug版apk簽名的時候會用到
gradle.ext.debugKeystore= properties.getProperty('debug.keystore')
......
println"initialize Minsheng Gradle Environment completes..."
}
//初始化
initMinshengGradleEnvironment()
//添加子Project信息
include 'CPosSystemSdk' , 'CPosDeviceSdk' ,'CPosSdkDemo','CPosDeviceServerApk', 'CPosSystemSdkWizarPosImpl'
注意,對於Android來講,local.properties文件是必須的,它的內容以下:
[local.properties]
local.dir=/home/innost/workspace/minsheng-flat-dir/
//注意,根據Android Gradle的規範,只有下面兩個屬性是必須的,其他都是我本身加的
sdk.dir=/home/innost/workspace/android-aosp-sdk/
ndk.dir=/home/innost/workspace/android-aosp-ndk/
debug.keystore=/home/innost/workspace/tools/mykeystore.jks
sdk.api=android-19
再次強調,sdk.dir和ndk.dir是Android Gradle必需要指定的,其餘都是我本身加的屬性。固然。不編譯ndk,就不須要ndk.dir屬性了。
3. posdevicebuild.gradle
做爲multi-project根目錄,通常狀況下,它的build.gradle是作一些全局配置。來看個人build.gradle
[posdevicebuild.gradle]
//下面這個subprojects{}就是一個Script Block
subprojects {
println"Configure for $project.name" //遍歷子Project,project變量對應每一個子Project
buildscript { //這也是一個SB
repositories {//repositories是一個SB
///jcenter是一個函數,表示編譯過程當中依賴的庫,所需的插件能夠在jcenter倉庫中
//下載。
jcenter()
}
dependencies { //SB
//dependencies表示咱們編譯的時候,依賴android開發的gradle插件。插件對應的
//class path是com.android.tools.build。版本是1.2.3
classpath'com.android.tools.build:gradle:1.2.3'
}
//爲每一個子Project加載utils.gradle 。固然,這句話能夠放到buildscript花括號以後
applyfrom: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
}//buildscript結束
}
感受解釋得好蒼白,SB在Gradle的API文檔中也是有的。先來看Gradle定義了哪些SB。如圖34所示:
圖34 SB的類型
你看,subprojects、dependencies、repositories都是SB。那麼SB究竟是什麼?它是怎麼完成所謂配置的呢?
仔細研究,你會發現SB後面都須要跟一個花括號,而花括號,恩,咱們感受裏邊可能一個Closure。因爲圖34說,這些SB的Description都有「Configure xxx for this project」,因此極可能subprojects是一個函數,而後其參數是一個Closure。是這樣的嗎?
Absolutely right。只是這些函數你直接到Project API裏不必定能找全。不過要是你好奇心重,不妨到https://docs.gradle.org/current/javadoc/,選擇Index這一項,而後ctrl+f,輸入圖34中任何一個Block,你都會找到對應的函數。好比我替你找了幾個API,如圖35所示:
圖35 SB對應的函數
特別提示:當你下次看到一個不認識的SB的時候,就去看API吧。
下面來解釋代碼中的各個SB:
l subprojects:它會遍歷posdevice中的每一個子Project。在它的Closure中,默認參數是子Project對應的Project對象。因爲其餘SB都在subprojects花括號中,因此至關於對每一個Project都配置了一些信息。
l buildscript:它的closure是在一個類型爲ScriptHandler的對象上執行的。主意用來所依賴的classpath等信息。經過查看ScriptHandler API可知,在buildscript SB中,你能夠調用ScriptHandler提供的repositories(Closure )、dependencies(Closure)函數。這也是爲何repositories和dependencies兩個SB爲何要放在buildscript的花括號中的緣由。明白了?這就是所謂的行話,得知道規矩。不知道規矩你就亂了。記不住規矩,又不知道查SDK,那麼就完全抓瞎,只能到網上處處找答案了!
l 關於repositories和dependencies,你們直接看API吧。後面碰到了具體代碼咱們再來介紹
4. CPosDeviceSdkbuild.gradle
CPosDeviceSdk是一個Android Library。按Google的想法,Android Library編譯出來的應該是一個AAR文件。可是個人項目有些特殊,我須要發佈CPosDeviceSdk.jar包給其餘人使用。jar在編譯過程當中會生成,可是它不屬於Android Library的標準輸出。在這種狀況下,我須要在編譯完成後,主動copy jar包到我本身設計的產出物目錄中。
//Library工程必須加載此插件。注意,加載了Android插件就不要加載Java插件了。由於Android
//插件自己就是拓展了Java插件
apply plugin: 'com.android.library'
//android的編譯,增長了一種新類型的ScriptBlock-->android
android {
//你看,我在local.properties中設置的API版本號,就能夠一次設置,多個Project使用了
//藉助我特地設計的gradle.ext.api屬性
compileSdkVersion =gradle.api //這兩個紅色的參數必須設置
buildToolsVersion = "22.0.1"
sourceSets{ //配置源碼路徑。這個sourceSets是Java插件引入的
main{ //main:Android也用了
manifest.srcFile 'AndroidManifest.xml' //這是一個函數,設置manifest.srcFile
aidl.srcDirs=['src'] //設置aidl文件的目錄
java.srcDirs=['src'] //設置java文件的目錄
}
}
dependencies { //配置依賴關係
//compile表示編譯和運行時候須要的jar包,fileTree是一個函數,
//dir:'libs',表示搜索目錄的名稱是libs。include:['*.jar'],表示搜索目錄下知足*.jar名字的jar
//包都做爲依賴jar文件
compile fileTree(dir: 'libs', include: ['*.jar'])
}
} //android SB配置完了
//clean是一個Task的名字,這個Task好像是Java插件(這裏是Android插件)引入的。
//dependsOn是一個函數,下面這句話的意思是 clean任務依賴cposCleanTask任務。因此
//當你gradle clean以執行clean Task的時候,cposCleanTask也會執行
clean.dependsOn 'cposCleanTask'
//建立一個Task,
task cposCleanTask() <<{
cleanOutput(true) //cleanOutput是utils.gradle中經過extra屬性設置的Closure
}
//前面說了,我要把jar包拷貝到指定的目錄。對於Android編譯,我通常指定gradle assemble
//它默認編譯debug和release兩種輸出。因此,下面這個段代碼表示:
//tasks表明一個Projects中的全部Task,是一個容器。getByName表示找到指定名稱的任務。
//我這裏要找的assemble任務,而後我經過doLast添加了一個Action。這個Action就是copy
//產出物到我設置的目標目錄中去
tasks.getByName("assemble"){
it.doLast{
println "$project.name: After assemble, jar libs are copied tolocal repository"
copyOutput(true)
}
}
/*
由於個人項目只提供最終的release編譯出來的Jar包給其餘人,因此不須要編譯debug版的東西
當Project建立完全部任務的有向圖後,我經過afterEvaluate函數設置一個回調Closure。在這個回調
Closure裏,我disable了全部Debug的Task
*/
project.afterEvaluate{
disableDebugBuild()
}
Android本身定義了好多ScriptBlock。Android定義的DSL參考文檔在
https://developer.android.com/tools/building/plugin-for-gradle.html下載。注意,它竟然沒有提供在線文檔。
圖36所示爲Android的DSL參考信息。
圖36 Android Gradle DSL參考示意
圖37爲buildToolsVersion和compileSdkVersion的說明:
圖37 buildToolsVersion和compileSdkVersion的說明
從圖37可知,這兩個變量是必需要設置的.....
5. CPosDeviceServerApk build.gradle
再來看一個APK的build,它包含NDK的編譯,而且還要簽名。根據項目的需求,咱們只能籤debug版的,而release版的簽名得發佈unsigned包給領導簽名。另外,CPosDeviceServerAPK依賴CPosDeviceSdk。
雖然我能夠先編譯CPosDeviceSdk,獲得對應的jar包,而後設置CPosDeviceServerApk直接依賴這個jar包就好。可是我更但願CPosDeviceServerApk能直接依賴於CPosDeviceSdk這個工程。這樣,整個posdevice能夠作到這幾個Project的依賴關係是最新的。
[build.gradle]
apply plugin: 'com.android.application' //APK編譯必須加載這個插件
android {
compileSdkVersion gradle.api
buildToolsVersion "22.0.1"
sourceSets{ //差很少的設置
main{
manifest.srcFile 'AndroidManifest.xml'
//經過設置jni目錄爲空,咱們可不使用apk插件的jni編譯功能。爲何?由於聽說
//APK插件的jni功能好像不是很好使....暈菜
jni.srcDirs = []
jniLibs.srcDir 'libs'
aidl.srcDirs=['src']
java.srcDirs=['src']
res.srcDirs=['res']
}
}//main結束
signingConfigs { //設置簽名信息配置
debug { //若是咱們在local.properties設置使用特殊的keystore,則使用它
//下面這些設置,無非是函數調用....請務必閱讀API文檔
if(project.gradle.debugKeystore != null){
storeFile file("file://${project.gradle.debugKeystore}")
storePassword "android"
keyAlias "androiddebugkey"
keyPassword "android"
}
}
}//signingConfigs結束
buildTypes {
debug {
signingConfig signingConfigs.debug
jniDebuggable false
}
}//buildTypes結束
dependencies {
//compile:project函數可指定依賴multi-project中的某個子project
compile project(':CPosDeviceSdk')
compile fileTree(dir: 'libs', include: ['*.jar'])
} //dependices結束
repositories{
flatDir {//flatDir:告訴gradle,編譯中依賴的jar包存儲在dirs指定的目錄
name "minsheng-gradle-local-repository"
dirsgradle.LOCAL_JAR_OUT //LOCAL_JAR_OUT是我存放編譯出來的jar包的位置
}
}//repositories結束
}//android結束
/*
建立一個Task,類型是Exec,這代表它會執行一個命令。我這裏讓他執行ndk的
ndk-build命令,用於編譯ndk。關於Exec類型的Task,請自行腦補Gradle的API
*/
//注意此處建立task的方法,是直接{}喔,那麼它後面的tasks.withType(JavaCompile)
//設置的依賴關係,還有意義嗎?Think!若是你能想明白,gradle掌握也就差很少了
task buildNative(type: Exec, description: 'CompileJNI source via NDK') {
if(project.gradle.ndkDir == null) //看看有沒有指定ndk.dir路徑
println "CANNOT Build NDK"
else{
commandLine "/${project.gradle.ndkDir}/ndk-build",
'-C', file('jni').absolutePath,
'-j', Runtime.runtime.availableProcessors(),
'all', 'NDK_DEBUG=0'
}
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn buildNative
}
......
//對於APK,除了拷貝APK文件到指定目錄外,我還特地爲它們加上了自動版本命名的功能
tasks.getByName("assemble"){
it.doLast{
println "$project.name: After assemble, jar libs are copied tolocal repository"
project.ext.versionName = android.defaultConfig.versionName
println "\t versionName = $versionName"
copyOutput(false)
}
}
6. 結果展現
在posdevice下執行gradle assemble命令,最終的輸出文件都會拷貝到我指定的目錄,結果如圖38所示:
圖38 posdevice執行結果
圖38所示爲posdevice gradle assemble的執行結果:
l library包都編譯release版的,copy到xxx/javaLib目錄下
l apk編譯debug和release-unsigned版的,copy到apps目錄下
l 全部產出物都自動從AndroidManifest.xml中提取versionName。
4.4.4 實例2
下面這個實例也是來自一個實際的APP。這個APP對應的是一個單獨的Project。可是根據我前面的建議,我會把它改形成支持Multi-ProjectsBuild的樣子。即在工程目錄下放一個settings.build。
另外,這個app有一個特色。它有三個版本,分別是debug、release和demo。這三個版本對應的代碼都徹底同樣,可是在運行的時候須要從assets/runtime_config文件中讀取參數。參數不一樣,則運行的時候會跳轉到debug、release或者demo的邏輯上。
注意:我知道assets/runtime_config這種作法不decent,但,這是一個既有項目,咱們只能作小範圍的適配,而不是傷筋動骨改用更好的方法。另外,從將來的需求來看,暫時也沒有大改的必要。
引入gradle後,咱們該如何處理呢?
解決方法是:在編譯build、release和demo版本前,在build.gradle中自動設置runtime_config的內容。代碼以下所示:
[build.gradle]
apply plugin: 'com.android.application' //加載APP插件
//加載utils.gradle
apply from:rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
//buildscript設置android app插件的位置
buildscript {
repositories { jcenter() }
dependencies { classpath 'com.android.tools.build:gradle:1.2.3' }
}
//androidScriptBlock
android {
compileSdkVersion gradle.api
buildToolsVersion "22.0.1"
sourceSets{//源碼設置SB
main{
manifest.srcFile 'AndroidManifest.xml'
jni.srcDirs = []
jniLibs.srcDir 'libs'
aidl.srcDirs=['src']
java.srcDirs=['src']
res.srcDirs=['res']
assets.srcDirs = ['assets'] //多了一個assets目錄
}
}
signingConfigs {//簽名設置
debug { //debug對應的SB。注意
if(project.gradle.debugKeystore != null){
storeFile file("file://${project.gradle.debugKeystore}")
storePassword "android"
keyAlias "androiddebugkey"
keyPassword "android"
}
}
}
/*
最關鍵的內容來了: buildTypesScriptBlock.
buildTypes和上面的signingConfigs,當咱們在build.gradle中經過{}配置它的時候,
其背後的所表明的對象是NamedDomainObjectContainer<BuildType> 和
NamedDomainObjectContainer<SigningConfig>
注意,NamedDomainObjectContainer<BuildType/或者SigningConfig>是一種容器,
容器的元素是BuildType或者SigningConfig。咱們在debug{}要填充BuildType或者
SigningConfig所包的元素,好比storePassword就是SigningConfig類的成員。而proguardFile等
是BuildType的成員。
那麼,爲何要使用NamedDomainObjectContainer這種數據結構呢?由於往這種容器裏
添加元素能夠採用這樣的方法: 好比signingConfig爲例
signingConfig{//這是一個NamedDomainObjectContainer<SigningConfig>
test1{//新建一個名爲test1的SigningConfig元素,而後添加到容器裏
//在這個花括號中設置SigningConfig的成員變量的值
}
test2{//新建一個名爲test2的SigningConfig元素,而後添加到容器裏
//在這個花括號中設置SigningConfig的成員變量的值
}
}
在buildTypes中,Android默認爲這幾個NamedDomainObjectContainer添加了
debug和release對應的對象。若是咱們再添加別的名字的東西,那麼gradleassemble的時候
也會編譯這個名字的apk出來。好比,我添加一個名爲test的buildTypes,那麼gradle assemble
就會編譯一個xxx-test-yy.apk。在此,test就好像debug、release同樣。
*/
buildTypes{
debug{ //修改debug的signingConfig爲signingConfig.debug配置
signingConfig signingConfigs.debug
}
demo{ //demo版須要混淆
proguardFile 'proguard-project.txt'
signingConfig signingConfigs.debug
}
//release版沒有設置,因此默認沒有簽名,沒有混淆
}
......//其餘和posdevice 相似的處理。來看如何動態生成runtime_config文件
def runtime_config_file = 'assets/runtime_config'
/*
咱們在gradle解析完整個任務以後,找到對應的Task,而後在裏邊添加一個doFirst Action
這樣能確保編譯開始的時候,咱們就把runtime_config文件準備好了。
注意,必須在afterEvaluate裏邊才能作,不然gradle沒有創建完任務有向圖,你是找不到
什麼preDebugBuild之類的任務的
*/
project.afterEvaluate{
//找到preDebugBuild任務,而後添加一個Action
tasks.getByName("preDebugBuild"){
it.doFirst{
println "generate debug configuration for ${project.name}"
def configFile = new File(runtime_config_file)
configFile.withOutputStream{os->
os << I am Debug\n' //往配置文件裏寫 I am Debug
}
}
}
//找到preReleaseBuild任務
tasks.getByName("preReleaseBuild"){
it.doFirst{
println "generate release configuration for ${project.name}"
def configFile = new File(runtime_config_file)
configFile.withOutputStream{os->
os << I am release\n'
}
}
}
//找到preDemoBuild。這個任務明顯是由於咱們在buildType裏添加了一個demo的元素
//因此Android APP插件自動爲咱們生成的
tasks.getByName("preDemoBuild"){
it.doFirst{
println "generate offlinedemo configuration for${project.name}"
def configFile = new File(runtime_config_file)
configFile.withOutputStream{os->
os << I am Demo\n'
}
}
}
}
}
.....//copyOutput
最終的結果如圖39所示:
圖39 實例2的結果
幾個問題,爲何我知道有preXXXBuild這樣的任務?
答案:gradle tasks --all查看全部任務。而後,多嘗試幾回,直到成功
5、總結
到此,我我的以爲Gradle相關的內容都講完了。很難相信我僅花了1個小時不到的時間就爲實例2添加了gradle編譯支持。在一週之前,我還以爲這是個心病。回想學習gradle的一個月時間裏,走過很多彎路,求解問題的思路也和最開始不同:
l 最開始的時候,我一直把gradle當作腳本看。而後處處到網上找怎麼配置gradle。可能能編譯成功,可是徹底不知道爲何。好比NameDomainObjectContainer,爲何有debug、release。能本身加別的嗎?不知道怎麼加,沒有章法,沒有參考。出了問題只能google,找到一個解法,試一試,成功就無論。這麼搞,內心不踏實。
l 另外,對語法不熟悉,尤爲是Groovy語法,雖然看了下快速教材,但總感受一到gradle就看不懂。主要問題仍是閉包,好比Groovy那一節寫得文件拷貝的例子中的withOutputStream,還有gradle中的withType,都是些啥玩意啊?
l 因此後來下決心先把Groovy學會,主要是把本身暴露在閉包裏邊。另外,Groovy是一門語言,總得有SDK說明吧。寫了幾個例子,慢慢體會到Groovy的好處,也熟悉Groovy的語法了。
l 接着開始看Gradle。Gradle有幾本書,我看過Gradle in Action。說實話,看得很是痛苦。如今想起來,Gradle其實比較簡單,知道它的生命週期,知道它怎麼解析腳本,知道它的API,幾乎很快就能幹活。而Gradle In Action一上來就很細,並且沒有從API角度介紹。說個頗有趣的事情,書中有個相似下面的例子
task myTask << {
println 'I am myTask'
}
書中說,若是代碼沒有加<<,則這個任務在腳本initialization(也就是你不管執行什麼任務,這個任務都會被執行,I am myTask都會被輸出)的時候執行,若是加了<<,則在gradle myTask後才執行。
尼瑪我開始徹底不知道爲何,死記硬背。如今你明白了嗎????
這和咱們調用task這個函數的方式有關!若是沒有<<,則閉包在task函數返回前會執行,而若是加了<<,則變成調用myTask.doLast添加一個Action了,天然它會等到grdle myTask的時候纔會執行!
如今想起這個事情我仍是很憤怒,API都說很清楚了......並且,若是你把Gradle當作編程框架來看,對於咱們這些程序員來講,寫這幾百行代碼,那還算是事嘛?? --------------------- 做者:阿拉神農 來源:CSDN 原文:https://blog.csdn.net/Innost/article/details/48228651 版權聲明:本文爲博主原創文章,轉載請附上博文連接!