中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

作為 Android 開發(fā)者必須了解的 Gradle 知識(shí)

2018-07-20    來源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢(shì)上線!快速搭建集群,上萬Linux鏡像隨意使用

在Android開發(fā)中,很多時(shí)候我們不需要修改 *.gradle 文件太多,我們添加依賴、修改target compile、最低支持API level,或者修改簽名配置和build類型。其它更復(fù)雜一些邏輯,我們最后可能就是從Stack Overflow中copy了一些自己也不太懂的代碼。本文中我們將一步一步介紹Android工程中用到的gradle文件及其背后的原理。

1. Groovy

1.1 語法

Gradle文件其實(shí)是用Groovy腳本寫的,我們都會(huì)寫java,所以入門Groovy非常簡(jiǎn)單。首先我們需要了解一下幾點(diǎn):

1.調(diào)用至少包含一個(gè)參數(shù)的方法時(shí)不需要使用括號(hào):

def printAge(String name, int age) {
    print("$name is $age years old")
}

def printEmptyLine() {
    println()
}

def callClosure(Closure closure) {
    closure()
}

printAge "John", 24 // Will print "John is 24 years old"
printEmptyLine() // Will, well, print empty line
callClosure { println("From closure") } // Will print "From closure"

2.如果方法的最后一個(gè)參數(shù)是閉包(或者說是lambda表達(dá)式),可以寫在括號(hào)外(注:這個(gè)特性很重要,gradle文件中的很多配置其實(shí)都是參數(shù)為閉包的方法):

def callWithParam(String param, Closure<String> closure) {
    closure(param)
}

callWithParam("param", { println it }) // Will print "param"
callWithParam("param") { println it } // Will print "param"
callWithParam "param", { println it } // Will print "param"

3.對(duì)于Groovy方法中命名過的參數(shù),會(huì)被轉(zhuǎn)移到一個(gè)map中做為方法的第一個(gè)參數(shù),那些沒有命名的參數(shù)則加在參數(shù)列表之后:

def printPersonInfo(Map<String, Object> person) {
    println("${person.name} is ${person.age} years old")
}

def printJobInfo(Map<String, Object> job, String employeeName) {
    println("$employeeName works as ${job.title} at ${job.company}")
}

printPersonInfo name: "John", age: 24
printJobInfo "John", title: "Android developer", company: "Tooploox"

這段程序會(huì)打印“John is 24 years old”和“John works as Android developer at Tooploox”,方法調(diào)用的參數(shù)可以是亂序的,map會(huì)被作為第一個(gè)參數(shù)傳入!這里的方法調(diào)用也省略了括號(hào)。

1.2 閉包

閉包是一個(gè)非常重要的特性,需要解釋一下。閉包可以理解為lambada。他們是一段可以被執(zhí)行的代碼,可以有參數(shù)列表和返回值。我們可以改變一個(gè)閉包的委托:

class WriterOne {
    def printText(str) {
        println "Printed in One: $str"
    }
}

class WriterTwo {
    def printText(str) {
        println "Printed in Two: $str"
    }
}

def printClosure = {
    printText "I come from a closure"
}

printClosure.delegate = new WriterOne()
printClosure() // will print "Printed in One: I come from a closure
printClosure.delegate = new WriterTwo()
printClosure() // will print "Printed in Two: I come from a closure

我們可以看到 printClosure 調(diào)用了不同委托的 printText 方法,之后會(huì)解析這個(gè)特性在gradle中的重要性。

2. Gradle

2.1 腳本文件

有三個(gè)主要的gradle腳本,每個(gè)都是一個(gè)代碼塊。

  • build.gradle 文件,針對(duì) Project 對(duì)象
  • settings.gradle文件,針對(duì) Settings 對(duì)象
  • 全局配置的初始化gradle腳本,針對(duì) Gradle 實(shí)例

2.2 Projects

gradle 構(gòu)建一般包含多個(gè)Project(在Android中每個(gè)module對(duì)應(yīng)這里的project),project中包含tasks。一般至少有一個(gè)root project,包含很多subprojects,subproject也可以嵌套project(注:Android 中對(duì)應(yīng)每個(gè)library module還可以依賴其它library module)。

3. 構(gòu)建基于Gradle的Android工程

Android工程中我們一般有如下的結(jié)構(gòu):

1是root project的setting文件,被 Settings 執(zhí)行

2是root project的build配置

3是App project的屬性文件,會(huì)被注入到 App的 Settings 中

4是App project的build配置

3.1 創(chuàng)建gradle工程

我們新建一個(gè)文件夾,命名為 example , cd 進(jìn)入后執(zhí)行 gradle projects 命令,之后就已經(jīng)擁有一個(gè)gradle project了:

$ gradle projects
:projects
------------------------------------------------------------
Root project
------------------------------------------------------------
Root project 'example'
No sub-projects
To see a list of the tasks of a project, run gradle <project-path>:tasks
For example, try running gradle :tasks
BUILD SUCCESSFUL
Total time: 0.741 secs

3.2 配置projects層級(jí)

