Android Studio——Gradle與build.gradle

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译柏、DeplendenciesPropertiesMethods姐霍。

實(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ù)一般是ClosureAction基茵。

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被廢棄祖凫,分離為apiimplementation,將依賴(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:versionMaven倉(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 Referencehttps://docs.gradle.org/current/dsl/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蓄坏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子丑念,更是在濱河造成了極大的恐慌涡戳,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脯倚,死亡現(xiàn)場(chǎng)離奇詭異渔彰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)推正,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)恍涂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人舔稀,你說(shuō)我怎么就攤上這事乳丰。” “怎么了内贮?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵产园,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我夜郁,道長(zhǎng)什燕,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任竞端,我火速辦了婚禮屎即,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘事富。我一直安慰自己技俐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布统台。 她就那樣靜靜地躺著雕擂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贱勃。 梳的紋絲不亂的頭發(fā)上井赌,一...
    開(kāi)封第一講書(shū)人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音贵扰,去河邊找鬼仇穗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛戚绕,可吹牛的內(nèi)容都是我干的纹坐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼列肢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼恰画!你這毒婦竟也來(lái)了宾茂?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拴还,失蹤者是張志新(化名)和其女友劉穎跨晴,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體片林,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡端盆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了费封。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焕妙。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖弓摘,靈堂內(nèi)的尸體忽然破棺而出焚鹊,到底是詐尸還是另有隱情,我是刑警寧澤韧献,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布末患,位于F島的核電站,受9級(jí)特大地震影響锤窑,放射性物質(zhì)發(fā)生泄漏璧针。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一渊啰、第九天 我趴在偏房一處隱蔽的房頂上張望探橱。 院中可真熱鬧,春花似錦绘证、人聲如沸隧膏。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)私植。三九已至,卻和暖如春车酣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背索绪。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工湖员, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瑞驱。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓娘摔,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親唤反。 傳聞我的和親對(duì)象是個(gè)殘疾皇子凳寺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355