Android默認(rèn)使用Gradle作為構(gòu)建工具斑鼻。
Why Gradle
Android Studio Project Site的描述如下
Gradle的出現(xiàn)滿足了很多現(xiàn)在構(gòu)建工具的需求,Gradle提供了一個(gè)DSL(領(lǐng)域特定語(yǔ)言),一個(gè)約定優(yōu)于配置的方法娱俺,還有更強(qiáng)大的依賴管理,Gradle使得我們可以拋棄XML的繁瑣配置废麻,引入動(dòng)態(tài)語(yǔ)言Groovy來定義你的構(gòu)建邏輯荠卷。
Groovy
由于Gradle基于Groovy,要理解Gradle烛愧,必須先了解Groovy油宜。
Groovy 被很多人認(rèn)為是一種腳本語(yǔ)言掂碱,其實(shí)不準(zhǔn)確,官網(wǎng)表述:
Groovy是一種基于JVM(Java虛擬機(jī))的動(dòng)態(tài)語(yǔ)言慎冤,它結(jié)合了Python疼燥、Ruby和Smalltalk的許多強(qiáng)大的特性,Groovy 代碼能夠與 Java 代碼很好地結(jié)合蚁堤,也能用于擴(kuò)展現(xiàn)有代碼醉者。由于其運(yùn)行在 JVM 上的特性,Groovy 可以使用其他 Java 語(yǔ)言編寫的庫(kù)披诗。
基礎(chǔ):
- 注釋和java一樣
- 語(yǔ)句可以不以分號(hào)結(jié)尾
- 定義變量是可以不指定類型撬即,定義變量使用def關(guān)鍵字(不強(qiáng)制,但推薦呈队,def會(huì)改變作用域)
按套路剥槐,現(xiàn)在該上“HelloWorld”了
java版HelloWorld:
public class Test {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
Groovy版HelloWorld:
println "Hello World"
Groovy版本比java版本簡(jiǎn)單很多,我們可以將groovy文件編譯為class查看:
groovyc -d classes test.groovy
編譯后得到test.class:
- test.groovy被轉(zhuǎn)為一個(gè)繼承自Script的test類
- 每一個(gè)腳本都會(huì)生成一個(gè)main函數(shù)
- 腳本中的代碼都會(huì)放到run函數(shù)中
- 如果腳本中定義了函數(shù)宪摧,則函數(shù)會(huì)被定義在test類中
之前說def會(huì)改變變量作用域粒竖,舉個(gè)栗子:
def num = 1 // 也可以 int num = 1
def printNum() {
println num
}
printNum()
結(jié)果是運(yùn)行異常:
Caught: groovy.lang.MissingPropertyException: No such property: num for class: test
groovy.lang.MissingPropertyException: No such property: num for class: test
at test.printNum(test.groovy:4)
at test.run(test.groovy:7)
找不到屬性?我們生成class查看:
結(jié)果一目了然绍刮,num是run方法中的局部變量温圆,當(dāng)然無法在printNum中訪問。
我們?cè)倏床患觗ef的class結(jié)果:
此時(shí)通過callGroovyObjectGetProperty來訪問num變量孩革。
也可以將num變?yōu)槌蓡T變量岁歉,這樣printNum自然就可以訪問了
import groovy.transform.Field;
@Field num = 1 //在num前面加上@Field標(biāo)注,num就是test的成員變量了膝蜈。
引號(hào):
Groovy支持單引號(hào)锅移,雙引號(hào),三引號(hào)
- 單引號(hào)中的內(nèi)容嚴(yán)格對(duì)應(yīng)Java的String饱搏,不對(duì)$進(jìn)行轉(zhuǎn)義非剃。
- 雙引號(hào)的內(nèi)容如果有
表達(dá)式先求值
- 三引號(hào)可以指示一個(gè)多行的字符串,并可以在其中自由的使用單引號(hào)和雙引號(hào)推沸。
def name = "Candy"
println 'Hello $name'
println "Hello $name"
println "Hello ${name}"
輸出:
Hello $name
Hello Candy
Hello Candy
函數(shù):
除非指定了確定的返回類型(void也可以作為一種返回值)备绽,否則定義函數(shù)必須加上關(guān)鍵字def。
函數(shù)默認(rèn)最后一行語(yǔ)句的值為函數(shù)返回值鬓催,可以顯式的return返回值肺素。
def method1() {
return "This is method1"
}
def method2() {
"This is method2"
}
String method3() {
return "This is method3"
}
void method4() {
println "hello method4"
}
println method1()
println method2()
println method3()
println method4()
輸出:
This is method1
This is method2
This is method3
hello method4
null
參數(shù)可以不指定類型,參數(shù)可以有默認(rèn)值:
def printPeople(name, age=18) {
println "name is ${name}, age is ${age}"
}
printPeople("Jack", 22)
printPeople("Terry")
// 輸出:
// name is Jack, age is 22
// name is Terry, age is 18
當(dāng)函數(shù)有一個(gè)或多個(gè)參數(shù)時(shí)宇驾,調(diào)用的時(shí)候可以不加括號(hào)倍靡,比如:
println "Hello World" // 等同于 println("Hello World")
def someMethod1(it) {
println "someMethod1, it=${it}"
}
def someMethod2() {
println "someMethod2"
}
someMethod1(1) // someMethod1, it=1
someMethod1 2 // someMethod1, it=2
someMethod2() // someMethod2
someMethod2 // 報(bào)錯(cuò)
另外一個(gè)語(yǔ)法特性是Command Chain,不僅可以省略圓括號(hào)课舍,又可以省略”.”號(hào)塌西。比如:a(b).c(d)他挎,可以寫成:a b c d。
def name(name) {
println "name: $name"
return this
}
def age(age) {
println "age: $age"
return this
}
name "Jerry" age 18
函數(shù)調(diào)用不加括號(hào)在Gradle中有很多運(yùn)用捡需,舉個(gè)栗子:
include ':app'
實(shí)際是函數(shù)調(diào)用:
閉包
Groovy閉包文檔:http://www.groovy-lang.org/closures.html
閉包語(yǔ)句:
def clos = { param -> println "${param}" }
clos.call("Hello") // 等同于 clos("Hello")办桨,輸出 Hello
如果閉包沒定義參數(shù)的話,則隱含有一個(gè)參數(shù)it:
def clos = { println "${it}" }
clos("Hello") // 輸出 Hello
當(dāng)然也可以指定閉包沒有參數(shù):
def clos = { -> println "${it}" }
clos("Hello") // 報(bào)錯(cuò)
當(dāng)Closure作為函數(shù)最后一個(gè)參數(shù)時(shí)站辉,可以將Closure拿到括號(hào)外邊崔挖,比如:
def doSomething(arg, Closure clos) {
print "${arg} "
clos()
}
doSomething ("Hello", { println 'Candy' })
doSomething "Hello", { println 'Bob' }
doSomething ("Hello") { println 'Tom' }
// 輸出:
// Hello Candy
// Hello Bob
// Hello Tom
閉包在gradle中有大量的運(yùn)用,比如:
repositories {
jcenter()
}
這在gradle腳本中很常見的寫法庵寞,其實(shí)repositories是一個(gè)函數(shù):
/**
* Configures the repositories for the script dependencies. Executes the given closure against the {@link
* RepositoryHandler} for this handler. The {@link RepositoryHandler} is passed to the closure as the closure's
* delegate.
*
* @param configureClosure the closure to use to configure the repositories.
*/
void repositories(Closure configureClosure);
可以看到這個(gè)函數(shù)的參數(shù)是一個(gè)閉包狸相,那么上面的寫法就等同于:
repositories({
jcenter()
})
至此Groovy暫時(shí)告一段落,我們已經(jīng)了解了一些Groovy的基礎(chǔ)知識(shí)捐川,下一步就是學(xué)習(xí)Gradle了脓鹃。
Gradle
Gradle是一個(gè)框架,它負(fù)責(zé)定義流程和規(guī)則古沥。而具體的編譯工作則是通過插件的方式來完成的瘸右。比如編譯Java有Java插件,編譯Groovy有Groovy插件岩齿,編譯Android APP有Android APP插件太颤,編譯Android Library有Android Library插件。
我們?cè)贏ndroidStudio中創(chuàng)建project時(shí)盹沈,會(huì)默認(rèn)生成一個(gè)多項(xiàng)目結(jié)構(gòu)的Gradle工程:
上圖三個(gè).gradle結(jié)尾的文件就是Gradle的構(gòu)建腳本龄章。
共有三種不同類型的腳本:
Init script
我們大部分人并不常用,但是它確實(shí)可以配置:
- 在命令行里指定:
gradle -I 或者 --init-script <pathToInit.gradle>
- 在USER_HOME/.gradle/init.d目錄下乞封,放置init.gradle文件
Settings script
settings.gradle對(duì)于多項(xiàng)目的Project是必須的做裙,文件在rootProject根目錄下,Settings腳本的Delegate是Settings對(duì)象肃晚,可以看下Settings類都有哪些函數(shù):
我們常用的就是include函數(shù)了:
include ':app'
Build script
build.gradle腳本文件是我們最常編輯的锚贱,絕大部分配置工作都在這里面。每一個(gè)待編譯的Project都對(duì)應(yīng)一個(gè)build.gradle关串,它的Delegate是Project:
每一個(gè)待編譯Project都有一個(gè)build.gradle拧廊,每一個(gè)build.gradle文件都會(huì)轉(zhuǎn)換成一個(gè)Project對(duì)象,在構(gòu)建的時(shí)候包含一系列的Task晋修。比如一個(gè)Android APK的編譯包含:Java源碼編譯Task吧碾、資源編譯Task、JNI編譯Task飞蚓、lint檢查Task滤港、打包生成APK的Task廊蜒、簽名Task等趴拧。
Task
Task 是Gradle中的一種數(shù)據(jù)類型溅漾,它代表了一些要執(zhí)行的工作。不同的插件可以添加不同的Task著榴。每一個(gè)Task都需要和一個(gè)Project關(guān)聯(lián)添履。
創(chuàng)建Task:
task hello1 {
doLast {
println '--- Task hello1 ---'
}
}
task hello2
hello2.doLast {
println '--- Task hello2 ---'
}
tasks.create('hello3')
hello3.doLast {
println '--- Task hello3 ---'
}
使用命令gradle [task]
來執(zhí)行task,如果task是駝峰命名時(shí)(比如task名字是helloWorld)脑又,可以使用縮寫 gradle hW
暮胧。
task依賴:
task hello {
doLast {
println 'Hello World !'
}
}
task intro() {
doLast {
println "I am Gradle"
}
}
intro.dependsOn(hello)
// 或:
// task intro(dependsOn: hello) {
// doLast {
// println "I am Gradle"
// }
// }
執(zhí)行gradle -q intro
后輸出:
Hello World !
I am Gradle
Android Plugin for Gradle
Android Plugin for Gradle 是通過Gradle插件機(jī)制實(shí)現(xiàn)的Android平臺(tái)的Gradle構(gòu)建插件,詳情請(qǐng)參考:
Android Gradle Plugin User Guide
Android Plugin DSL Reference
Configure Your Build
build.gradle解讀
defaultConfig:
defaultConfig {
// 包名
applicationId "com.goodl.gradledemo"
// 最低版本
minSdk 9
// 目標(biāo)版本
targetSdk 25
// 版本號(hào)
versionCode 1
// 版本名稱
versionName "1.0"
// 自動(dòng)化測(cè)試
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a'
}
resConfigs "en", "zh"
}
signConfigs:
signingConfigs {
// debug 簽名
debug {}
// release 簽名
release {
// 簽名文件所在路徑
storeFile file("ray.jks")
// 簽名密碼
storePassword "111111"
// 別名
keyAlias "rayhahah"
keyPassword "111111"
v2SigningEnabled true
}
// 自定義簽名配置
ray {
// 和上面的屬性一致问麸,根據(jù)個(gè)人需求實(shí)現(xiàn)不同配置
}
}
buildTypes:
buildTypes {
debug {
minifyEnabled false
signingConfig signingConfigs.debug
buildConfigField 'boolean', 'ENV_PRODUCTION', "false"
}
uat {
initWith debug // 從給定的構(gòu)建類型復(fù)制所有屬性
buildConfigField 'boolean', 'ENV_PRODUCTION', "false"
}
release {
minifyEnabled true // 是否混淆
zipAlignEnabled true // zipAlign優(yōu)化
shrinkResources true // 移除無用的resource文件
signingConfig signingConfigs.release // 簽名
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField 'boolean', 'ENV_PRODUCTION', "true"
}
}
sourceSets:
sourceSets {
// 這樣的配置適用于將 Eclipse 中的項(xiàng)目結(jié)構(gòu)遷移到 AndroidStudio 中
main {
// 指定src資源目標(biāo)目錄
java.srcDirs = ['src']
// 指定asset的目標(biāo)目錄
assets.srcDirs = ['assets']
// 指定res的目標(biāo)目錄
res.srcDirs = ['res']
// 指定依賴C文件的目標(biāo)目錄
jni.srcDirs = ['jni']
// 指定依賴so文件的目標(biāo)目錄
jniLibs.srcDirs = ['libs']
// 指定Manifest的目標(biāo)文件路徑
manifest.srcFile 'AndroidManifest.xml'
}
}
productFlavors:
// 多渠道打包配置
productFlavors {
xiaomi {
manifestPlaceholders = [CHANNEL_VALUE: "xiaomi"]
}
myapp {
manifestPlaceholders = [CHANNEL_VALUE: "myapp"]
}
}
lintOptions:
lintOptions {
// 啟用出錯(cuò)停止grgradle構(gòu)建
abortOnError false
// true -- 檢查所有問題點(diǎn)往衷,包含其他默認(rèn)關(guān)閉項(xiàng)
checkAllWarnings true
// 關(guān)閉指定問題檢查
disable 'TypographyFractions', 'TypographyQuotes'
// 打開指定問題檢查
enable 'RtlHardcoded', 'RtlCompat', 'RtlEnabled'
// 僅檢查指定問題
check 'NewApi', 'InlinedApi'
// true -- 生成HTML報(bào)告(帶問題解釋,源碼位置严卖,等)
htmlReport true
// html 報(bào)告可選路徑(構(gòu)建器默認(rèn)是lint-results.html )
htmlOutput file("lint-report.html")
// 忽略指定問題的規(guī)則(同關(guān)閉檢查)
ignore 'TypographyQuotes'
}
重命名 apk 名稱:
static def releaseTime() {
return new Date().format("yyyyMMdd", TimeZone.getTimeZone("UTC"))
}
android {
defaultConfig {
// ...
}
// ...
applicationVariants.all { variant ->
variant.outputs.all {
outputFileName = "myapp_v${defaultConfig.versionName}_${releaseTime()}_${variant.name}.apk"
}
}
}