如果我們要建立一個(gè)默認(rèn)的Android project(空的root project和一個(gè)包含Application的app project),我們就需要配置 settings.gradle , the documentation 中介紹 settings.gradle :

聲明需要實(shí)例化的配置和build的project的層級(jí)體系配置

我們通過void include( String [] projectPaths)方法來添加projects:

這里的冒號(hào) : 用于分隔子project,可以參考這里 here 。因此我們?cè)谶@里寫 :app , 而不是直接寫 app 。

在 settings.gradle 中寫 rootProject.name = <<name>> 也是一個(gè)比較好的實(shí)踐。如果沒有寫,那么root project 的默認(rèn)名字就是project所在文件夾的名字。

3.3 配置Android 子project

我們已經(jīng)配置了root project的 build.gradle ,現(xiàn)在來看看如何配置Android project。

從 user guide 可以知道我們首先要為app project配置 com.android.application 插件,我們來看看 apply 方法:

void apply(Closure closure)
void apply(Map<String, ?> options)
void apply(Action<? super ObjectConfigurationAction> action)

盡管第三個(gè)方法很重要,我們通常使用是第二個(gè)方法,它用到我們之前提到的特性,通過map來傳遞參數(shù)。通過文檔我們可以查看可以使用哪些參數(shù):

void apply(Map(<String, ?> options)

以下是可用的參數(shù):

from: 可以引入一個(gè)腳本apply(...),如 apply from: "bintray.gradle" 從而導(dǎo)入一個(gè)可用腳本。

plugin: apply的plugin的id或者實(shí)現(xiàn)類

to: 委托目標(biāo)

我們知道需要傳遞一個(gè)id值作為 plugin 的參數(shù),可以寫作: apply(plugin:'com.android.application') ,這里的括號(hào)也可以省略,我們?cè)赼pp的 build.gradle 中配置:

命令行中運(yùn)行:

報(bào)錯(cuò)了,找不到 com.android.application 的定義,這不奇怪,我們并沒有配置,但是gradle是如何查找Android的plugin jar包呢?在 user guide 可以找到答案,我們需要配置plugin的路徑。

現(xiàn)在我們可以在root project或者app的 build.gradle 中配置路徑,但是因?yàn)?buildscript 閉包是 ScriptHandler 執(zhí)行的,其它子project也需要使用,因此最好配置在root project的 build.gradle 中:

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.0-beta2'
    }
}

如果我們?cè)谏线叺拇a中添加括號(hào),那么就會(huì)發(fā)現(xiàn)其實(shí)都是帶有閉包參數(shù)的方法調(diào)用。如果我們研究下 文檔 ,我們就可以知道是有哪些對(duì)象執(zhí)行這些閉包的,總結(jié)如下:

buildscript(Closure) 是 Project 實(shí)例中調(diào)用的,傳遞的閉包的由 ScriptHandler 執(zhí)行

repositories(Closure) 是在 ScriptHandler 實(shí)例中調(diào)用,傳遞的閉包由 RepositoryHandler 執(zhí)行

dependencies(Closure) 是在 ScriptHandler 實(shí)例中調(diào)用,傳遞的閉包由 DependencyHandler 執(zhí)行。

也就是說 jcenter() 是由 RepositoryHandler 調(diào)用

classpath(String) 是由 DependencyHandler(*) 調(diào)用

譯者注:如果這里看不懂的同學(xué),可以再回頭看看groovy的語法部分,其實(shí)這里上邊的代碼都是方法,如buildscript是Project的方法,我們知道groovy語法中如果最后一個(gè)參數(shù)是閉包的話,可以不寫括號(hào)。

如果查看 DependencyHandler 的代碼,我們會(huì)發(fā)現(xiàn)其實(shí)沒有 classpath 這個(gè)方法,這是一種特殊的調(diào)用,我們?cè)谏院笥懻摗?/p>

3.4 配置Android subproject

如果我們現(xiàn)在執(zhí)行Gradle task,依然有錯(cuò)誤:

顯然,我們還沒有設(shè)置Android相關(guān)的配置,但是我們的Android plugin已經(jīng)可以被正確apply了,我們?cè)黾右恍┡渲茫?/p>

android {
   buildToolsVersion "25.0.1"
   compileSdkVersion 25
}

到這里我們知道,android方法被加入到了 Project 實(shí)例中,閉包傳遞給了delegate(這里是AppExtension),定義了 buildToolsVersion 和 compileSdkVersion 方法,Android plugin使用這種方式接收所有的配置,包括default configuration,flavors等等。

想要執(zhí)行g(shù)radle task,還需要兩個(gè)文件: AndroidManifest.xml 和 local.properties , local.properties 中配置 sdk.dir ,(或者在系統(tǒng)環(huán)境中配置 ANDROID_HOME ),指向Android SDK的位置。

3.5 擴(kuò)展

