前言
為什么需要學(xué)Gradle?
Gradle 是 Android 現(xiàn)在主流的編譯工具懂扼,雖然在Gradle 出現(xiàn)之前和之后都有對應(yīng)更快的編譯工具出現(xiàn),但是 Gradle 的優(yōu)勢就在于它是親兒子站粟,Gradle 確實(shí)比較慢浦妄,這和它的編譯過程有關(guān)呢灶,但是現(xiàn)在的Gradle 編譯速度已經(jīng)有了成倍提高吴超。除此之外,相對其他編譯工具鸯乃,最重要的鲸阻,他和 Android Studio 的關(guān)系非常緊密,可以說對于一些簡單的程序我們幾乎不需要任何代碼上的配置只使用 Android Studio 就可以完成編譯和運(yùn)行缨睡。
但是對于一些比較復(fù)雜的鸟悴,特別是多人團(tuán)隊(duì)合作的項(xiàng)目我們會(huì)需要一些個(gè)性化的配置來提高我們的開發(fā)效率。比如我們要自定義編譯出的apk包的名字奖年、對于一些特殊產(chǎn)品我們可能會(huì)要用同一個(gè)項(xiàng)目編譯出免費(fèi)版
和付費(fèi)版
的apk细诸。這些高級的功能都需要我們對配置代碼進(jìn)行自定義地修改。
最近伴隨著 Android Studio2.0的發(fā)布陋守, Gradle 也進(jìn)行了一次非常大的升級震贵,叫Instant Run.它的編譯速度網(wǎng)上有人用逆天兩個(gè)字來形容。當(dāng)我們第一次點(diǎn)擊run水评、debug按鈕的時(shí)候猩系,它運(yùn)行時(shí)間和我們往常一樣。但是接下去的時(shí)間里中燥,你每次修改代碼后點(diǎn)擊run寇甸、debug按鈕,對應(yīng)的改變將迅速的部署到你正在運(yùn)行的程序上,傳說速度快到你都來不及把注意力集中到手機(jī)屏幕上幽纷,它就已經(jīng)做好相應(yīng)的更改式塌。但是剛出來的似乎對一些項(xiàng)目的兼容性不太好,現(xiàn)在升級后不知道怎么樣友浸。
為什么要了解命令行編譯?
在很多情況下我們都是使用的 Android Studio 來build偏窝、debug項(xiàng)目收恢。Android Studio 能滿足我們開發(fā)的大多數(shù)需求,但是某些情況下命令行能夠讓我們編譯的效率更高祭往,過程更明朗伦意,一些高級的配置也需要熟悉命令行才能夠使用,比如在服務(wù)器編譯硼补,某些項(xiàng)目初始化的時(shí)候如果直接交給Android Studio 驮肉,它會(huì)一直Loading,你都不知道它在干嘛,但是用命令行你就知道它卡在了哪個(gè)環(huán)節(jié)已骇,你只需要修改某些代碼离钝,馬上就能夠編譯過了。
了解 Gradle 之后我們可以做什么褪储?
we can do everything what we want.
- 自定義編譯輸出文件格式卵渴。
- hook Android 編譯過程。
- 配置和改善 Gradle 編譯速度
Gralde Overview
History
我們知道鲤竹,Android 的編譯過程非常復(fù)雜:
我們需要一種工具幫我們更快更方便更簡潔地完成 Android 程序的編譯±硕粒現(xiàn)在結(jié)合Android Studio 我們一般使用的工具都是Gradle, 在 Gradle 出現(xiàn)以前Android 也有對應(yīng)的編譯工具叫 Ant,在Gradle 出現(xiàn)之后,也有新的編譯工具出現(xiàn)辛藻,就是FaceBook 的Buck工具碘橘。這些編譯工具在出現(xiàn)的時(shí)候幾乎都比 Gradle 要快,Gradle 之所以慢是跟它的編譯周期有很大關(guān)系吱肌。
Gradle 的編譯周期
在解析 Gradle 的編譯過程之前我們需要理解在 Gradle 中非常重要的兩個(gè)對象痘拆。Project和Task。
每個(gè)項(xiàng)目的編譯至少有一個(gè) Project
,一個(gè) build.gradle
就代表一個(gè)project
,每個(gè)project
里面包含了多個(gè)task
,task 里面又包含很多action
岩榆,action是一個(gè)代碼塊错负,里面包含了需要被執(zhí)行的代碼。
在編譯過程中勇边, Gradle 會(huì)根據(jù) build相關(guān)文件犹撒,聚合所有的project
和task
,執(zhí)行task 中的 action粒褒。因?yàn)?code>build.gradle文件中的task
非常多识颊,先執(zhí)行哪個(gè)后執(zhí)行那個(gè)需要一種邏輯來保證。這種邏輯就是依賴邏輯,幾乎所有的Task 都需要依賴其他 task 來執(zhí)行祥款,沒有被依賴的task 會(huì)首先被執(zhí)行清笨。所以到最后所有的 Task 會(huì)構(gòu)成一個(gè) 有向無環(huán)圖(DAG Directed Acyclic Graph)的數(shù)據(jù)結(jié)構(gòu)。
編譯過程分為三個(gè)階段:
- 初始化階段:創(chuàng)建 Project 對象刃跛,如果有多個(gè)build.gradle抠艾,也會(huì)創(chuàng)建多個(gè)project.
- 配置階段:在這個(gè)階段,會(huì)執(zhí)行所有的編譯腳本桨昙,同時(shí)還會(huì)創(chuàng)建project的所有的task检号,為后一個(gè)階段做準(zhǔn)備。
- 執(zhí)行階段:在這個(gè)階段蛙酪,gradle 會(huì)根據(jù)傳入的參數(shù)決定如何執(zhí)行這些task,真正action的執(zhí)行代碼就在這里.
剛剛我們提到Gradle 編譯的時(shí)候的一些相關(guān)文件齐苛,下面我們挨個(gè)解析一下這些文件。
Gradle Files
對于一個(gè)gradle 項(xiàng)目桂塞,最基礎(chǔ)的文件配置如下:
一個(gè)項(xiàng)目有一個(gè)setting.grade
凹蜂、包括一個(gè)頂層的 build.grade
文件、每個(gè)Module 都有自己的一個(gè)build.grade
文件阁危。
- setting.gradle:這個(gè) setting 文件定義了哪些module 應(yīng)該被加入到編譯過程玛痊,對于單個(gè)module 的項(xiàng)目可以不用需要這個(gè)文件,但是對于 multimodule 的項(xiàng)目我們就需要這個(gè)文件欲芹,否則gradle 不知道要加載哪些項(xiàng)目卿啡。這個(gè)文件的代碼在初始化階段就會(huì)被執(zhí)行。
- 頂層的build.gradle:頂層的build.gradle文件的配置最終會(huì)被應(yīng)用到所有項(xiàng)目中菱父。它典型的配置如下:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
allprojects{
repositories{
jcenter()
}
}
buildscript:定義了 Android 編譯工具的類路徑颈娜。
repositories
中,jCenter
是一個(gè)著名的 Maven 倉庫。allprojects:中定義的屬性會(huì)被應(yīng)用到所有 moudle 中浙宜,但是為了保證每個(gè)項(xiàng)目的獨(dú)立性官辽,我們一般不會(huì)在這里面操作太多共有的東西。
每個(gè)項(xiàng)目單獨(dú)的 build.gradle:針對每個(gè)moudle 的配置粟瞬,如果這里的定義的選項(xiàng)和頂層build.gradle定義的相同同仆,后者會(huì)被覆蓋。典型的 配置內(nèi)容如下:
- apply plugin:第一行代碼應(yīng)用了Android 程序的gradle插件裙品,作為Android 的應(yīng)用程序俗批,這一步是必須的,因?yàn)?code>plugin中提供了Android 編譯市怎、測試岁忘、打包等等的所有task。
- android:這是編譯文件中最大的代碼塊区匠,關(guān)于android 的所有特殊配置都在這里干像,這就是又我們前面的聲明的 plugin 提供的。
-
defaultConfig
就是程序的默認(rèn)配置,注意麻汰,如果在AndroidMainfest.xml
里面定義了與這里相同的屬性速客,會(huì)以這里的為主。 - 這里最有必要要說明的是
applicationId
的選項(xiàng):在我們曾經(jīng)定義的AndroidManifest.xml
中五鲫,那里定義的包名有兩個(gè)用途:一個(gè)是作為程序的唯一識別ID,防止在同一手機(jī)裝兩個(gè)一樣的程序溺职;另一個(gè)就是作為我們R資源類的包名。在以前我們修改這個(gè)ID會(huì)導(dǎo)致所有用引用R資源類的地方都要修改位喂。但是現(xiàn)在我們?nèi)绻薷?code>applicationId只會(huì)修改當(dāng)前程序的ID,而不會(huì)去修改源碼中資源文件的引用辅愿。 - buildTypes:定義了編譯類型,針對每個(gè)類型我們可以有不同的編譯配置忆某,不同的編譯配置對應(yīng)的有不同的編譯命令。默認(rèn)的有debug阔蛉、release 的類型弃舒。
- dependencies:是屬于gradle 的依賴配置。它定義了當(dāng)前項(xiàng)目需要依賴的其他庫状原。
Gradle Wrapper
Gradle 不斷的在發(fā)展聋呢,新的版本難免會(huì)對以往的項(xiàng)目有一些向后兼容性的問題,這個(gè)時(shí)候,gradle wrapper
就應(yīng)運(yùn)而生了颠区。
gradlw wrapper 包含一些腳本文件和針對不同系統(tǒng)下面的運(yùn)行文件削锰。wrapper 有版本區(qū)分,但是并不需要你手動(dòng)去下載毕莱,當(dāng)你運(yùn)行腳本的時(shí)候器贩,如果本地沒有會(huì)自動(dòng)下載對應(yīng)版本文件。
在不同操作系統(tǒng)下面執(zhí)行的腳本不同朋截,在 Mac 系統(tǒng)下執(zhí)行
./gradlew ...
在windows 下執(zhí)行
gradle.bat
進(jìn)行編譯蛹稍。
如果你是直接從eclipse 中的項(xiàng)目轉(zhuǎn)換過來的,程序并不會(huì)自動(dòng)創(chuàng)建wrapper腳本部服,我們需要手動(dòng)創(chuàng)建唆姐。在命令行輸入以下命令即可
gradle wrapper --gradle-version 2.4
wrapper 就是我們使用命令行編譯的開始。下面我們看看 wrapper 有什么樣的作用廓八。
Gradle basics
Gradle 會(huì)根據(jù)build 文件的配置生成不同的task奉芦,我們可以直接單獨(dú)執(zhí)行每一個(gè)task。通過
./gradlew tasks
列出所有task剧蹂。如果通過同時(shí)還想列出每個(gè)task 對應(yīng)依賴的其他task声功,可以使用
./gradlew tasks -all
其實(shí)每當(dāng)我們在Android Studio點(diǎn)擊 build,rebuild,clean菜單的時(shí)候,執(zhí)行的就是一些gradle task.
Android tasks
有四個(gè)基本的 task, Android 繼承他們分別進(jìn)行了自己的實(shí)現(xiàn):
- assemble:對所有的 buildType 生成 apk 包国夜。
- clean:移除所有的編譯輸出文件减噪,比如apk
- check:執(zhí)行
lint
檢測編譯。 - build:同時(shí)執(zhí)行
assemble
和check
命令
這些都是基本的命令,在實(shí)際項(xiàng)目中會(huì)根據(jù)不同的配置筹裕,會(huì)對這些task 設(shè)置不同的依賴醋闭。比如 默認(rèn)的 assmeble 會(huì)依賴 assembleDebug 和assembleRelease,如果直接執(zhí)行assmeble朝卒,最后會(huì)編譯debug证逻,和release 的所有版本出來。如果我們只需要編譯debug 版本抗斤,我們可以運(yùn)行assembleDebug囚企。
除此之外還有一些常用的新增的其他命令,比如 install命令瑞眼,會(huì)將編譯后的apk 安裝到連接的設(shè)備龙宏。
我們運(yùn)行的許多命令除了會(huì)輸出到命令行,還會(huì)在build文件夾下生產(chǎn)一份運(yùn)行報(bào)告伤疙。比如check命令會(huì)生成lint-results.html.在build/outputs中银酗。
Configuration
BuildConfig
這個(gè)類相信大家都不會(huì)陌生,我們最常用的用法就是通過BuildConfig.DEBUG
來判斷當(dāng)前的版本是否是debug版本徒像,如果是就會(huì)輸出一些只有在 debug 環(huán)境下才會(huì)執(zhí)行的操作黍特。 這個(gè)類就是由gradle 根據(jù) 配置文件生成的。為什么gradle 可以直接生成一個(gè)Java 字節(jié)碼類锯蛀,這就得益于我們的 gradle 的編寫語言是Groovy, Groovy 是一種 JVM 語言灭衷,JVM 語言的特征就是,雖然編寫的語法不一樣旁涤,但是他們最終都會(huì)編程 JVM 字節(jié)碼文件翔曲。同是JVM 語言的還有 Scala,Kotlin 等等。
這個(gè)功能非常強(qiáng)大拭抬,我們可以通過在這里設(shè)置一些key-value對部默,這些key-value 對在不同編譯類型的 apk 下的值不同,比如我們可以為debug 和release 兩種環(huán)境定義不同的服務(wù)器造虎。比如:
除此之外傅蹂,我們還可以為不同的編譯類型的設(shè)置不同的資源文件,比如:
Repositories
Repositories 就是代碼倉庫算凿,這個(gè)相信大家都知道份蝴,我們平時(shí)的添加的一些 dependency 就是從這里下載的,Gradle 支持三種類型的倉庫:Maven,Ivy和一些靜態(tài)文件或者文件夾氓轰。在編譯的執(zhí)行階段婚夫,gradle 將會(huì)從倉庫中取出對應(yīng)需要的依賴文件,當(dāng)然署鸡,gradle 本地也會(huì)有自己的緩存案糙,不會(huì)每次都去取這些依賴限嫌。
gradle 支持多種 Maven 倉庫,一般我們就是用共有的jCenter就可以了时捌。
有一些項(xiàng)目怒医,可能是一些公司私有的倉庫中的,這時(shí)候我們需要手動(dòng)加入倉庫連接:
如果倉庫有密碼奢讨,也可以同時(shí)傳入用戶名和密碼
我們也可以使用相對路徑配置本地倉庫稚叹,我們可以通過配置項(xiàng)目中存在的靜態(tài)文件夾作為本地倉庫:
Dependencies
我們在引用庫的時(shí)候,每個(gè)庫名稱包含三個(gè)元素:組名:庫名稱:版本號,如下:
如果我們要保證我們依賴的庫始終處于最新狀態(tài)拿诸,我們可以通過添加通配符的方式扒袖,比如:
但是我們一般不要這么做,這樣做除了每次編譯都要去做網(wǎng)絡(luò)請求查看是否有新版本導(dǎo)致編譯過慢外亩码,最大的弊病在于我們使用過的版本很很困難是測試版季率,性能得不到保證,所以描沟,在我們引用庫的時(shí)候一定要指名依賴版本蚀同。
Local dependencies
File dependencies
通過files()方法可以添加文件依賴,如果有很多jar文件啊掏,我們也可以通過fileTree()方法添加一個(gè)文件夾,除此之外衰猛,我們還可以通過通配符的方式添加迟蜜,如下:
Native libraries
配置本地 .so庫。在配置文件中做如下配置啡省,然后在對應(yīng)位置建立文件夾娜睛,加入對應(yīng)平臺(tái)的.so文件。
文件結(jié)構(gòu)如下:
Library projects
如果我們要寫一個(gè)library項(xiàng)目讓其他的項(xiàng)目引用卦睹,我們的bubild.gradle的plugin 就不能是andrid plugin了畦戒,需要引用如下plugin
apply plugin: 'com.android.library'
引用的時(shí)候在setting文件中include即可。
如果我們不方便直接引用項(xiàng)目结序,需要通過文件的形式引用障斋,我們也可以將項(xiàng)目打包成aar文件,注意徐鹤,這種情況下垃环,我們在項(xiàng)目下面新建arrs文件夾,并在build.gradle 文件中配置 倉庫:
當(dāng)需要引用里面的某個(gè)項(xiàng)目時(shí)返敬,通過如下方式引用:
Build Variants
在開發(fā)中我們可能會(huì)有這樣的需求:
- 我們需要在debug 和 release 兩種情況下配置不同的服務(wù)器地址遂庄;
- 當(dāng)打市場渠道包的時(shí)候,我們可能需要打免費(fèi)版劲赠、收費(fèi)版涛目,或者內(nèi)部版秸谢、外部版的程序。
- 渠道首發(fā)包通常需要要求在歡迎頁添加渠道的logo霹肝。等等
- 為了讓市場版和debug版同時(shí)存在與一個(gè)手機(jī)估蹄,我們需要編譯的時(shí)候自動(dòng)給debug版本不一樣的包名。
- 這些需求都需要在編譯的時(shí)候動(dòng)態(tài)根據(jù)當(dāng)前的編譯類型輸出不同樣式的apk文件阿迈。這時(shí)候就是我們的buildType大展身手的時(shí)候了元媚。
Build Type
android 默認(rèn)的帶有Debug和Release兩種編譯類型。比如我們現(xiàn)在有一個(gè)新的statging的編譯類型
Source sets
每當(dāng)創(chuàng)建一個(gè)新的build type 的時(shí)候苗沧,gradle 默認(rèn)都會(huì)創(chuàng)建一個(gè)新的source set刊棕。我們可以建立與main文件夾同級的文件夾,根據(jù)編譯類型的不同我們可以選擇對某些源碼直接進(jìn)行替換待逞。
除了代碼可以替換甥角,我們的資源文件也可以替換
除此之外,不同編譯類型的項(xiàng)目识樱,我們的依賴都可以不同嗤无,比如,如果我需要在staging和debug兩個(gè)版本中使用不同的log框架怜庸,我們這樣配置:
Product flavors
前面我們都是針對同一份源碼編譯同一個(gè)程序的不同類型当犯,如果我們需要針對同一份源碼編譯不同的程序(包名也不同),比如 免費(fèi)版和收費(fèi)版割疾。我們就需要Product flavors
嚎卫。
注意,Product flavors和Build Type是不一樣的,而且他們的屬性也不一樣宏榕。所有的 product flavor 版本和defaultConfig 共享所有屬性拓诸!
像Build type 一樣,product flavor 也可以有自己的source set
文件夾麻昼。除此之外奠支,product flavor 和 build type 可以結(jié)合,他們的文件夾里面的文件優(yōu)先級甚至高于 單獨(dú)的built type 和product flavor 文件夾的優(yōu)先級抚芦。如果你想對于 blue類型的release 版本有不同的圖標(biāo)倍谜,我們可以建立一個(gè)文件夾叫blueRelease
,注意叉抡,這個(gè)順序不能錯(cuò)枢劝,一定是 flavor+buildType 的形式。
更復(fù)雜的情況下卜壕,我們可能需要多個(gè)product 的維度進(jìn)行組合您旁,比如我想要 color 和 price 兩個(gè)維度去構(gòu)建程序。這時(shí)候我們就需要使用flavorDimensions
:
根據(jù)我們的配置轴捎,再次查看我們的task,發(fā)現(xiàn)多了這些task:
Resource merge priority
在Build Type中定義的資源優(yōu)先級最大鹤盒,在Library 中定義的資源優(yōu)先級最低蚕脏。
Signing configurations
如果我們打包市場版的時(shí)候,我們需要輸入我們的keystore數(shù)據(jù)侦锯。如果是debug 版本驼鞭,系統(tǒng)默認(rèn)會(huì)幫我們配置這些信息。這些信息在gradle 中都配置在signingConfigs
中尺碰。
配置之后我們需要在build type中直接使用
Optimize
Speeding up multimodule builds
可以通過以下方式加快gradle 的編譯:
-
開啟并行編譯:在項(xiàng)目根目錄下面的
gradle.properties
中設(shè)置
org.gradle.parallel=true
-
開啟編譯守護(hù)進(jìn)程:該進(jìn)程在第一次啟動(dòng)后回一直存在挣棕,當(dāng)你進(jìn)行二次編譯的時(shí)候,可以重用該進(jìn)程亲桥。同樣是在
gradle.properties
中設(shè)置洛心。
org.gradle.daemon=true
org.gradle.jvmargs=-Xms256m -Xmx1024m
Reducing apk file
在編譯的時(shí)候,我們可能會(huì)有很多資源并沒有用到题篷,此時(shí)就可以通過shrinkResources
來優(yōu)化我們的資源文件词身,除去那些不必要的資源。
如果我們需要查看該命令幫我們減少了多少無用的資源番枚,我們也可以通過運(yùn)行shrinkReleaseResources
命令來查看log.
某些情況下法严,一些資源是需要通過動(dòng)態(tài)加載的方式載入的,這時(shí)候我也需要像 Progard 一樣對我們的資源進(jìn)行keep操作葫笼。方法就是在res/raw/
下建立一個(gè)keep.xml
文件深啤,通過如下方式 keep 資源:
Manual shrinking
對一些特殊的文件或者文件夾,比如 國際化的資源文件路星、屏幕適配資源墓塌,如果我們已經(jīng)確定了某種型號,而不需要重新適配奥额,我們可以直接去掉不可能會(huì)被適配的資源。這在為廠商適配機(jī)型定制app的時(shí)候是很用的访诱。做法如下:
比如我們可能有非常多的國際化的資源垫挨,如果我們應(yīng)用場景只用到了English,Danish,Dutch的資源,我們可以直接指定我們的resConfig
:
對于尺寸文件我們也可以這樣做
Profiling
當(dāng)我們執(zhí)行所有task的時(shí)候我們都可以通過添加--profile
參數(shù)生成一份執(zhí)行報(bào)告在reports/profile
中触菜。示例如下:
我們可以通過這份報(bào)告看出哪個(gè)項(xiàng)目耗費(fèi)的時(shí)間最多九榔,哪個(gè)環(huán)節(jié)耗費(fèi)的時(shí)間最多。
Practice
在開發(fā)的過程中涡相,我們可能會(huì)遇到很多情況需要我們能夠自己定義task哲泊,在自定義task 之前,我們先簡單看看groovy 的語法催蝗。
Groovy
我們前面看到的那些build.gradle 配置文件切威,和xml 等的配置文件不同,這些文件可以說就是可以執(zhí)行的代碼丙号,只是他們的結(jié)構(gòu)看起來通俗易懂先朦,和配置文件沒什么兩樣缰冤,這也是Google 之所以選擇Groovy 的原因。除此之外喳魏,Groovy 是一門JVM 語言棉浸,也就是,Groovy 的代碼最終也會(huì)被編譯成JVM 字節(jié)碼刺彩,交給虛擬機(jī)去執(zhí)行迷郑,我們也可以直接反編譯這些字節(jié)碼文件。
我們這里簡單地說一下 groovy 一些語法创倔。
變量
在groovy 中录豺,沒有固定的類型瀑志,變量可以通過def
關(guān)鍵字引用,比如:
def name = 'Andy'
我們通過單引號引用一串字符串的時(shí)候這個(gè)字符串只是單純的字符串,但是如果使用雙引號引用牧挣,在字符串里面還支持插值操作,
def name = 'Andy'
def greeting = "Hello, $name!"
方法
類似 python 一樣瓷产,通過def
關(guān)鍵字定義一個(gè)方法涌矢。方法如果不指定返回值,默認(rèn)返回最后一行代碼的值朗徊。
def square(def num) {
num * num
}
square 4
類
Groovy 也是通過Groovy 定義一個(gè)類:
class MyGroovyClass {
String greeting
String getGreeting() {
return 'Hello!'
}
}
- 在Groovy 中首妖,默認(rèn)所有的類和方法都是
pulic
的,所有類的字段都是private
的爷恳; - 和java一樣有缆,我們通過
new
關(guān)鍵字得到類的實(shí)例,使用def接受對象的引用:def instance = new MyGroovyClass()
- 而且在類中聲明的字段都默認(rèn)會(huì)生成對應(yīng)的setter,getter方法温亲。所以上面的代碼我們可以直接調(diào)用
instance.setGreeting 'Hello, Groovy!'
棚壁,注意,groovy 的方法調(diào)用是可以沒有括號的栈虚,而且也不需要分號結(jié)尾袖外。除此之外,我們甚至也可以直接調(diào)用魂务; - 我們可以直接通過
instance.greeting
這樣的方式拿到字段值曼验,但其實(shí)這也會(huì)通過其get
方法,而且不是直接拿到這個(gè)值粘姜。
map鬓照、collections
在 Groovy 中,定義一個(gè)列表是這樣的:
List list = [1, 2, 3, 4, 5]
遍歷一個(gè)列表是這樣的:
list.each() { element ->
println element
}
定義一個(gè) map 是這樣的:
Map pizzaPrices = [margherita:10, pepperoni:12]
獲取一個(gè)map 值是這樣的:
pizzaPrices.get('pepperoni')
pizzaPrices['pepperoni']
閉包
在Groovy 中有一個(gè)閉包的概念孤紧。閉包可以理解為就是 Java 中的匿名內(nèi)部類豺裆。閉包支持類似lamda形式的語法調(diào)用。如下:
def square = { num ->
num * num
}
square 8
如果只有一個(gè)參數(shù)号显,我們甚至可以省略這個(gè)參數(shù)留储,默認(rèn)使用it作為參數(shù)翼抠,最后代碼是這樣的:
Closure square = {
it * it
}
square 16
理解閉包的語法后,我們會(huì)發(fā)現(xiàn)获讳,其實(shí)在我們之前的配置文件里阴颖,android,dependencies
這些后面緊跟的代碼塊,都是一個(gè)閉包而已丐膝。
Groovy in Gradle
了解完 groovy 的基本語法后量愧,我們來看看 gradle 里面的代碼就好理解多了。
- apply
apply plugin: 'com.android.application'
這段代碼其實(shí)就是調(diào)用了project對象的apply方法帅矗,傳入了一個(gè)以plugin
為key的map偎肃。完整寫出來就是這樣的:
project.apply([plugin: 'com.android.application'])
實(shí)際調(diào)用的時(shí)候會(huì)傳入一個(gè)DependencyHandler
的閉包,代碼如下:
Task
運(yùn)行該 task
./gradlew hello
注意:我們前面說過,gradle的生命周期分三步浑此,初始化累颂,配置和執(zhí)行。上面的代碼在配置過程就已經(jīng)執(zhí)行了凛俱,所以紊馏,打印出的字符串發(fā)生在該任務(wù)執(zhí)行之前,如果要在執(zhí)行階段才執(zhí)行任務(wù)中的代碼應(yīng)該如下設(shè)置:
添加Action:前面我們說過task 包含系列的action,當(dāng)task 被執(zhí)行的時(shí)候蒲犬,所有的action 都會(huì)被依次執(zhí)行朱监。如果我們要加入自己的action,我們可以通過復(fù)寫doFirst()
和doLast()
方法。
打印出來是這樣的:
Task 依賴:前面我們也說過原叮,task 之間的關(guān)系就是依賴關(guān)系赫编,關(guān)于Task 的依賴有兩種,must RunAfter和dependsOn奋隶。比如:
task task1 <<{ printfln="" 'task1'="" }="" task="" task2="" <<{="" 'task2'="" task2.mustRunAfter="" task1<="" code=""/>
task task1 <<{ printfln="" 'task1'="" }="" task="" task2="" <<{="" 'task2'="" task2.dependsOn="" task1<="" code=""/>
他們的區(qū)別是擂送,運(yùn)行的的時(shí)候前者必須要都按順序加入gradlew task2 task1執(zhí)行才可以順利執(zhí)行,否則單獨(dú)執(zhí)行每個(gè)任務(wù)唯欣,后者只需要執(zhí)行g(shù)radlew task2即可同時(shí)執(zhí)行兩個(gè)任務(wù)嘹吨。
Practice
我們可以通過兩個(gè)例子來實(shí)踐task。
keystore 保護(hù)
這里直接將 store 的密碼明文寫在這里對于產(chǎn)品的安全性來說不太好黍聂,特別是如果該源碼開源,別人就可以用你的 id 去發(fā)布app身腻。對于這種情況产还,我們需要構(gòu)建一個(gè)動(dòng)態(tài)加載任務(wù),在編譯release 源碼的時(shí)候從本地文件(未加入git)獲取keystore 信息嘀趟,如下:
你還可以設(shè)置一個(gè)保險(xiǎn)措施脐区,萬一我們的沒有找到對應(yīng)的文件需要用戶從控制臺(tái)輸入密碼
最后設(shè)置最終值
然后設(shè)置release 任務(wù)依賴于我們剛剛設(shè)置的任務(wù)
通過 hook Android 編譯插件 重命名 apk
最后編譯出來的apk 名字類似 app-debug-1.0.apk
。
總結(jié)
按照習(xí)慣還是要寫一個(gè)總結(jié)她按,本來也是想寫一個(gè)關(guān)于Gradle在Android中的使用牛隅,網(wǎng)上查閱相關(guān)資料時(shí)看到了這篇文章炕柔,總結(jié)的相當(dāng)完整,先直接分享出來吧媒佣,接下來還是會(huì)寫關(guān)于Gradle的使用匕累, 到時(shí)候會(huì)把各個(gè)部分分開來詳細(xì)說說。