1. 程式人生 > >深入理解Android(一):Gradle詳解

深入理解Android(一):Gradle詳解

作者 鄧凡平

編者按:隨著移動裝置硬體能力的提升,Android系統開放的特質開始顯現,各種開發的奇技淫巧、黑科技不斷湧現,InfoQ特聯合《深入理解Android》系列圖書作者鄧凡平,開設深入理解Android專欄,探索Android從框架到應用開發的奧祕。

Gradle是當前非常“勁爆”的構建工具。本篇文章就是專為講解Gradle而來。介紹Gradle之前,先說點題外話。

一、題外話

說實話,我在索尼工作的時候,就見過Gradle。但是當時我一直不知道這是什麼東西。而且索尼工具組的工程師還將其和Android Studio索尼版一起推送,偶一看就更沒興趣了。為什麼那個時候如此不待見Gradle呢?因為我此前一直是做ROM開發。在這個層面上,我們用make,mm或者mmm就可以了。而且,編譯耗時對我們來說也不是啥痛點,因為用組內吊炸天的神機伺服器完整編譯索尼的image也要耗費1個小時左右。所以,那個時侯Gradle完全不是我們的菜。

現在,搞APP開發居多,編譯/打包等問題立即就成痛點了。比如:

  • 一個APP有多個版本,Release版、Debug版、Test版。甚至針對不同APP Store都有不同的版本。在以前ROM的環境下,雖然可以配置Android.mk,但是需要依賴整個Android原始碼,而且還不能完全做到滿足條件,很多事情需要手動搞。一個app如果涉及到多個開發者,手動操作必然會帶來混亂。
  • library工程我們需要編譯成jar包,然後釋出給其他開發者使用。以前是用eclipse的export,做一堆選擇。要是能自動編譯成jar包就爽了。

上述問題對絕大部分APP開發者而言都不陌生,而Gradle

作為一種很方便的的構建工具,可以非常輕鬆得解決構建過程中的各種問題。

二、閒言構建

構建,叫build也好,叫make也行。反正就是根據輸入資訊然後幹一堆事情,最後得到幾個產出物(Artifact)。

最最簡單的構建工具就是make了。make就是根據Makefile檔案中寫的規則,執行對應的命令,然後得到目標產物。

日常生活中,和構建最類似的一個場景就是做菜。輸入各種食材,然後按固定的工序,最後得到一盤菜。當然,做同樣一道菜,由於需求不同,做出來的東西也不盡相同。比如,宮保雞丁這道菜,回民要求不能放大油、口淡的要求少放鹽和各種油、辣不怕的男女漢子們可以要求多放辣子....總之,做菜包含固定的工序,但是對於不同條件或需求,需要做不同的處理。

在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,即Domain Specific Language,領域相關語言。什麼是DSL,說白了它是某個行業中的行話。還是不明白?徐克導演得《智取威虎山》中就有很典型的DSL使用描述,比如:

土匪:蘑菇,你哪路?什麼價?(什麼人?到哪裡去?)

楊子榮:哈!想啥來啥,想吃奶來了媽媽,想孃家的人,孩子他舅舅來了。(找同行)

楊子榮:拜見三爺!

土匪:天王蓋地虎!(你好大的膽!敢來氣你的祖宗?)

楊子榮:寶塔鎮河妖!(要是那樣,叫我從山上摔死,掉河裡淹死。)

土匪:野雞悶頭鑽,哪能上天王山!(你不是正牌的。)

楊子榮:地上有的是米,喂呀,有根底!(老子是正牌的,老牌的。)

Gradle中也有類似的行話,比如sourceSets代表原始檔的集合等.....太多了,記不住。以後我們都會接觸到這些行話。那麼,對使用者而言,這些行話的好處是什麼呢?這就是:

一句行話可以包含很多意思,而且在這個行當裡的人一聽就懂,不用解釋。另外,基於行話,我們甚至可以建立一個模板,使用者只要往這個模板裡填必須要填的內容,Gradle就可以非常漂亮得完成工作,得到想要的東西。

