Hi,大家好啊。笨鳥之旅已經(jīng)很久都沒有更新了六孵,感謝大家這么久以來還把我留在列表里。這么久以來不更新的原因主要是我本人進(jìn)入了一個(gè)迷茫期幅骄,對(duì)于工作和生活都提不起興致來劫窒。加上每天工作的時(shí)間太長,一回家就癱在床上了拆座。這也導(dǎo)致了很久之前的文章計(jì)劃一直都擱淺主巍,做的計(jì)劃一次一次的delay冠息。于是生活進(jìn)入了一個(gè)負(fù)循環(huán)。但是生活還是要向上看煤禽,還是要堅(jiān)持去努力做點(diǎn)事情铐达,努力去變得更好,所以我又來了檬果。以后一定會(huì)努力的學(xué)習(xí)瓮孙,努力的發(fā)文。
這段時(shí)間來學(xué)習(xí)了gradle选脊,也體會(huì)到了gradle從初步理解到基本熟悉杭抠,再到深入源碼這樣一個(gè)過程中的一些曲折。這篇文章主要是gradle的基礎(chǔ)知識(shí)篇恳啥∑樱看完這篇文章,你可以:
- 清楚gradle的定義和解決的痛點(diǎn)
- 基本理解Android gradle的運(yùn)作機(jī)制
- 基本理解gradle的大部分語法
- 學(xué)會(huì)基本的groovy開發(fā)
本篇文章預(yù)計(jì)學(xué)習(xí)時(shí)間30分鐘
如果你想關(guān)注gradle更深入的一些知識(shí)钝的,請(qǐng)繼續(xù)關(guān)注后續(xù)gradle文章翁垂。
what is gradle?
先來看一段維基百科上對(duì)于gradle的解釋。
Gradle是一個(gè)基于Apache Ant和Apache Maven概念的項(xiàng)目自動(dòng)化構(gòu)建工具硝桩。它使用一種基于Groovy的特定領(lǐng)域語言來聲明項(xiàng)目設(shè)置沿猜,而不是傳統(tǒng)的XML。當(dāng)前其支持的語言限于Java碗脊、Groovy和Scala啼肩,計(jì)劃未來將支持更多的語言。
可能剛接觸gradle的同學(xué)都不是很了解gradle的這個(gè)定義衙伶∑碜梗可能就只會(huì)跟著網(wǎng)上的教程copy一點(diǎn)配置,但是不理解這些配置背后的原理矢劲。那么怎么來理解這句話呢赦拘,我們可以把握到三個(gè)要點(diǎn):首先,它是一種構(gòu)建工具
芬沉,其次躺同,gradle是基于maven概念
的,最后花嘶,使用groovy
這種語言來聲明。要理解這幾句話蹦漠,我們先考慮幾個(gè)場景椭员。
1.渠道管理
:國內(nèi)手機(jī)市場有大大小小數(shù)十個(gè),大的手機(jī)廠商也有五六個(gè)笛园,每個(gè)廠商可能又有不同的定制rom隘击。如果我們要為不同市場和廠商進(jìn)行適配侍芝,那就需要寫這樣的代碼
if(isHuawei) {
// dosomething
} else if(isOppo) {
// dosomething
}
這樣的話,繁瑣不說埋同,對(duì)單個(gè)手機(jī)而言大量的無用代碼被編譯進(jìn)apk中州叠,包體積和運(yùn)行速度都會(huì)受影響。為了解決這個(gè)問題凶赁,gradle引進(jìn)了productFlavor和buildType的能力咧栗,能根據(jù)情況來進(jìn)行打包。所以說他是一個(gè)自動(dòng)化構(gòu)建工具
虱肄≈掳澹可以看官方文檔
2.依賴管理
:我們通常會(huì)在項(xiàng)目中引入各種三方庫進(jìn)行代碼復(fù)用。比如咏窿,直接手動(dòng)把jar或者aar copy到項(xiàng)目中斟或,然后添加依賴。這種方法缺陷很明顯集嵌,首先配置和刪除流程很繁瑣萝挤,其次,同一個(gè)jar可能會(huì)被多個(gè)項(xiàng)目所引用根欧,導(dǎo)致不知不覺就copy了多個(gè)jar怜珍。最后,版本管理艱難咽块。為了解決這個(gè)問題绘面,gradle是基于maven倉庫,配置和刪除的時(shí)候僅需要對(duì)倉庫的坐標(biāo)進(jìn)行操作侈沪,所有的庫都會(huì)被gradle統(tǒng)一管理揭璃,大多數(shù)情況下每個(gè)庫只會(huì)有一個(gè)版本存在于項(xiàng)目中,并且每個(gè)庫只會(huì)有一個(gè)副本存在于項(xiàng)目中亭罪。
所以gradle其實(shí)不是什么神秘的東西瘦馍,只是基于某種語言(groovy, java, kotlin)的一種構(gòu)建工具而已。只要我們大概掌握了基本的用法和他的內(nèi)部原理应役,日常工作中就會(huì)知道自己網(wǎng)上搜到的命令是什么意思啦情组。skr~
小試牛刀-android中的gradle
咱們先看看日常工作中經(jīng)常用到的幾個(gè)gradle文件÷嵯椋可以看到主要有有三個(gè)文件:
1.build.gradle
根文件下放的通常放的是針對(duì)整個(gè)工程的通用配置院崇,每個(gè)module下面的build.gradle文件是針對(duì)每個(gè)module自身的配置。
buildscript {
ext.kotlin_version = '1.2.71'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
jcenter()
}
}
這是一個(gè)默認(rèn)的配置袍祖,我們可以看到有buildscript底瓣,allprojects,repositories蕉陋,dependencies幾個(gè)配置項(xiàng)捐凭,這些配置項(xiàng)是干嘛的呢拨扶,很多的同學(xué)在剛學(xué)gradle的時(shí)候都是一臉懵逼的。這些其實(shí)是gradle的一種特定的語法茁肠,我們稱之為DSL(domain-specific language)患民。可以參考官網(wǎng)垦梆。這里可以看到allprojects代理的是每個(gè)project匹颤,可以理解成我們的每個(gè)module,也就是對(duì)我們所寫的每個(gè)module的配置奶赔。buildscript主要配置的是打包相關(guān)的東西惋嚎,比如gradle版本,gradle插件版本等站刑,這些都是針對(duì)構(gòu)建工具自己的配置另伍。repositories,dependencies是三方庫的倉庫和坐標(biāo)绞旅。所以根目錄的build.gradle相當(dāng)于是整體的配置摆尝。
而module下的build.gradle主要是android,dependencies等配置項(xiàng)因悲。
apply plugin: 'com.android.application'
android{
...
}
dependencies{
...
}
可能有些同學(xué)會(huì)感到奇怪堕汞,為啥我們?cè)?a target="_blank">官網(wǎng)沒有看到android這個(gè)配置項(xiàng)呢?這個(gè)主要是因?yàn)樗⒉皇莋radle的DSL晃琳,某種意義上說應(yīng)該算是android特有的讯检,是通過Android的插件'com.android.application'帶進(jìn)來的配置項(xiàng)。我們?nèi)绻训谝恍袆h掉卫旱,就會(huì)發(fā)現(xiàn)android{}這個(gè)配置項(xiàng)找不到了人灼。
所以形病,我們可以發(fā)現(xiàn)球及,build.gradle里面的配置項(xiàng),要么是gradle自帶的绒北,要么是各種插件定義的适贸。有不認(rèn)識(shí)的配置項(xiàng)灸芳,就去官網(wǎng)查詢一下就好了,授人以魚不如授人以漁嘛拜姿。我們后面也會(huì)講解到引進(jìn)插件的方式和怎么定義插件和配置項(xiàng)烙样。
2.settings.gradle
這個(gè)文件主要是決定每個(gè)module是否參與構(gòu)建。我們可以這樣去理解蕊肥,settings.gradle相當(dāng)于是每個(gè)module的開關(guān)谒获,關(guān)上了這個(gè)module就不能使用了,別的依賴到它的module也都會(huì)出問題。
3.gradle.properties
這里主要是增加和修改一些可以在構(gòu)建過程中直接使用的參數(shù)究反。不只是可以添加自定義參數(shù),還可以修改系統(tǒng)的參數(shù)哦~
總結(jié)一下儒洛,就是說根目錄下有一個(gè)build.gradle精耐,處理整個(gè)工程的配置項(xiàng),根目錄下的settings.gradle配置整個(gè)工程中參與構(gòu)建的module琅锻,每個(gè)module自己有一個(gè)build.gradle卦停,處理自己模塊的配置。這就是android構(gòu)建的一個(gè)大概情況恼蓬。當(dāng)然惊完,看了這一部分肯定還是不懂怎么去寫的,接下來我們走進(jìn)代碼層面处硬。
groovy-學(xué)gradle的密鑰
gradle可以使用groovy小槐,kotlin,java等語言進(jìn)行書寫荷辕,但是groovy相對(duì)來說是目前比較流行的gradle配置方式凿跳,下面我們講解一點(diǎn)groovy基礎(chǔ)。不講太多疮方,夠用就行控嗜。
1.字符串
groovy的字符串分為兩種java.lang.String和groovy.lang.GString。其中單引號(hào)和三引號(hào)是String類型的骡显。雙引號(hào)是GString類型的疆栏。支持占位插值操作。和kotlin一樣惫谤,groovy的插值操作也是用${}
或者$
來標(biāo)示壁顶,${}
用于一般替代字串或者表達(dá)式,$
主要用于A.B的形式中石挂。
def number = 1
def eagerGString = "value == ${number}"
def lazyGString = "value == ${ -> number }"
println eagerGString
println lazyGString
number = 2
println eagerGString
println lazyGString
2.字符Character
Groovy沒有明確的Character博助。但是可以強(qiáng)行聲明。
char c1 = 'A'
assert c1 instanceof Character
def c2 = 'B' as char
assert c2 instanceof Character
def c3 = (char)'C'
assert c3 instanceof Character
4.List
Groovy的列表和python的很像痹愚。支持動(dòng)態(tài)擴(kuò)展富岳,支持放置多種數(shù)據(jù)。使用方法支持def和直接定義拯腮。還可以像python那樣索引
//List中存儲(chǔ)任意類型
def heterogeneous = [1, "a", true]
//判斷List默認(rèn)類型
def arrayList = [1, 2, 3]
assert arrayList instanceof java.util.ArrayList
//使用as強(qiáng)轉(zhuǎn)類型
def linkedList = [2, 3, 4] as LinkedList
assert linkedList instanceof java.util.LinkedList
//定義指定類型List
LinkedList otherLinked = [3, 4, 5]
assert otherLinked instanceof java.util.LinkedList
// 像python一樣索引
assert letters[1] == 'b'
//負(fù)數(shù)下標(biāo)則從右向左index
assert letters[-1] == 'd'
assert letters[-2] == 'c'
//指定item賦值判斷
letters[2] = 'C'
assert letters[2] == 'C'
//給List追加item
letters << 'e'
assert letters[ 4] == 'e'
assert letters[-1] == 'e'
//獲取一段List子集
assert letters[1, 3] == ['b', 'd']
assert letters[2..4] == ['C', 'd', 'e']
5.Map
//定義一個(gè)Map
def colors = [red: '#FF0000', green: '#00FF00', blue: '#0000FF']
//獲取一些指定key的value進(jìn)行判斷操作
assert colors['red'] == '#FF0000'
assert colors.green == '#00FF00'
6.運(yùn)算符
- **: 次方運(yùn)算符窖式。
- ?.:安全占位符。和kotlin一樣避免空指針異常动壤。
- .@:直接域訪問操作符萝喘。因?yàn)镚roovy自動(dòng)支持屬性getter方法,但有時(shí)候我們有一個(gè)自己寫的特殊getter方法,當(dāng)不想調(diào)用這個(gè)特殊的getter方法則可以用直接域訪問操作符阁簸。這點(diǎn)跟kotlin的
- .&:方法指針操作符爬早,因?yàn)殚]包可以被作為一個(gè)方法的參數(shù),如果想讓一個(gè)方法作為另一個(gè)方法的參數(shù)則可以將一個(gè)方法當(dāng)成一個(gè)閉包作為另一個(gè)方法的參數(shù)启妹。
- ?::二目運(yùn)算符筛严。與kotlin中的類似。
-
*.
展開運(yùn)算符饶米,一個(gè)集合使用展開運(yùn)算符可以得到一個(gè)元素為原集合各個(gè)元素執(zhí)行后面指定方法所得值的集合桨啃。
cars = [
new Car(make: 'Peugeot', model: '508'),
null,
new Car(make: 'Renault', model: 'Clio')]
assert cars*.make == ['Peugeot', null, 'Renault']
assert null*.make == null
7.閉包
groovy里比較重要的是閉包的概念。官方定義是“Groovy中的閉包是一個(gè)開放檬输,匿名的代碼塊照瘾,可以接受參數(shù),返回值并分配給變量”丧慈。
其實(shí)閉包跟kotlin的lambda函數(shù)很像析命,都是先定義后執(zhí)行。但是又有一些細(xì)微的區(qū)別逃默。接下來我們細(xì)講講gradle的閉包碳却。
閉包是可以用作方法參數(shù)的代碼塊,Groovy的閉包更象是一個(gè)代碼塊或者方法指針笑旺,代碼在某處被定義然后在其后的調(diào)用處執(zhí)行昼浦。一個(gè)閉包實(shí)際上就是一個(gè)Closure類型的實(shí)例。寫法和kotlin的lambda函數(shù)很像筒主。
我們常見的閉包是這樣的
//最基本的閉包
{ item++ }
//使用->將參數(shù)與代碼分離
{item -> item++ }
//使用隱含參數(shù)it
{ println it }
//使用顯示的名為參數(shù)
{ name -> println name }
// 調(diào)用方法
a.call()
a()
// Groovy的閉包支持最后一個(gè)參數(shù)為不定長可變長度的參數(shù)关噪。
def multiConcat = { int n, String... args ->
args.join('')*n
}
大家要注意,如果我們單純的只是寫成 a = { item++ }, 這只是定義了一個(gè)閉包乌妙,是不能運(yùn)行的使兔。必須調(diào)用a.call()才能運(yùn)行出來。所以大家可以理解了藤韵,閉包就是一段代碼塊而已虐沥。當(dāng)我們有需要的時(shí)候,可以去運(yùn)行它泽艘,這么一想是不是和lambda函數(shù)很像欲险?
如果你看了官網(wǎng),你會(huì)發(fā)現(xiàn)有一些這樣的說法匹涮,
什么叫做delegate天试?這里涉及到閉包內(nèi)部的三種對(duì)象。
- this 對(duì)應(yīng)于定義閉包的那個(gè)類然低,如果在內(nèi)部類中定義喜每,指向的是內(nèi)部類
- owenr 對(duì)應(yīng)于定義閉包的那個(gè)類或者閉包务唐,如果在閉包中定義,對(duì)應(yīng)閉包带兜,否則同this一致
- delegate 默認(rèn)是和owner一致枫笛,或者自定義delegate指向
this和owner都比較好理解。我們可以用閉包的getxxx方法獲取
def thisObject = closure.getThisObject()
def ownerObject = closure.getOwner()
def delegate = closure.getDelegate()
重頭戲還是delegate這個(gè)對(duì)象刚照。閉包可以設(shè)置delegate對(duì)象崇堰,設(shè)置delegate的意義就是將閉包和一個(gè)具體的對(duì)象關(guān)聯(lián)起來。
我們先來看個(gè)例子涩咖,這里以自定義android閉包為例。
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
}
}
這個(gè)閉包對(duì)應(yīng)的實(shí)體類是兩個(gè)繁莹。
# Android.groovy
class Android {
private int mCompileSdkVersion
private String mBuildToolsVersion
private ProductFlavor mProductFlavor
Android() {
this.mProductFlavor = new ProductFlavor()
}
void compileSdkVersion(int compileSdkVersion) {
this.mCompileSdkVersion = compileSdkVersion
}
void buildToolsVersion(String buildToolsVersion) {
this.mBuildToolsVersion = buildToolsVersion
}
void defaultConfig(Closure closure) {
closure.setDelegate(mProductFlavor)
closure.setResolveStrategy(Closure.DELEGATE_FIRST)
closure.call()
}
@Override
String toString() {
return "Android{" +
"mCompileSdkVersion=" + mCompileSdkVersion +
", mBuildToolsVersion='" + mBuildToolsVersion + '\'' +
", mProductFlavor=" + mProductFlavor +
'}'
}
}
# ProductFlavor.groovy
class ProductFlavor {
private int mVersionCode
private String mVersionName
private int mMinSdkVersion
private int mTargetSdkVersion
def versionCode(int versionCode) {
mVersionCode = versionCode
}
def versionName(String versionName) {
mVersionName = versionName
}
def minSdkVersion(int minSdkVersion) {
mMinSdkVersion = minSdkVersion
}
def targetSdkVersion(int targetSdkVersion) {
mTargetSdkVersion = targetSdkVersion
}
@Override
String toString() {
return "ProductFlavor{" +
"mVersionCode=" + mVersionCode +
", mVersionName='" + mVersionName + '\'' +
", mMinSdkVersion=" + mMinSdkVersion +
", mTargetSdkVersion=" + mTargetSdkVersion +
'}'
}
}
然后定義的時(shí)候就寫成
//閉包定義
def android = {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
minSdkVersion 15
targetSdkVersion 25
versionCode 1
versionName "1.0"
}
}
//調(diào)用
Android bean = new Android()
android.delegate = bean
android.call()
println bean.toString()
//打印結(jié)果
Android{mCompileSdkVersion=25, mBuildToolsVersion='25.0.2', mProductFlavor=ProductFlavor{mVersionCode=1, mVersionName='1.0', mMinSdkVersion=15, mTargetSdkVersion=25}}
這樣就能將閉包中聲明的值檩互,賦給兩個(gè)對(duì)象Android和ProductFlavor來處理了。
上面官網(wǎng)的圖里咨演,說ScriptHandler被設(shè)置成buildscript的delegate闸昨。意思就是buildscript定義的參數(shù)被ScriptHandler拿來使用了。大家有興趣的可以去看看ScriptHandler的源碼~
Project與Task-gradle構(gòu)建體系
上面我們講完了基本的用法薄风,大家可能懂gradle的配置和寫法了饵较。但是可能還是不懂gradle的構(gòu)建體系到底是怎么樣的。這里我們就要深入進(jìn)gradle的構(gòu)建體系Project和Task了遭赂。下面的東西看著就要?jiǎng)觿?dòng)腦筋了循诉。
1.Task
Task是gradle腳本中的最小可執(zhí)行單元。類圖如下:
值得注意的是因?yàn)镚radle構(gòu)建腳本默認(rèn)的名字是build.gradle撇他,當(dāng)在shell中執(zhí)行g(shù)radle命令時(shí)茄猫,Gradle會(huì)去當(dāng)前目錄下尋找名字是build.gradle的文件。所以只有定義在build.gradle中的Task才是有效的困肩。
可以通過三種方式來聲明task划纽。我們可以根據(jù)自己的項(xiàng)目需要去定義Task。比如自定義task接管gradle的編譯過程
task myTask2 << {
println "doLast in task2"
}
//采用 Project.task(String name) 方法來創(chuàng)建
project.task("myTask3").doLast {
println "doLast in task3"
}
//采用 TaskContainer.create(String name) 方法來創(chuàng)建
project.tasks.create("myTask4").doLast {
println "doLast in task4"
}
TaskContianer 是用來管理所有的 Task 實(shí)例集合的锌畸,可以通過 Project.getTasks() 來獲取 TaskContainer 實(shí)例勇劣。
常見接口:
findByPath(path: String): Task
getByPath(path: String): Task
getByName(name: String): Task
withType(type: Class): TaskCollection
matching(condition: Closure): TaskCollection
//創(chuàng)建task
create(name: String): Task
create(name: String, configure: Closure): Task
create(name: String, type: Class): Task
create(options: Map<String, ?>): Task
create(options: Map<String, ?>, configure: Closure): Task
//當(dāng)task被加入到TaskContainer時(shí)的監(jiān)聽
whenTaskAdded(action: Closure)
Gradle支持增量編譯。了解過編譯profile文件的朋友都知道潭枣,里面有大量的task都是up-to-date
比默。那么這種up-to-date是什么意思呢。Gradle的Task會(huì)把每次運(yùn)行的結(jié)果緩存下來盆犁,當(dāng)下次運(yùn)行時(shí)退敦,會(huì)檢查一個(gè)task的輸入輸出有沒有變更。如果沒有變更就是up-to-date蚣抗,跳過編譯侈百。
2.Project
先從Project對(duì)象講起瓮下,Project是與Gradle交互的主接口。android開發(fā)中最為我們所熟悉的就是build.gradle文件钝域,這個(gè)文件與Project是一對(duì)一的關(guān)系讽坏,build.gradle文件是project對(duì)象的委托,腳本中的配置都是對(duì)應(yīng)著Project的Api例证。Gradle構(gòu)建進(jìn)程啟動(dòng)的時(shí)候會(huì)根據(jù)build.gradle去實(shí)例化Project類路呜。也就是說,構(gòu)建的時(shí)候织咧,每個(gè)build.gradle文件會(huì)生成一個(gè)Project對(duì)象胀葱,這個(gè)對(duì)象負(fù)責(zé)當(dāng)前module的構(gòu)建。
Project本質(zhì)上是包含多個(gè)Task的容器笙蒙,所有的Task存在TaskContainer中抵屿。我們從名字可以看出
可以看到dependencies, configuration, allprojects, subprojects, beforeEvaluate, afterEvaluate這些都是我們常見的配置項(xiàng),在build.gradle文件中接收一個(gè)閉包Closure捅位。
好了轧葛,現(xiàn)在我們已經(jīng)聊了build.gradle了,但是大家都知道艇搀,我們項(xiàng)目中還有一個(gè)settings.gradle呢尿扯,這個(gè)是拿來干嘛的呢?這就要說到Project的Lifecycle
了焰雕,也就是Gradle構(gòu)建Project的步驟衷笋,看官網(wǎng)原文:
- Create a Settings instance for the build.
- Evaluate the settings.gradle script, if present, against the Settings object to configure it.
- Use the configured Settings object to create the hierarchy of Project instances.
- Finally, evaluate each Project by executing its build.gradle file, if present, against the project. The projects are evaluated in breadth-wise order(寬度搜索), such that a project is evaluated before its child projects. This order can be overridden by calling
Project.evaluationDependsOnChildren()
or by adding an explicit evaluation dependency usingProject.evaluationDependsOn(java.lang.String)
.
也就是說,Project對(duì)象依賴Settings對(duì)象的構(gòu)建矩屁。我們常在settings.gradle文件中配置需要引入的module右莱,就是這個(gè)原因。
3.Property
看完了build.gradle和settings.gradle档插,接下來我們講講gradle.properties慢蜓。這個(gè)文件存放的鍵值對(duì)形式的屬性,這些屬性能被項(xiàng)目中的gradle腳本使用ext.xxx所訪問郭膛。
我們也可以使用Properties類來動(dòng)態(tài)創(chuàng)建屬性文件晨抡。如:
def defaultProps = new Properties()
defaultProps.setProperty("debuggable", 'true')
defaultProps.setProperty("groupId", GROUP)
并且屬性可以繼承,在一個(gè)項(xiàng)目中定義的屬性可以自動(dòng)被子項(xiàng)目繼承则剃。所以在哪個(gè)子項(xiàng)目都可以使用project.ext.xxx訪問耘柱。不同子項(xiàng)目間采用通用的配置插件來配置
apply from: rootProject.file('library.gradle')
總結(jié)
通過上面的學(xué)習(xí),大家應(yīng)該已經(jīng)了解了gradle的基本配置棍现,寫法和比較淺顯的內(nèi)部原理了调煎。因?yàn)槠颍钊氲膬?nèi)容我們放在下一篇己肮。敬請(qǐng)期待《一篇文章深入gradle》
參考:
官網(wǎng)
[Android Gradle] 搞定Groovy閉包這一篇就夠了
我是Android笨鳥之旅士袄,一個(gè)陪著你慢慢變強(qiáng)的公眾號(hào)悲关。