gradle學(xué)習(xí)筆記(一)
概念
Gradle本身是基于Groovy腳本語言進(jìn)行構(gòu)建的,并通過Domain Specific Language(DSL語言)進(jìn)行描述和控制構(gòu)建邏輯的。
參考的文檔:
gradle初探
項目全局build.gradle
文件中最重要的就是buildscript
的部分代碼。在buildscript
中,Gradle制定了使用jcenter
代碼倉庫,同事聲明了依賴的Android Gradle插件版本。
在allprojects
領(lǐng)域中,開發(fā)者可以為項目整體配置一些屬性。
Module build.gradle
Gradle使用的是DSL語言,它是針對某個領(lǐng)域所設(shè)計出來的特定的語言,因?yàn)橛辛祟I(lǐng)域的限制,要解決的問題就被劃定了范圍。因此要針對每個特定的領(lǐng)域進(jìn)行分析即可。
apply plugin 領(lǐng)域
apply plugin 這塊領(lǐng)域描述了Gradle所引入的插件。
apply plugin:'com.android.application'
表示該module是一個Android Application。這個插件包含了Android項目相關(guān)的所有工具。android 領(lǐng)域
android{...}
這塊領(lǐng)域描述了該Android module構(gòu)建過程中所用到的所有參數(shù)。默認(rèn)情況下,IDE自動創(chuàng)建了compileSdkVersion、buildToolsVersion這兩個參數(shù),分別對應(yīng)變異的SDK版本和ANDROID build tools版本。而在android領(lǐng)域內(nèi),系統(tǒng)還默認(rèn)創(chuàng)建了兩個領(lǐng)域---defaultConfig和buildTypes,這兩個領(lǐng)域。dependencies 領(lǐng)域
dependencies{...}
這塊領(lǐng)域描述了該Android module構(gòu)建過程中所依賴的所有庫,庫可以是以jar的形式進(jìn)行以來,或者是使用Android推薦的aar形式進(jìn)行依賴。aar相對于jar具有不可比擬的優(yōu)勢,不僅配置以來更加簡單,而且可以將圖片的資源文件放入aar中供主項目依賴,幾乎等同于依賴源碼。
Gradle Task
- 查看工程有哪些Task:
./gradlew task
- 各個Task的具體作用與各個Task之間的相互調(diào)用關(guān)系:
./gradlew task -all
- assemble task用于組合項目的所有輸出,包含了
assembleDebug
和assembleRelease
兩個Task - check task 用于執(zhí)行檢查任務(wù)
- build Task 類似一個組合指令,執(zhí)行了check和assemble的所有工作
- clean task 用于清理所有中間編譯結(jié)果,這個指令使用的非常廣泛。
Gradle進(jìn)階
構(gòu)建全局配置
-
全局參數(shù)
在項目根目錄下的build.gradle中,通過ext領(lǐng)域可以指定全局的配置信息,代碼如下所示:
ext{
compileSdkVersion = 23
buildToolsVersion = "23.0.2"
minSdkVersion = 14
targetSdkVersion = 23
versionCode = 3
versionName = "1.0.1"
}
-
引用配置
在配置好全局參數(shù)后,就可以在每個module中使用這些配置了,例如
compileSdkVersion rootProject.ext.compileSdkVersion
方法非常簡單,通過rootProject.ext可以引用所有的全局參數(shù)。
另外,開發(fā)者也可以把ext全局配置卸載allprojects領(lǐng)域中,這樣在每個module中就可以直接引用申明的變量了。
allprojects{
repositores{
jecnter()
}
ext {
COMPILE_SDK_VERSION = 22
}
}
這樣寫的好處是可以將配置進(jìn)行統(tǒng)一管理。但壞處是如果這樣寫的話,Gradle的版本更新通知檢查機(jī)制就無限了。大部分時候,這種寫法是利大于弊的。
構(gòu)建defaultConfig
defaultConfig{
applicationId "com.xxx.xxx"
minSdkVersion 14
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
這些設(shè)置替換了AndroidMainifest文件中的屬性。除此之外可以動態(tài)控制VersionName的生成。
defaultConfig{
applicationId "com.xxx.xxx"
minSdkVersion 14
targetSdkVersion 23
versionCode 1
versionName getCustomVersionName()
}
def getCustomVersionName(){
...
}
構(gòu)建buildTypes
通過創(chuàng)建不同的構(gòu)建類型,從而生成不同類型的apk,可以幫助開發(fā)者完成很多事情。例如實(shí)現(xiàn)只有在debug類型下才開啟的功能,如調(diào)試、Log等功能,以及為不同構(gòu)建類型實(shí)現(xiàn)不同的參數(shù)配置,等等。
- 構(gòu)建類型基礎(chǔ)
buildTypes{
release{
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'),`proguard-rules.pro`
}
}
除了系統(tǒng)默認(rèn)的構(gòu)建type--debug和release之外,gradle同樣支持自定義創(chuàng)建新的構(gòu)建類型。例如,在腳本中添加一個xys類型,同時設(shè)置該類型的applicationIdSuffix的參數(shù)為".xxx",代碼如下:
buildTypes{
release{
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'),`proguard-rules.pro`
}
xxx{
applicationIdSuffix ".xys"
}
}
執(zhí)行./gradlew build
之后再build目錄中多生成了一個app-xxx-unsigned.apk,這個就是自定義的新的buildType-xys類型。那么applicationIdSuffix參數(shù)的作用是什么呢?在Android系統(tǒng)中,系統(tǒng)是通過包名來區(qū)分應(yīng)用的。如果應(yīng)用的包名相同,那么就意味著這是一個應(yīng)用。因此在構(gòu)建類型的時候,可以指定applicationIdSuffix參數(shù)為默認(rèn)的包名增加一個后綴。例如前面例子中的”.xxx“,以此區(qū)分不同的構(gòu)建類型。類似的方式,還可以給debug版本增加”.debug“的后綴,給release版本增加".release"的后綴。
- 構(gòu)建類型buildTypes的繼承
buildTypes{
release{
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'),`proguard-rules.pro`
}
xxx.initWith(buildTypes.debug)
xxx{
applicationIdSuffix ".xxx"
}
}
構(gòu)建signingConfigs
Android Apk使用簽名來保證App的合法性。android系統(tǒng)有一個默認(rèn)的debug簽名,debug包會默認(rèn)使用這個debug簽名進(jìn)行簽名。那么當(dāng)你需要給其他版本設(shè)置簽名的時候,就需要自己來配置signingConfigs領(lǐng)域
生成簽名
生成簽名有兩種:
- 命令
- android studio
生成的簽名文件是xxx.jks文件。對于企業(yè)項目來說,這個key通常是存放在打包服務(wù)器上的,那么在gradle腳本中,就需要通過具體的路徑來訪問。這一點(diǎn)與訪問各種配置文件的方式是一樣的。
-
配置簽名
生成了簽名文件后,就可以在build.gradle腳本的android領(lǐng)域中配置簽名的相關(guān)參數(shù)
signingConfigs{
xxx{
storeFile file("xxx_key.jsk")
storePassword "12344567"
keyAlias "xxx"
keyPassword "1234567"
}
}
配置的信息就是前面在創(chuàng)建簽名時填寫的信息。需要注意的是,簽名信息一定要包含在一個領(lǐng)域中,你可以給這個領(lǐng)域起一個名字,例如在這里的”xxx“(通常情況下,會使用debug,release這樣的簽名)。
-
使用簽名
配置好相關(guān)的簽名信息后,就可以在構(gòu)建類型的時候加入簽名的設(shè)置。這樣生成的apk就會包含簽名版和未簽名版兩種,完整的配置如下所示。
signingConfigs{
xxx{
storeFile file("xxx_key.jsk")
storePassword "12344567"
keyAlias "xxx"
keyPassword "1234567"
}
}
buildTypes{
release{
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'),`proguard-rules.pro`
}
xxx{
signingConfig signingConfigs.xxx
applicationIdSuffix ".xxx"
}
}
Anroid領(lǐng)域中的可選配置
在Android領(lǐng)域中,還有一些可選的配置。在具體的開發(fā)場景中,開發(fā)者可以根據(jù)自己的需要進(jìn)行配置。
-
compileOptions
配置編譯的選項,類似于compileSdkVersion。不是設(shè)置Android SDK的選項,而是設(shè)置Java的編譯選項,通常可以在這里指定Java的編譯版本。
compileOptions{
sourceCompatibility Java Version.VERSION_1_8
targetCompatibility Java Version.VERSION_1_8
}
指定編譯版本,通常是為了使用某些版本中的一些語言新特性。
- lintOptions
Lint代碼檢查,這個選項打開,在編譯的時候,會因?yàn)長int的error而終止。
構(gòu)建Proguard
Proguard配置是Android的apk混淆文件配置,但它的作用絕對不僅僅是混淆代碼。他同樣可以精簡代碼、資源,優(yōu)化代碼結(jié)構(gòu)。
buildTypes{
release{
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),`proguard-rules.pro`
}
xxx{
signingConfig signingConfigs.xxx
applicationIdSuffix ".xxx"
}
}
Gradle動態(tài)參數(shù)配置
Gradle既然是一種腳本配置語言,那么它一定可以通過配置文件動態(tài)配置其編譯腳本,列入前面在配置簽名腳本時,使用的代碼如下所示。
signingConfigs{
xxx {
storeFile file("xxx_key.jsk")
storePassword "12344567"
keyAlias "xxx"
keyPassword "1234567"
}
}
使用gradle.properties文件來配置腳本的動態(tài)參數(shù)。
System.properties方式
在gradle.properties文件中添加以下配置
systemProp.keyAliasPassword=1234567
systemProp.keyAlias=xxx
systemProp.keyStorePassword=1234567
systemProp.keyStore=xxx_key.jks
這些配置實(shí)際上就是之前寫死的配置參數(shù),只不過這里把它們配置到了systemProp中,那么在build.gradle腳本進(jìn)行引用的時候,就可以通過System.properties[KEY]獲取這些參數(shù)。
signingConfigs{
xxx {
storeFile file(System.properties['keyStore'])
storePassword System.properties['keyStorePassword']
keyAlias System.properties['xxx.keyAlias']
keyPassword System.properties['xxx.keyAliasPassword']
}
}
通過project.property(Key)方法,就可以去除對應(yīng)的Value。這種方式與使用System.properties的方式基本一樣。
多渠道打包
所謂多渠道打包,實(shí)際上就是在代碼層面上標(biāo)記不同的渠道名,從而便于統(tǒng)計不同的應(yīng)用市場該apk的下載量。而且有些時候有些暴還可以以從網(wǎng)頁的外鏈接或者一些非市場的渠道進(jìn)行下載。這些都需要進(jìn)行統(tǒng)計,因此多渠道打包,變成了打包任務(wù)的重中之重。
利用Gradle進(jìn)行多渠道打包,將開發(fā)者從之前繁雜的ant打包中解放出來。Gradle的強(qiáng)大功能,將多渠道打包變得異常簡單,只需要在Gradle腳本中進(jìn)行簡單配置,即可完成多渠道打包。
-
創(chuàng)建渠道占位符
首先AndroidMainifest文件的Application節(jié)點(diǎn)下,創(chuàng)建如下所示的meta-data節(jié)點(diǎn)
<meta-data
android:name="PRODUCT"
android:value="${CHANNEL_VALUE}"/>
其中”${CHANNEL_VALUE}“就是要進(jìn)行替換的渠道占位符。
-
配置Gradle腳本
在項目的Gradle腳本的android領(lǐng)域中,添加productFlavors領(lǐng)域,并添加定義的渠道名。同時,使用manifestPlaceholders指定要替換渠道占位符的值。
productFlavors{
product1{
manifestPlaceholders=[CHANNEL_VALUE:"PRODUCT1"]
}
product2{
manifestPlaceholders=[CHANNEL_VALUE:"PRODUCT2"]
}
product3{
manifestPlaceholders=[CHANNEL_VALUE:"PRODUCT3"]
}
}
實(shí)際上除了渠道名,AndroidMainifest文件中的其他設(shè)置,同樣可以使用占位符進(jìn)行配置。只要利用manifestPlaceholders進(jìn)行替換即可,原理與多渠道類似。這一個技巧可以讓項目能夠直接在編譯腳本--build.gradle中進(jìn)行動態(tài)參數(shù)控制,便于統(tǒng)一管理。更進(jìn)一步,在Module中同樣可以進(jìn)行這些動態(tài)參數(shù)的控制。例如某些Module的封裝,需要配置一些炎癥Key作為參數(shù),如果這些Key卸載Module中,Module就是去了通用性。因此借助manifestPlaceholders,開發(fā)者可以將動態(tài)參數(shù)配置到Module中,通過主項目的manifestPlaceholders。可以參考博客
腳本優(yōu)化
productFlavors{
product1{
manifestPlaceholders=[CHANNEL_VALUE:"PRODUCT1"]
}
product2{
manifestPlaceholders=[CHANNEL_VALUE:"PRODUCT2"]
}
product3{
manifestPlaceholders=[CHANNEL_VALUE:"PRODUCT3"]
}
}
productFlavors.all{flavor->
flavor.manifestPlaceholders=[CHANNEL_VALUE:name]
}
增加的productFlavors.all領(lǐng)域?qū)λ械膒roductFlavors進(jìn)行遍歷,并使用其name作為渠道名。這些name實(shí)際上就是produce1,Produce2,produce3
生成重命名包
在生成渠道包后,包的明明通常是默認(rèn)命名,即app-渠道名-buildType.apk。但是通常情況下,項目經(jīng)理都會要求對報名進(jìn)行重命名,以滿足市場部的需求。那么這時候就可以通過Gradle腳本進(jìn)行快速重命名,而不需要再使用rename指令或者Python指令或者Python腳本進(jìn)行修改
application Variants.all{variant ->
variant.outputs.each{ output ->
if(output.outputFile != null &&
output.outputFile.name.endsWith('.apk') &&
'release'.equals(variant.buildType.name)){
def apkFile = new File(output.outputFile.getParent(),
"XXXApp_${variant.flavorName}_ver${variant.versionName}.apk")
output.outputFile = apkFile
}
}
}
將這段腳本放到android領(lǐng)域中即可,當(dāng)執(zhí)行g(shù)radle build指令時該task也會執(zhí)行,與多渠道優(yōu)化的那段代碼非常類似,它去除了所有的生成的apk包,并判斷其文件是否是apk、是否是release版本。如果是,則重新將其命名為”XXXApp_渠道名_ver版本號.apk“。代碼其實(shí)非常簡單,但難就難在對groovy語言的理解和gradle android插件的熟悉度上。很多系統(tǒng)變量和內(nèi)置變量。
為不同版本添加不同代碼
在開發(fā)中,不同的版本通常有不同的代碼功能。例如最常用的Log開關(guān),在debug版本中會打印開發(fā)日志,而在release版本中需要關(guān)閉的。因此,一般會有一個全局的變量開關(guān),根據(jù)不同的版本設(shè)置不同的值。這一切在gradle腳本的支持下,僅僅變成了一句配置。
buildTypes{
rlease{
buildConfigField "boolean","testFlag","true"
minifyEnabled true
shrinkResources ture
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
}
xxx{
buildConfigFiles "boolean","testFlag","false"
signingConfig signingConfigs.xxx
applicationIdSuffix ".xxx"
}
}
通過制定buildConfigField的三個參數(shù)--類型、名稱、值,就可以將一個變量設(shè)置到不同的buildType中去。打開系統(tǒng)的BuildConfig類,可以看到不同buildType下對應(yīng)的testFlag的值。該文件對應(yīng)的路徑為/項目/app/build/generated/source/buildConfig/(你也可以通過雙擊Shift進(jìn)行快速查找)
直接通過BuildConfig類,就可以獲取到不同buildType所對應(yīng)的值了。如果是String類型的變量,在寫入字符串的時候,需要加入轉(zhuǎn)義字符。
buildConfigField "String","myname","\"abs\""
除了Java代碼可以使用這種方式進(jìn)行添加之外,資源文件同樣可以進(jìn)行分版本設(shè)置屬性值。例如要給不同的版本設(shè)置不同的AppName
defaultConfig{
....
resVaule("string","app_name","XXXApp")
}
buildTypes{
release{
...
resVaule("string","app_name","XXXAppRelease")
}
debug{
...
resVaule("string","app_name","XXXAppDebug")
}
}
defbuildTime(){
return new Date().format("yyyy-MM-dd HH:mm:ss")
}
defaultConfig{
resValue "String","build_time",buildTime()
}
在上面的代碼中,定義了一個buildTime方法,并賦值給自定義的build_time變量。這時候不需要在Java代碼中增加變量,即可直接引用已經(jīng)編譯到R文件中的變量build_time,代碼如下:
Log.d("test",getString(R.string.build_time))
Gradle多項目依賴
- build.gradle:控制每個module的編譯過程。
- gradle.properties:設(shè)置Gradle腳本中的參數(shù)。
- local.properties:Gradle的SDK相關(guān)環(huán)境變量配置。
- settings.gradle:配置Gradle的多項目管理。
使用Gradle上傳aar到Maven庫
開發(fā)者可以將自己開發(fā)的庫項目上傳到Maven庫,供其他程序調(diào)用。上傳的方式為通過腳本進(jìn)行提交
uploadArchives{
repositories{
mavenDeployer{
pom.groupId = GROUPID
pom.artifactId = ARTIFACTID
if(System.properties['isRelease'].toBooleans()){
pom.version = VERSION
repository(url: nexusReleases){
authentication(userName:nexusUsername,password:nexusPassword)
}
}else{
pom.version = "${VERSION}-SNAPSHOT"
repository(url:nexusSnapshots){
authentication(userName:nexusUsername,password:nexusPassword)
}
}
pom.project{
descriptoin 'xxxx'
}
}
}
}
同時,還需要在gradle.properties文件中進(jìn)行參數(shù)的配置
GROUP_ID = com.xxxx.cccc
ARTIFACT_ID = aaaa
VERSION = 1.x.xxx
RELEASE_REPOSITORY_URL = maven url
nexusUsername = username
nexusPassword = password
systemProp.isRelease = true
Gradle 依賴管理
- 強(qiáng)制刷新配置
compile('com.xxx.xxx:3.0.1-SNAPSHOT@aar'){
transitive = true
}
如果增加一個屬性transitive并讓其值為true,則代表會強(qiáng)制刷新遠(yuǎn)程庫,避免遠(yuǎn)程庫更新后本地未刷新的問題。
Gradle依賴傳遞
在使用Gradle aar文件時,京城會發(fā)生這樣的情況,主項目A依賴庫項目B,庫項目B依賴庫項目C和jar包D。這時候主項目在引用庫項目B時,寫成如下所示的方式。
compile 'com.xxx.xxxx:xxxx:1.0.0-SNAPSHOT'
這樣的寫法也是一般引用庫項目的標(biāo)準(zhǔn)寫法,其表示B項目及其依賴的所有項目,即C和D。那么如果C或者D出現(xiàn)重復(fù)依賴的問題,或者主項目只想依賴庫項目B而不像依賴庫項目B所以來的項目,則可以使用@aar關(guān)鍵字關(guān)閉依賴傳遞,使用方法如下所示。
compile 'com.xxx.xxxx:xxxx:1.0.0-SNAPSHOT@aar'
如果這樣引用庫項目B,則不會進(jìn)行依賴傳遞。但要注意的是,libs目錄下的jar文件時不受影響的,開發(fā)者在使用過程中需要非常注意。
另外,還可以使用exclude module排除一個庫中引用的其他庫,例如aar庫A依賴了B和C,此時可以通過以下的方式進(jìn)行依賴。
compile('com.xxx.yyy:aaa:1.1.1'){
exclude module:'com.xxx.yyy.bbb:1.1.2'
}
傳遞依賴問題是使用Gradle時一定會遇到的問題,不僅僅是依賴傳遞的庫會沖突,而且也會發(fā)生資源沖突的問題。因此遇到Gradle編譯錯誤的時候,一定要仔細(xì)分析錯誤的原因,找到?jīng)_突的根本原因從而去解決問題
Gradle依賴統(tǒng)一管理
Gradle引用依賴非常簡單,但一旦涉及多module,每個module的依賴管理就變得非常麻煩。這就和編程中使用的變量一樣,每個module中都引用自己的依賴-局部變量,這樣就造成多個module有多個局部變量,不利于項目管理。因此,最好是使用類似全局變量的方式來進(jìn)行統(tǒng)一的管理。
在根目錄的build.gralde腳本中配置如下所示的代碼。
ext{
android=[compileSdkVersion:23,
buildToolsVersion:'23.0.2']
dependencies = [supportv7:'com.android.support:appcompat-v7:23.2.0']
}
在全局Gradle腳本中,指定了android和dependencies兩個列表,并在其中配置了統(tǒng)一的參數(shù)和對應(yīng)的值。這樣在每個module中,一顆通過代碼使用全局的依賴配置。
android{
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.android.buildToolsVersion
}
dependencies{
compile rootProject.ext.dependencies.supportv7
}
更進(jìn)一步,開發(fā)者還可以把這些全局參數(shù)抽取出來,寫到一個單獨(dú)的配置文件中。例如,比這在項目根目錄下創(chuàng)建一個config.gradle文件,并寫入如下所示的代碼
ext{
android = [compileSdkVersin:23,
buildToolsVersion:'23.0.2']
dependencies=[supportv7:'com:android.support:appcompat-v7:23.2.0']
}
這里就要把ext全局參數(shù)抽取出來了。下一步,在根目錄下的build.grdle文件中,使用代碼加載這個配置文件,代碼如下所示。
apply from:'config.gradle'
這樣就可以在所有的子module中使用這些參數(shù)了,通過這種統(tǒng)一的依賴管理方式,可以統(tǒng)一所有module的依賴配置,避免使用不同版本的依賴庫而導(dǎo)致的沖入,而且也利于項目的管理。
Gradle使用技巧
- Debug模式禁用掉Lint
./gradlew build -x lint
,其中-x參數(shù)表示排除掉一個Task,即Lint。通過這種方式可以實(shí)現(xiàn)禁止Lint的執(zhí)行。 - Debug模式禁用AAPT
aaptOptions.cruncherEnabled = false
。 - 官方文檔
Gradle加速
Gradle在編譯時會執(zhí)行大量的Task,同時生成很多中間文件。因此磁盤IO會造成編譯速度緩慢。解決該問題的最好辦法就是為電腦更換固態(tài)硬盤,增加磁盤的IO速度。同時盡量減少本地庫項目的依賴,多實(shí)用aar進(jìn)行依賴。
在gradle.properties文件中增加如下代碼
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.configureondemand=true
同時,在build.gradle中增加如下代碼
dexOptions{
incremental true
javaMaxHeapSize "4g"
}
gradle.properties文件中的代碼,表示開啟Gradle的多線程和多核心支持。而build.gradle中的代碼,表示開啟Gradle的增量編譯,增加編譯的內(nèi)存資源的4G。
Gradle自定義插件
Gradle提供了強(qiáng)大的插件自定義功能,可以在某些情況下通過自定義插件實(shí)現(xiàn)自己的一些功能。官方文檔
在Gradle中創(chuàng)建自定義插件,Gradle提供了以下三種方式。
- 在build.gradle腳本中直接使用
- 在buildSrc中使用
- 在獨(dú)立Module中使用
Gradle插件可以在IDEA中進(jìn)行開發(fā),也開一在Android Studio中進(jìn)行研發(fā)。他們唯一不同就是IDEA提供了Gradle開發(fā)的插件,比較方便創(chuàng)建文件和目錄。而在Android studio中,開發(fā)者需要手動創(chuàng)建。
Gradle 打包配置
編譯時報重復(fù)文件的錯誤
// 1. pickFirsts:當(dāng)出現(xiàn)重復(fù)文件,會使用第一個匹配的文件打包進(jìn)入。
// 2. merges:當(dāng)出現(xiàn)重復(fù)文件,合并重復(fù)的文件打入APK,兩個文件會進(jìn)行拼接
// 3. excludes:打包的時候排除匹配的文件
packagingOptions {
pickFirst 'lib/armeabi-v7a/libyuv.so'
merge 'lib/armeabi-v7a/libyuv.so'
exclude 'lib/armeabi-v7a/libyuv.so'
}