android 方法是如何出現(xiàn)在 Project 實(shí)例中的呢,還有我們的build.gradle是怎樣被執(zhí)行的?簡(jiǎn)單的說,Android plugin 用android這個(gè)名字注冊(cè) AppExtension 類為 extension 。這個(gè)超出了本文的范圍,但是我們要知道Gradle可以為每一個(gè)注冊(cè)過的 plugin增加閉包配置。

3.6 依賴

還有一個(gè)重要的部分,dependencies還沒有討論:

dependencies {
    compile 'io.reactivex.rxjava2:rxjava:2.0.4'
    testCompile 'junit:junit:4.12'
    annotationProcessor 'org.parceler:parceler:1.1.6'
}

為什么這里特殊呢,因?yàn)槿绻榭?DependencyHandler ,也就是執(zhí)行這個(gè)閉包的委托,它是沒有 compile , testCompile 等方法的。這個(gè)問題是有意義的,如果我們隨意增加一個(gè) freeCompile 'somelib' ,可以嗎? DependencyHandler 不會(huì)定義所有的方法,其實(shí)這里涉及到Groory語音的另一個(gè)特性: methodMissing ,這允許在運(yùn)行時(shí)catch對(duì)于未定義方法的調(diào)用。

實(shí)際上Gradle使用了 MethodMixIn 中聲明的 methodMissing ,類似的機(jī)制在為定義的屬性中也是一樣的。

相關(guān)的dependency操作可以在 這里 找到,它的行為如下:

如果未定義方法的調(diào)用方有至少一個(gè)參數(shù),如果存在 configuration() 與被調(diào)用方法有相同的名字,那么就根據(jù)參數(shù)的類型和數(shù)量,調(diào)用具有相關(guān)參數(shù)的 doAdd 方法。

每個(gè)plugin都可以增進(jìn)configuration到dependencies handler中,如Android插件增加了 compile, compileClasspath, testCompile 和一些其它配置 here ,Android 插件還增加了 annotationProcessor 配置,根據(jù)不同build類型和產(chǎn)品形式還有 <variant>Compile, <variant>TestCompile 等等。

由于 doAdd 是私有方法,一次這里調(diào)用的是公有的 add 方法,我們可以重寫上邊的代碼,但最后不要這樣做:

dependencies {
    add('compile', 'io.reactivex.rxjava2:rxjava:2.0.4')
    add('testCompile', 'junit:junit:4.12')
    add('annotationProcessor', 'org.parceler:parceler:1.1.6')
}

3.7 Flavors, build types, signing configs

我們看以下代碼:

productFlavors {
    prod {

    }

    dev {
        minSdkVersion 21
        multiDexEnabled true
    }
}

如果我們查看源碼,可以發(fā)現(xiàn)productFlavors是這樣聲明的:

void productFlavors(Action<? super 
NamedDomainObjectContainer<ProductFlavorDsl>> action) {
    action.execute(productFlavors)    
}

Action<T> 是Gradle中定義的由 T 執(zhí)行的閉包

所有這里我們有了 NamedDomainObjectContainer , NamedDomainObjectContainer 可以創(chuàng)建和配置多個(gè) ProductFlavorDsl 類型的對(duì)象,并根據(jù) ProductFlavorDsl 的名字保存 ProductFlavorDsl 。

這個(gè)容器可以使用動(dòng)態(tài)方法創(chuàng)建指定類型的對(duì)象(這里的ProductFlavorDsl),并和名字一起存放在容器中,所以當(dāng)我們使用 {} 參數(shù)調(diào)用 prod 方法時(shí),他被 productFlavors 實(shí)例執(zhí)行,執(zhí)行說明如下:

NamedDomainObjectContainer 獲取到被調(diào)用方法的名字,生成 ProductFlavorDsl 對(duì)象,配置給定的閉包,保存方法名字到新的配置 ProductFlavorDsl 的映射。

Android plugin可以從 productFlavors 中獲取 ProductFlavorDsl ,我們可以把它作為屬性進(jìn)行訪問: productFlavors.dev ,這樣我們就可以拿到名字為 dev 的 ProductFlavorDsl ,這也是我們可以寫 signingConfig signingConfigs.debug 的原因。

4. 總結(jié)

對(duì)于Android開發(fā)者來說,Gradle文件是非常常用的,并不是什么黑魔法。但是Gradle有很多約定,而且使用Groovy語言也增加了一些復(fù)雜性,知道這兩點(diǎn),Gradle并不是什么魔法。希望了解通過這篇文章介紹的內(nèi)容,即使是從stackoverflow中粘貼代碼,也能知道它背后的意義。

這是一篇譯文,原文作者對(duì)Android的gradle進(jìn)行了比較深入的介紹,希望各位同學(xué)可以真正了解我們常用的gradle文件背后的原理,而不僅僅是簡(jiǎn)單地配置gralde。文中有些不太容易理解的地方,可以根據(jù)文中給出的鏈接了解更多內(nèi)容。

 

來自:http://www.jianshu.com/p/c31513f5f550

 

標(biāo)簽: 代碼 腳本 開發(fā)者

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:Android性能優(yōu)化之被忽視的優(yōu)化點(diǎn)

下一篇:Golang必備技巧:接口型函數(shù)