這就和現在的智慧炒菜機器似的,只要選擇菜譜,把食材準備好,剩下的事情就不用你操心了。吃貨們對這種做菜方式肯定是以反感為主,太沒有特色了。但是程式設計師對Gradle類似做法卻熱烈擁抱。

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

  • Groovy,由於它基於Java,所以我們僅介紹Java之外的東西。瞭解Groovy語言是掌握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。由於此文的主要目的是Gradle,所以我們不會過多討論Groovy中細枝末節的東西,而是把知識點集中在以後和Gradle打交道時一些常用的地方上。

3.1  Groovy開發環境

在學習本節的時候,最好部署一下Groovy開發環境。根據Groovy官網的介紹,部署Groovy開發環境非常簡單,在Ubuntu或者cygwin之類的地方:

  • curl -s get.gvmtool.net | bash
  • source "$HOME/.gvm/bin/gvm-init.sh"
  • gvm install groovy
  • 執行完最後一步,Groovy就下載並安裝了。

 然後,建立一個test.groovy檔案,裡邊只有一行程式碼:

println  "hello groovy"
  • 執行groovy test.groovy,輸出結果如圖2所示:

 

親們,必須要完成上面的操作啊。做完後,有什麼感覺和體會?

最大的感覺可能就是groovy和shell指令碼,或者python好類似。

另外,除了可以直接使用JDK之外,Groovy還有一套GDK

說實話,看了這麼多家API文件,還是Google的Android API文件做得好。其頁面中右上角有一個搜尋欄,在裡邊輸入一些關鍵字,瞬間就能列出候選類,相關文件,方便得不得了啊.....

3.2  一些前提知識

為了後面講述方面,這裡先介紹一些前提知識。初期接觸可能有些彆扭,看習慣就好了。

  • Groovy註釋標記和Java一樣,支援//或者/**/
  • Groovy語句可以不用分號結尾。Groovy為了儘量減少程式碼的輸入,確實煞費苦心
  • Groovy中支援動態型別,即定義變數的時候可以不指定其型別。Groovy中,變數定義可以使用關鍵字def。注意,雖然def不是必須的,但是為了程式碼清晰,建議還是使用def關鍵字
   def variable1 = 1   //可以不使用分號結尾
   def varable2 = "I am a person"
   def  int x = 1   //變數定義時,也可以直接指定型別
  •   函式定義時,引數的型別也可以不指定。比如
String testFunction(arg1,arg2){//無需指定引數型別
  ...
}
  • 除了變數定義可以不指定型別外,Groovy中函式的返回值也可以是無型別的。比如:

//無型別的函式定義,必須使用def關鍵字

def  nonReturnTypeFunc(){
     last_line   //最後一行程式碼的執行結果就是本函式的返回值
}

//如果指定了函式返回型別,則可不必加def關鍵字來定義函式
String  getString(){
   return "I am a string"
}

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

  • 函式返回值:Groovy的函式裡,可以不使用return xxx來設定xxx為函式返回值。如果不使用return語句的話,則函式裡最後一句程式碼的執行結果被設定成返回值。比如
//下面這個函式的返回值是字串"getSomething return value"

def getSomething(){

      "getSomething return value" //如果這是最後一行程式碼,則返回型別為String

      1000 //如果這是最後一行程式碼,則返回型別為Integer

}

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

  • Groovy對字串支援相當強大,充分吸收了一些指令碼語言的優點:

1  單引號''中的內容嚴格對應Java中的String,不對$符號進行轉義

   def singleQuote='I am $ dolloar'  //輸出就是I am $ dolloar

2  雙引號""的內容則和指令碼語言的處理有點像,如果字元中有$號的話,則它會$表示式先求值。

   def doubleQuoteWithoutDollar = "I am one dollar" //輸出 I am one dollar
   def x = 1
   def doubleQuoteWithDollar = "I am $x dolloar" //輸出I am 1 dolloar

3 三個引號'''xxx'''中的字串支援隨意換行 比如

   def multieLines = ''' begin
     line  1 
     line  2
     end '''
  • 最後,除了每行程式碼不用加分號外,Groovy中函式呼叫的時候還可以不加括號。比如:
println("test") ---> println "test"

