Gradle
Gradle是一種項(xiàng)目構(gòu)建工具痛黎,說(shuō)白了就是對(duì)項(xiàng)目進(jìn)行配置棘街,告訴編譯器怎么編譯項(xiàng)目的管理工具。面向Java應(yīng)用為主移必。當(dāng)前其支持的語(yǔ)言限于Java室谚、Groovy、Kotlin和Scala崔泵,計(jì)劃未來(lái)將支持更多的語(yǔ)言秒赤。
Gradle is an open-source build automation tool focused on flexibility and performance. Gradle build scripts are written using a Groovy or Kotlin DSL.
Gradle現(xiàn)在主要的呈現(xiàn)形式是Groovy語(yǔ)言,鑒于Google對(duì)Kotlin的大力推崇管削,和Kotlin更加靈活的特性(沒(méi)有最靈活倒脓,只有更靈活),Gradle也支持用Kotlin編寫(xiě)含思,雖然崎弃,目前某些API的兼容還不是那么友好。
對(duì)于學(xué)生含潘,比如我饲做,Maven就夠用了,除非Android強(qiáng)制的Gradle構(gòu)建遏弱。但是在大型項(xiàng)目構(gòu)建中盆均,Gradle更加順手,雖然我沒(méi)做過(guò)啥大型項(xiàng)目??漱逸。寫(xiě)這篇的目的是講解Android中的build.gradle泪姨,方便能看懂。
背景
build.gradle的背景知識(shí)可以去官方文檔饰抒,這里詳細(xì)介紹了如何安裝配置Gradle肮砾,以及如何通過(guò)gradle構(gòu)建一個(gè)項(xiàng)目等等。
API
官方文檔袋坑,這里詳細(xì)介紹了Gradle的API仗处,想要細(xì)致了解的也可以參考。
Gradle腳本
Gradle一般會(huì)產(chǎn)生兩個(gè)腳本文件,settings.gradle和build.gradle婆誓。settings.gradle包含項(xiàng)目的模塊信息吃环,build.gradle包含項(xiàng)目(模塊)的構(gòu)建配置細(xì)節(jié)。每個(gè)腳本文件都對(duì)應(yīng)一個(gè)實(shí)例對(duì)象洋幻,settings.gradle對(duì)應(yīng)Setting郁轻,build.gradle對(duì)應(yīng)Project。一個(gè)項(xiàng)目的構(gòu)建過(guò)程遵循以下的生命周期:
- 創(chuàng)建Settings對(duì)象鞋屈,默認(rèn)配置范咨;
- 要是有settings.gradle腳本故觅,覆蓋配置Settings對(duì)象厂庇;
- 根據(jù)Settings對(duì)象,建立Project實(shí)例們的層次性(比如多模塊)输吏;
- 最后权旷,基于寬度優(yōu)先的原則,通過(guò)執(zhí)行build.gradle配置構(gòu)建每個(gè)Project贯溅,當(dāng)然拄氯,可以通過(guò)
Project.evaluationDependsOnChildren()
或者Project.evaluationDependsOn(java.lang.String)
重載改變這種默認(rèn)的構(gòu)建順序。
Gradle腳本由以下元素組成Plugins它浅、Tasks译柏、Deplendencies,Properties和Methods姐霍。
實(shí)例分析
apply plugin: 'com.android.application'
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.8.5"
}
def localPropertiesFile = project.rootProject.file('signing.properties')
def useLocalSigning = localPropertiesFile.exists()
Properties localProperties = null
if (useLocalSigning) {
localProperties = new Properties()
localProperties.load(localPropertiesFile.newDataInputStream())
}
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
signingConfigs {
gbDistribution {
if (useLocalSigning) {
storeFile file(localProperties.getProperty('signing.storeFile'))
keyAlias localProperties.getProperty('signing.keyAlias')
storePassword localProperties.getProperty('signing.storePassword')
keyPassword localProperties.getProperty('signing.keyPassword')
}
}
}
defaultConfig {
applicationId "com.genonbeta.TrebleShot"
minSdkVersion 14
targetSdkVersion 28
versionCode 98
versionName "1.4.2"
testInstrumentationRunnerArguments clearPackageData: 'true'
vectorDrawables {
useSupportLibrary = true
}
}
lintOptions {
checkReleaseBuilds false
abortOnError false
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
testCoverageEnabled = true
applicationIdSuffix '.debug'
versionNameSuffix '-DEBUG'
}
}
flavorDimensions 'mode'
productFlavors {
fossReliant {
dimension 'mode'
signingConfig signingConfigs.gbDistribution
}
googlePlay {
dimension 'mode'
signingConfig signingConfigs.gbDistribution
}
}
}
task jacocoTestReport(type: JacocoReport) {
group = "Reporting"
description = "Generate Jacoco coverage reports after running tests."
reports {
xml.enabled = true
html.enabled = true
csv.enabled = true
}
classDirectories.from = fileTree(
dir: './build/intermediates/javac/fossReliantDebug/compileFossReliantDebugJavaWithJavac',
excludes: ['**/*$InjectAdapter.class',
'**/*$ModuleAdapter.class',
'**/*$ViewInjector*.class',
'**/R.class', '**/R$*.class'
])
sourceDirectories.from = files('../app/src/main/java')
executionData.from = files("$buildDir/outputs/code-coverage/connected/coverage.ec")
doFirst {
new File("$buildDir/intermediates/javac/fossReliantDebug/compileFossReliantDebugJavaWithJavac").eachFileRecurse { file ->
if (file.name.contains('$$')) {
file.renameTo(file.path.replace('$$', '$'))
}
}
}
}
task copyHebrewResources(type: Copy) {
File mergedResourcesDir = new File("${buildDir}/mergedResources")
if (!mergedResourcesDir.isDirectory())
mergedResourcesDir.mkdirs()
for (String resEach : android.sourceSets.main.res.srcDirs) {
File currentFolder = new File(resEach)
for (File folderContents : currentFolder.listFiles()) {
if (folderContents.name.endsWith("-he")) {
String iwNamedFolder = "${folderContents.name.substring(0, folderContents.name.lastIndexOf("-he"))}-iw"
from folderContents.path
into "${mergedResourcesDir.path}/${iwNamedFolder}"
println("Copying resource ${folderContents.path}")
}
}
}
println("Adding merged resource directories to the resources array ${mergedResourcesDir.path}")
android.sourceSets.main.res.srcDirs += mergedResourcesDir.path
}
preBuild.dependsOn copyHebrewResources
dependencies {
testImplementation 'junit:junit:4.12'
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.appcompat:appcompat:1.0.2'
annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
googlePlayImplementation 'com.anjlab.android.iab.v3:library:1.0.44'
}
這是來(lái)自一個(gè)Android應(yīng)用的部分build.gradle代碼鄙麦。
Plugins
Plugin在腳本中的意義類(lèi)似常規(guī)編程中的import導(dǎo)包。除了Groovy和Gradle默認(rèn)導(dǎo)入的基本API镊折,其他的胯府,比如android{...},都需要通過(guò)Plugin導(dǎo)入恨胚÷钜颍可以通過(guò)PluginAware.apply(java.util.Map)或者PluginDependenciesSpec的方式導(dǎo)入插件包。這里的Map中的鍵不是隨意設(shè)置的赃泡,支持from寒波、plugin、to升熊,每次apply類(lèi)似傳進(jìn)去一句import指令俄烁,將插件中的API委托給當(dāng)前的腳本對(duì)象。支持導(dǎo)入的插件類(lèi)型應(yīng)該是和Gradle版本綁定的僚碎,比如Android的Gradle插件猴娩,與Gradle版本之間就存在著兼容關(guān)系。
//PluginAware.apply(java.util.Map)
apply plugin: 'com.android.application'
apply plugin: 'jacoco'
//等價(jià)于
//PluginDependenciesSpec
plugins{
id 'com.android.application'
id 'jacoco'
}
Properties && Methods
在腳本中,最復(fù)雜的就是各種properties和methods卷中。Gradle中的屬性根據(jù)來(lái)源和功能被劃分到5個(gè)集合中矛双,按照Project對(duì)象的檢索順序依次為:
-
Project Properties,腳本默認(rèn)自帶屬性一系列的項(xiàng)目屬性蟆豫,比如name等议忽。可以直接通過(guò)屬性名訪問(wèn)十减,也可以通過(guò)
project.屬性名
的方式訪問(wèn)栈幸,本質(zhì)上沒(méi)區(qū)別。這些屬性的讀寫(xiě)依賴(lài)于對(duì)應(yīng)的set/get方法帮辟,類(lèi)似Spring等框架中常見(jiàn)的自動(dòng)注入速址; - Extra Properties,這是項(xiàng)目于級(jí)維護(hù)的屬性由驹,便于各模塊間交互芍锚,可以在project.ext這個(gè)namespace中進(jìn)行自定義屬性的鍵值對(duì)的聲明、讀取和更新蔓榄;
- Extensions Properties并炮,插件導(dǎo)入的只讀屬性,這些屬性與extension同名甥郑;
- Convention Properties逃魄,這些屬性是插件通過(guò)Project對(duì)象下的Convention對(duì)象傳遞給腳本的,其讀寫(xiě)操作依賴(lài)于Convention對(duì)象澜搅;
-
Tasks Properties伍俘,我們可以通過(guò)
task名.屬性名
的方式訪問(wèn)自定義或預(yù)定義的task的屬性,當(dāng)然店展,這種訪問(wèn)是只讀的养篓。
note:在本級(jí)project中檢索不到時(shí),會(huì)遞歸向上赂蕴,一直到root project柳弄,檢索extra properties或convention properties,只讀概说。
Gradle中的Methods集也分為5個(gè)碧注,而且,范圍和檢索順序基本和Properties類(lèi)似:
- Project對(duì)象自帶Methods糖赔;
- build腳本中自定義的Methods萍丐;
- Convention Methods;
- Tasks放典,Gradle會(huì)為每個(gè)Task建立一個(gè)同名Method逝变,其參數(shù)一般是Closure或Action基茵。
note:
- 在本級(jí)project中檢索不到時(shí),會(huì)遞歸向上壳影,一直到root project拱层,檢索需要的method;
- 以閉包為值得屬性宴咧,在Method檢索時(shí)根灯,會(huì)被作為Method看待。
jacoco {
toolVersion = "0.8.5"
//<==>setToolVersion("0.8.5") 最終調(diào)用
//<==>setToolVersion "0.8.5" 快捷寫(xiě)法
//<==>toolVersion "0.8.5" 不規(guī)則但可編譯
}
//<==>jacoco.toolVersion="0.8.5"
//<==>jacoco.setToolVersion("0.8.5")
jacoco是通過(guò)jacoco插件添加的屬性掺栅,上面的例子中烙肺,我們對(duì)其中的toolVersion屬性進(jìn)行了配置。由于Delegate氧卧,我們可以直接訪問(wèn)屬性對(duì)象內(nèi)部的成員屬性或方法桃笙,進(jìn)行封裝后,我們既可以通過(guò)閉包
進(jìn)行批量的配置假抄,也可以通過(guò)對(duì)象名.屬性名
怎栽,進(jìn)行單獨(dú)配置。直接通過(guò)屬性名訪問(wèn)的方式宿饱,使腳本更加易讀和美觀。雖然脚祟,無(wú)論怎么寫(xiě)谬以,都可以達(dá)到目的。
def localPropertiesFile = project.rootProject.file('signing.properties')
//<==>def localPropertiesFile = rootProject.file('signing.properties')
project
是內(nèi)置的屬性由桌,在不沖突混淆的情況下为黎,可以省略。
compileSdkVersion 28
//<==>compileSdkVersion(28)
通常行您,對(duì)屬性或方法的檢索順序是铭乾,屬性集->同名Method->同名get/set方法->上一級(jí)。BaseExtension中有setCompileSdkVersion和getCompileSdkVersion方法娃循,但是沒(méi)有compileSdkVersion這個(gè)屬性炕檩,所以會(huì)檢索到compileSdkVersion(String version)這個(gè)方法調(diào)用。雖然通過(guò)屬性名=屬性值
或者屬性名 屬性值
的格式經(jīng)過(guò)語(yǔ)法兼容后捌斧,都能進(jìn)行有效的配置笛质,但還是分類(lèi)配置比較好,有同名屬性或者set方法的捞蚂,通過(guò)=賦值妇押,有同名函數(shù)的通過(guò)快捷函數(shù)調(diào)用賦值,這樣經(jīng)過(guò)IDE的代碼格式化后看起來(lái)會(huì)比較清晰姓迅。
Tasks
task jacocoTestReport(type: JacocoReport){...}
//<==>task([type:JacocoReport],"jacocoTestReport",{...})
//<==> tasks.create("jacocoTestReport",JacocoReport.class,{...})
前面的配置還是比較容易理解的敲霍,這行代碼的原型就比較難懂了俊马。這是Gradle提供的快捷寫(xiě)法之一,在理解這行代碼原始形態(tài)的過(guò)程中肩杈,我也經(jīng)歷了比較多的猜測(cè)和推翻:
- Task構(gòu)造函數(shù)潭袱,通過(guò)命名參數(shù)的特性以及快捷寫(xiě)法最終導(dǎo)致這樣,但是這是通過(guò)task函數(shù)創(chuàng)建Task對(duì)象锋恬,并不是Task的構(gòu)造函數(shù)屯换,所以這種猜測(cè)被否定;
- Task名二次調(diào)用与学,先通過(guò)Task task(String name)創(chuàng)建Task對(duì)象彤悔,然后通過(guò)命令鏈、參數(shù)封裝等特性進(jìn)行二次配置索守,后來(lái)發(fā)現(xiàn)不存在二次調(diào)用的語(yǔ)法晕窑,這種猜測(cè)也被否定;
- Task task(Map, String, Closure)卵佛,Groovy有將鍵值參數(shù)封裝成Map作為第一個(gè)函數(shù)參數(shù)的特性杨赤,Gradle對(duì)Task的特殊封裝,jacocoTestReport既是Task的變量名截汪,也是Task名疾牲,允許不加引號(hào)定義,Groovy的快捷寫(xiě)法中衙解,允許參數(shù)表末的閉包位于括號(hào)外阳柔,所以,最終覺(jué)得還是這種猜測(cè)比較靠譜蚓峦。
配置閉包中的操作舌剂,對(duì)屬性和方法的訪問(wèn)也遵循和Project級(jí)別類(lèi)似的順序,由內(nèi)向外暑椰,由下向上霍转。
classDirectories.from = fileTree(...)
//classDirectories = fileTree(
classDirectories是JacocoReport中的屬性,具體的賦值因Gradle插件版本而異一汽,老版本是直接賦值避消,新版本通過(guò)from賦值。
fileTree(
dir:...
excludes:...
)
這是文件集合的快捷函數(shù)角虫,參數(shù)通過(guò)Map的形式導(dǎo)入沾谓,創(chuàng)建ConfigurableFileTree對(duì)文件集進(jìn)行規(guī)則配置,常用的配置參數(shù)有dir戳鹅、excludes均驶、includes、builtBy等枫虏。
**/R$*.class
在配置excludes等參數(shù)時(shí)妇穴,通常會(huì)用到Ant Path路徑匹配規(guī)則爬虱,乍一看和正則表達(dá)式還是挺像的。
? matches one character
* matches zero or more characters
** matches zero or more directories in a path
比如例子中展示的就是匹配所有R類(lèi)中的內(nèi)嵌類(lèi)腾它。
doFirst{...}
doFirst是Task中的流程函數(shù)跑筝,通過(guò)這一類(lèi)的函數(shù),用戶可以進(jìn)行task的前置后置等操作瞒滴。
preBuild.dependsOn copyHebrewResources
preBuild是Task名曲梗,通過(guò)dependsOn等函數(shù),可以改變Task的執(zhí)行順序妓忍。
Dependencies
dependencies {
testImplementation 'junit:junit:4.12'
implementation 'androidx.cardview:cardview:1.0.0'
googlePlayImplementation 'com.anjlab.android.iab.v3:library:1.0.44'
annotationProcessor 'androidx.annotation:annotation:1.0.2'
}
依賴(lài)就是將應(yīng)用在編譯過(guò)程需要的各種第三方包添加到項(xiàng)目庫(kù)的操作虏两,除了task,也就這個(gè)部分最容易出現(xiàn)問(wèn)題世剖,主要就是要求的依賴(lài)下載不下來(lái)的問(wèn)題定罢,可以參考另一篇文章查看對(duì)應(yīng)的解決方案。API上的差異旁瘫,Gradle 3.x以前的compile被廢棄祖凫,分離為api和implementation,將依賴(lài)程度細(xì)化了酬凳。
- api惠况,等同于compile,當(dāng)前項(xiàng)目依賴(lài)的包粱年,可以被依賴(lài)當(dāng)前項(xiàng)目的項(xiàng)目訪問(wèn)售滤,即,可導(dǎo)出式依賴(lài)台诗;
- implemention,內(nèi)部依賴(lài)赐俗,外部項(xiàng)目無(wú)法訪問(wèn)拉队,所以優(yōu)先使用implemention進(jìn)行依賴(lài)。
庫(kù)包名稱(chēng)格式阻逮,與Maven的大同小異粱快,groudId:artifactId:version
,Maven倉(cāng)庫(kù)不僅提供Maven格式的依賴(lài)導(dǎo)入叔扼,也提供Gradle格式的導(dǎo)入事哭。
Dependencies部分可繁可簡(jiǎn),對(duì)于不同類(lèi)型的庫(kù)包選用的API也不一樣瓜富, 比如鳍咱,注解包就是通過(guò)annotationProcessor進(jìn)行依賴(lài)。特殊情況在這些庫(kù)包的引導(dǎo)頁(yè)會(huì)有依賴(lài)導(dǎo)入樣例与柑,或者可以參考官方文檔學(xué)習(xí)谤辜。
參考文章
Gradle Build Language Reference:https://docs.gradle.org/current/dsl/