注意,雖然寫程式碼的時候,對於函式呼叫可以不帶括號,但是Groovy經常把屬性和函式呼叫混淆。比如

def getSomething(){
   "hello"
}

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

所以,呼叫函式要不要帶括號,我個人意見是如果這個函式是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中的容器類很簡單,就三種:

  • List:連結串列,其底層對應Java中的List介面,一般用ArrayList作為真正的實現類。
  • Map:鍵-值表,其底層對應Java中的LinkedHashMap。
  • 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為例,我們該如何更好得使用它呢?

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

 有了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函式。

3.4  閉包

3.4.1  閉包的樣子

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

閉包,是一種資料型別,它代表了一段可執行的程式碼。其外形如下:

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

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

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

說實話,從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!'

但是,如果在閉包定義時,採用下面這種寫法,則表示閉包沒有引數!

            
           

相關推薦

深入理解AndroidGradle

作者 鄧凡平 編者按:隨著移動裝置硬體能力的提升,Android系統開放的特質開始顯現,各種開發的奇技淫巧、黑科技不斷湧現,InfoQ特聯合《深入理解Android》系列圖書作者鄧凡平,開設深入理解Android專欄,探索Android從框架到應用開

深入理解overlayfs初識

Overlayfs是一種類似aufs的一種堆疊檔案系統,於2014年正式合入Linux-3.18主線核心,目前其功能已經基本穩定(雖然還存在一些特性尚未實現)且被逐漸推廣,特別在容器技術中更是勢頭難擋。本系列博文將首先介紹overlayfs的基本概念和應用場景,然後通過若干例項描述它的使用方式,

深入理解pythonpython語法總結基礎知識和對python中物件的理解

用python也用了兩年了,趁這次疫情想好好整理下。 大概想法是先對python一些知識點進行總結,之後就是根據python核心原始碼來對python的實現方式進行學習,不會閱讀整個原始碼,,,但是應該會把資料結構的實現、函式呼叫過程、以及python虛擬機器的基本原理根據原始碼解釋下。 當然限於筆者只是一個

深入理解JavaScript

odi 可能 方式 == tin mini 單詞 包括 fun 編寫高質量 JavaScript 代碼的基本要點 轉載:http://wiki.jikexueyuan.com/project/javascript-depth-understanding/start-jav

深入理解Plasma3Plasma MVP

這一系列文章將圍繞以太坊的二層擴容框架,介紹其基本執行原理,具體操作細節,安全性討論以及未來研究方向等。本篇文章主要介紹 Plasma 的一個最小實現 Plasma MVP(Minima Viable Plasma)。 在上一篇文章中我們已經理解了 Plasma 中的

深入理解Plasma2Plasma 細節剖析

這一系列文章將圍繞以太坊的二層擴容框架,介紹其基本執行原理,具體操作細節,安全性討論以及未來研究方向等。本篇文章主要對 Plasma 一些關鍵操作的細節進行剖析。 在上一篇文章中我們已經理解了什麼是 Plasma 框架以及它是如何執行的,這一篇文章將對其執行過程中的一

深入理解Plasma1Plasma 框架

這一系列文章將圍繞以太坊的二層擴容框架,介紹其基本執行原理,具體操作細節,安全性討論以及未來研究方向等。本篇文章作為開篇,主要目的是理解 Plasma 框架。 Plasma 作為以太坊的二層擴容框架,自從 2017 年被 Joseph Poon(Lightning N

深入理解overlayfs使用與原理分析

在初步瞭解overlayfs用途之後,本文將介紹如何使用overlayfs以及理解該檔案系統所特有的一些功能特性。由於目前主線核心對overlayfs正在不斷的開發和完善中,因此不同的核心版本改動可能較大,本文儘量與最新的核心版本保持一致,但可能仍會存在細微的出入。 核心版本:Linux-4.1

深入理解JVM——基本原理掃盲篇

前言   JVM一直是java知識裡面進階階段的重要部分,如果希望在java領域研究的更深入,則JVM則是如論如何也避開不了的話題,本系列試圖通過簡潔易讀的方式,講解JVM必要的知識點。 執行流程   我們都知道java一直宣傳的口號是:一次編譯,到處執行。那麼它如何實現的

深入理解 Tomcat原始碼環境搭建和 How Tomcat works 原始碼

轉載自:https://blog.csdn.net/qq_38182963/article/details/78660767 為了瞭解 tomcat 的原理, 樓主費勁心思, 從圖書館借來了絕版的<>, 下載了該書中的例項原始碼, 由於該書已經

深入理解ElasticSearch索引管理

索引管理 1、建立一個索引 到目前為止, 我們已經通過索引一篇文件建立了一個新的索引 。這個索引採用的是預設的配置,新的欄位通過動態對映的方式被新增到型別對映。現在我們需要對這個建立索引的過程做更多的控制:我們想要確保這個索引有數量適中的主分片,並且在我們索引任何資料 之

深入理解JVM——物件的建立

物件的建立 物件的建立,在語言層面上,通常只是new這個關鍵字而已。(本章所討論的物件限於普通Java物件,不包括陣列和 Class物件)虛擬機器遇到new指令時: 檢查這個指令的引數是否能在常量池定位到一個類的符號引用。 檢查這個符號引用代表的類是否已被載入,解析,初

深入理解JVM——執行時的資料區域

Java與C++的圍牆:記憶體動態分配,垃圾收集技術 程式計數器 當前執行緒所執行的位元組碼的行號指示器,通過改變這個計數器的值來選擇下一條執行的位元組碼指令,分支,迴圈,跳轉,異常處理,執行緒恢復等依賴計數器。 執行緒私有,唯一不會OutOfMemory的區域。 執行Jav

深入理解PlasmaPlasma 框架

這一系列文章將圍繞以太坊的二層擴容框架,介紹其基本執行原理,具體操作細節,安全性討論以及未來研究方向等。本篇文章作為開篇,主要目的是理解 Plasma 框架。 Plasma 作為以太坊的二層擴容框架,自從 2017 年被 Joseph Poon(Lightnin

深入理解JVM虛擬機器類載入機制

虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java型別,這就是虛擬機器的類載入機制。 在Java中,型別的載入、連線和初始化過程都是程式在執行期間完成的,這種策略雖然會令類載入時稍微增

深入理解Android4——理解Android中的JNI

在前面文章中簡單介紹了JNI,這一篇文章來簡單看一下jni.h中定義的一些常用方法,來實現通過C++呼叫Android中的Java程式碼。一、兩個引數的介紹在前面的程式碼中我們會遇到兩個引數,下面對這兩個引數做一解釋1、JNIEnv是指向可用JNI函式表的介面指標,C程式碼中

深入理解JVMJava記憶體區域

執行時資料區域 Java虛擬機器在執行Java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域。這些區域都有各自的用途,以及建立和銷燬的時間,有的區域隨著虛擬機器程序的啟動而存在,有些區域則依賴使用者執行緒的啟動和結束而建立和銷燬。根據《Java虛擬機

深入理解JVM類檔案結構

今天我們來介紹一下Class類檔案結構 1.概述 計算機雖然只能識別0和1,但是越來越多的程式語言選擇了與作業系統和機器指令集無關的、平臺中立的格式作為程式編譯後的儲存格式。Java虛擬機器不和包括Java在內的任何語言繫結,只與“class檔案”這種特定的二進

理解繼承JS面向物件程式設計封裝

依然是向阮前輩(阮一峰)學習的部落格原文 學了這一階段和峰大神的幾篇文章,打算下一階段滾回去把指標好好學一下 JS 中,萬物皆物件,但是它又不是一門真正的面向物件程式語言。 那如果我們要把“屬性(prototype)”和方法(“method”)封裝成一個物

深入理解jvm虛擬機器位元組碼執行引擎

執行時棧幀 每一個方法從呼叫開始到執行完成都對應著一張棧幀的進棧和出棧。棧幀中儲存著區域性變量表,運算元表,動態連結和方法返回地址。位於虛擬機器最頂層的稱為當前方法棧。 區域性變量表 儲存當前方法的區域性變數和引數,區域性變量表的容量以變數槽slo