前言
為什么需要學(xué)Gradle?
Gradle 是 Android 現(xiàn)在主流的編譯工具绿聘,雖然在Gradle 出現(xiàn)之前和之后都有對(duì)應(yīng)更快的編譯工具出現(xiàn)趾浅,但是 Gradle 的優(yōu)勢(shì)就在于它是親兒子扛吞,Gradle 確實(shí)比較慢鹃觉,這和它的編譯過程有關(guān)厢钧,但是現(xiàn)在的Gradle 編譯速度已經(jīng)有了成倍提高鳞尔。除此之外,相對(duì)其他編譯工具早直,最重要的寥假,他和 Android Studio 的關(guān)系非常緊密,可以說對(duì)于一些簡(jiǎn)單的程序我們幾乎不需要任何代碼上的配置只使用 Android Studio 就可以完成編譯和運(yùn)行霞扬。
但是對(duì)于一些比較復(fù)雜的炸裆,特別是多人團(tuán)隊(duì)合作的項(xiàng)目我們會(huì)需要一些個(gè)性化的配置來提高我們的開發(fā)效率朝巫。比如我們要自定義編譯出的apk包的名字、對(duì)于一些特殊產(chǎn)品我們可能會(huì)要用同一個(gè)項(xiàng)目編譯出免費(fèi)版和付費(fèi)版的apk。這些高級(jí)的功能都需要我們對(duì)配置代碼進(jìn)行自定義地修改务热。
最近伴隨著 Android Studio2.0的發(fā)布, Gradle 也進(jìn)行了一次非常大的升級(jí)酒奶,叫Instant Run.它的編譯速度網(wǎng)上有人用逆天兩個(gè)字來形容关摇。當(dāng)我們第一次點(diǎn)擊run、debug按鈕的時(shí)候肆汹,它運(yùn)行時(shí)間和我們往常一樣愚墓。但是接下去的時(shí)間里,你每次修改代碼后點(diǎn)擊run昂勉、debug按鈕浪册,對(duì)應(yīng)的改變將迅速的部署到你正在運(yùn)行的程序上,傳說速度快到你都來不及把注意力集中到手機(jī)屏幕上硼啤,它就已經(jīng)做好相應(yīng)的更改议经。但是剛出來的似乎對(duì)一些項(xiàng)目的兼容性不太好,現(xiàn)在升級(jí)后不知道怎么樣谴返。
為什么要了解命令行編譯煞肾?
在很多情況下我們都是使用的 Android Studio 來build、debug項(xiàng)目嗓袱。Android Studio 能滿足我們開發(fā)的大多數(shù)需求籍救,但是某些情況下命令行能夠讓我們編譯的效率更高,過程更明朗渠抹,一些高級(jí)的配置也需要熟悉命令行才能夠使用蝙昙,比如在服務(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ù)雜:
我們需要一種工具幫我們更快更方便更簡(jiǎn)潔地完成 Android 程序的編譯。現(xiàn)在結(jié)合Android Studio 我們一般使用的工具都是Gradle, 在 Gradle 出現(xiàn)以前Android 也有對(duì)應(yīng)的編譯工具叫Ant,在Gradle 出現(xiàn)之后吨铸,也有新的編譯工具出現(xiàn)行拢,就是FaceBook 的Buck工具。這些編譯工具在出現(xiàn)的時(shí)候幾乎都比 Gradle 要快诞吱,Gradle 之所以慢是跟它的編譯周期有很大關(guān)系舟奠。
Gradle 的編譯周期
在解析 Gradle 的編譯過程之前我們需要理解在 Gradle 中非常重要的兩個(gè)對(duì)象。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)閎uild.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 對(duì)象,如果有多個(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
對(duì)于一個(gè)gradle 項(xiàng)目刁标,最基礎(chǔ)的文件配置如下:
一個(gè)項(xiàng)目有一個(gè)setting.gradle、包括一個(gè)頂層的build.gradle文件址晕、每個(gè)Module 都有自己的一個(gè)build.gradle文件膀懈。
setting.gradle:這個(gè) setting 文件定義了哪些module 應(yīng)該被加入到編譯過程,對(duì)于單個(gè)module 的項(xiàng)目可以不用需要這個(gè)文件斩箫,但是對(duì)于 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:針對(duì)每個(gè)moudle 的配置,如果這里的定義的選項(xiàng)和頂層build.gradle定義的相同纳决,后者會(huì)被覆蓋碰逸。典型的 配置內(nèi)容如下:
apply plugin:第一行代碼應(yīng)用了Android 程序的gradle插件,作為Android 的應(yīng)用程序阔加,這一步是必須的饵史,因?yàn)閜lugin中提供了Android 編譯、測(cè)試胜榔、打包等等的所有task胳喷。
android:這是編譯文件中最大的代碼塊,關(guān)于android 的所有特殊配置都在這里夭织,這就是又我們前面的聲明的 plugin 提供的吭露。
defaultConfig就是程序的默認(rèn)配置,注意尊惰,如果在AndroidMainfest.xml里面定義了與這里相同的屬性讲竿,會(huì)以這里的為主。
這里最有必要要說明的是applicationId的選項(xiàng):在我們?cè)?jīng)定義的AndroidManifest.xml中弄屡,那里定義的包名有兩個(gè)用途:一個(gè)是作為程序的唯一識(shí)別ID,防止在同一手機(jī)裝兩個(gè)一樣的程序题禀;另一個(gè)就是作為我們R資源類的包名。在以前我們修改這個(gè)ID會(huì)導(dǎo)致所有用引用R資源類的地方都要修改琢岩。但是現(xiàn)在我們?nèi)绻薷腶pplicationId只會(huì)修改當(dāng)前程序的ID,而不會(huì)去修改源碼中資源文件的引用投剥。
buildTypes:定義了編譯類型,針對(duì)每個(gè)類型我們可以有不同的編譯配置担孔,不同的編譯配置對(duì)應(yīng)的有不同的編譯命令江锨。默認(rèn)的有debug吃警、release 的類型。
dependencies:是屬于gradle 的依賴配置啄育。它定義了當(dāng)前項(xiàng)目需要依賴的其他庫酌心。
Gradle Wrapper
Gradle 不斷的在發(fā)展,新的版本難免會(huì)對(duì)以往的項(xiàng)目有一些向后兼容性的問題挑豌,這個(gè)時(shí)候,gradle wrapper就應(yīng)運(yùn)而生了安券。
gradlw wrapper 包含一些腳本文件和針對(duì)不同系統(tǒng)下面的運(yùn)行文件。wrapper 有版本區(qū)分氓英,但是并不需要你手動(dòng)去下載侯勉,當(dāng)你運(yùn)行腳本的時(shí)候,如果本地沒有會(huì)自動(dòng)下載對(duì)應(yīng)版本文件铝阐。
在不同操作系統(tǒng)下面執(zhí)行的腳本不同址貌,在 Mac 系統(tǒng)下執(zhí)行./gradlew ...,在windows 下執(zhí)行g(shù)radle.bat進(jìn)行編譯徘键。
如果你是直接從eclipse 中的項(xiàng)目轉(zhuǎn)換過來的练对,程序并不會(huì)自動(dòng)創(chuàng)建wrapper腳本,我們需要手動(dòng)創(chuàng)建吹害。在命令行輸入以下命令即可
gradlewrapper--gradle-version2.4
它會(huì)創(chuàng)建如下目錄結(jié)構(gòu):
wrapper 就是我們使用命令行編譯的開始螟凭。下面我們看看 wrapper 有什么樣的作用。
Gradle basics
Gradle 會(huì)根據(jù)build 文件的配置生成不同的task它呀,我們可以直接單獨(dú)執(zhí)行每一個(gè)task螺男。通過./gradlew tasks列出所有task。如果通過同時(shí)還想列出每個(gè)task 對(duì)應(yīng)依賴的其他task钟些,可以使用./gradlew tasks -all烟号。
其實(shí)每當(dāng)我們?cè)贏ndroid Studio點(diǎn)擊 build,rebuild,clean菜單的時(shí)候,執(zhí)行的就是一些gradle task.
Android tasks
有四個(gè)基本的 task, Android 繼承他們分別進(jìn)行了自己的實(shí)現(xiàn):
assemble:對(duì)所有的 buildType 生成 apk 包政恍。
clean:移除所有的編譯輸出文件汪拥,比如apk
check:執(zhí)行l(wèi)int檢測(cè)編譯。
build:同時(shí)執(zhí)行assemble和check命令
這些都是基本的命令篙耗,在實(shí)際項(xiàng)目中會(huì)根據(jù)不同的配置迫筑,會(huì)對(duì)這些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對(duì),這些key-value 對(duì)在不同編譯類型的 apk 下的值不同面殖,比如我們可以為debug 和release 兩種環(huán)境定義不同的服務(wù)器竖哩。比如:
除此之外,我們還可以為不同的編譯類型的設(shè)置不同的資源文件脊僚,比如:
Repositories
Repositories 就是代碼倉庫相叁,這個(gè)相信大家都知道,我們平時(shí)的添加的一些 dependency 就是從這里下載的辽幌,Gradle 支持三種類型的倉庫:Maven,Ivy和一些靜態(tài)文件或者文件夾增淹。在編譯的執(zhí)行階段,gradle 將會(huì)從倉庫中取出對(duì)應(yīng)需要的依賴文件乌企,當(dāng)然虑润,gradle 本地也會(huì)有自己的緩存,不會(huì)每次都去取這些依賴加酵。
gradle 支持多種 Maven 倉庫拳喻,一般我們就是用共有的jCenter就可以了。
有一些項(xiàng)目猪腕,可能是一些公司私有的倉庫中的冗澈,這時(shí)候我們需要手動(dòng)加入倉庫連接:
如果倉庫有密碼,也可以同時(shí)傳入用戶名和密碼
我們也可以使用相對(duì)路徑配置本地倉庫陋葡,我們可以通過配置項(xiàng)目中存在的靜態(tài)文件夾作為本地倉庫:
Dependencies
我們?cè)谝脦斓臅r(shí)候亚亲,每個(gè)庫名稱包含三個(gè)元素:組名:庫名稱:版本號(hào),如下:
如果我們要保證我們依賴的庫始終處于最新狀態(tài),我們可以通過添加通配符的方式,比如:
但是我們一般不要這么做捌归,這樣做除了每次編譯都要去做網(wǎng)絡(luò)請(qǐng)求查看是否有新版本導(dǎo)致編譯過慢外肛响,最大的弊病在于我們使用過的版本很很困難是測(cè)試版,性能得不到保證陨溅,所以终惑,在我們引用庫的時(shí)候一定要指名依賴版本。
Local dependencies
File dependencies
通過files()方法可以添加文件依賴门扇,如果有很多jar文件雹有,我們也可以通過fileTree()方法添加一個(gè)文件夾,除此之外臼寄,我們還可以通過通配符的方式添加霸奕,如下:
Native libraries
配置本地.so庫。在配置文件中做如下配置吉拳,然后在對(duì)應(yīng)位置建立文件夾质帅,加入對(duì)應(yīng)平臺(tái)的.so文件。
文件結(jié)構(gòu)如下:
Library projects
如果我們要寫一個(gè)library項(xiàng)目讓其他的項(xiàng)目引用留攒,我們的bubild.gradle的plugin 就不能是andrid plugin了煤惩,需要引用如下plugin
applyplugin:'com.android.library'
引用的時(shí)候在setting文件中include即可。
如果我們不方便直接引用項(xiàng)目炼邀,需要通過文件的形式引用魄揉,我們也可以將項(xiàng)目打包成aar文件,注意拭宁,這種情況下洛退,我們?cè)陧?xiàng)目下面新建arrs文件夾,并在build.gradle 文件中配置 倉庫:
當(dāng)需要引用里面的某個(gè)項(xiàng)目時(shí)杰标,通過如下方式引用:
Build Variants
在開發(fā)中我們可能會(huì)有這樣的需求:
我們需要在debug 和 release 兩種情況下配置不同的服務(wù)器地址兵怯;
當(dāng)打市場(chǎng)渠道包的時(shí)候,我們可能需要打免費(fèi)版腔剂、收費(fèi)版媒区,或者內(nèi)部版、外部版的程序桶蝎。
渠道首發(fā)包通常需要要求在歡迎頁添加渠道的logo驻仅。等等
為了讓市場(chǎng)版和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ù)編譯類型的不同我們可以選擇對(duì)某些源碼直接進(jìn)行替換雹顺。
除了代碼可以替換丹墨,我們的資源文件也可以替換
除此之外,不同編譯類型的項(xiàng)目嬉愧,我們的依賴都可以不同贩挣,比如,如果我需要在staging和debug兩個(gè)版本中使用不同的log框架没酣,我們這樣配置:
Product flavors
前面我們都是針對(duì)同一份源碼編譯同一個(gè)程序的不同類型王财,如果我們需要針對(duì)同一份源碼編譯不同的程序(包名也不同),比如 免費(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)先級(jí)甚至高于 單獨(dú)的built type 和product flavor 文件夾的優(yōu)先級(jí)视事。如果你想對(duì)于 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)先級(jí)最大,在Library 中定義的資源優(yōu)先級(jí)最低推励。
Signing configurations
如果我們打包市場(chǎng)版的時(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 一樣對(duì)我們的資源進(jìn)行keep操作只盹。方法就是在res/raw/下建立一個(gè)keep.xml文件,通過如下方式 keep 資源:
Manual shrinking
對(duì)一些特殊的文件或者文件夾愤兵,比如 國際化的資源文件鹿霸、屏幕適配資源,如果我們已經(jīng)確定了某種型號(hào)秆乳,而不需要重新適配懦鼠,我們可以直接去掉不可能會(huì)被適配的資源。這在為廠商適配機(jī)型定制app的時(shí)候是很用的屹堰。做法如下:
比如我們可能有非常多的國際化的資源肛冶,如果我們應(yīng)用場(chǎng)景只用到了English,Danish,Dutch的資源,我們可以直接指定我們的resConfig:
對(duì)于尺寸文件我們也可以這樣做
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 之前,我們先簡(jiǎn)單看看groovy 的語法厉亏。
Groovy
我們前面看到的那些build.gradle 配置文件董习,和xml 等的配置文件不同,這些文件可以說就是可以執(zhí)行的代碼爱只,只是他們的結(jié)構(gòu)看起來通俗易懂皿淋,和配置文件沒什么兩樣,這也是Google 之所以選擇Groovy 的原因恬试。除此之外窝趣,Groovy 是一門JVM 語言,也就是训柴,Groovy 的代碼最終也會(huì)被編譯成JVM 字節(jié)碼哑舒,交給虛擬機(jī)去執(zhí)行,我們也可以直接反編譯這些字節(jié)碼文件畦粮。
我們這里簡(jiǎn)單地說一下 groovy 一些語法散址。
變量
在groovy 中乖阵,沒有固定的類型,變量可以通過def關(guān)鍵字引用预麸,比如:
defname='Andy'
我們通過單引號(hào)引用一串字符串的時(shí)候這個(gè)字符串只是單純的字符串瞪浸,但是如果使用雙引號(hào)引用,在字符串里面還支持插值操作吏祸,
defname='Andy'defgreeting="Hello, $name!"
方法
類似 python 一樣对蒲,通過def關(guān)鍵字定義一個(gè)方法。方法如果不指定返回值贡翘,默認(rèn)返回最后一行代碼的值蹈矮。
defsquare(defnum){? ? num * num}square4
類
Groovy 也是通過Groovy 定義一個(gè)類:
classMyGroovyClass{StringgreetingStringgetGreeting() {return'Hello!'}}
在Groovy 中,默認(rèn)所有的類和方法都是pulic的鸣驱,所有類的字段都是private的泛鸟;
和java一樣,我們通過new關(guān)鍵字得到類的實(shí)例踊东,使用def接受對(duì)象的引用:def instance = new MyGroovyClass()
而且在類中聲明的字段都默認(rèn)會(huì)生成對(duì)應(yīng)的setter,getter方法北滥。所以上面的代碼我們可以直接調(diào)用instance.setGreeting 'Hello, Groovy!',注意闸翅,groovy 的方法調(diào)用是可以沒有括號(hào)的再芋,而且也不需要分號(hào)結(jié)尾。除此之外坚冀,我們甚至也可以直接調(diào)用济赎;
我們可以直接通過instance.greeting這樣的方式拿到字段值,但其實(shí)這也會(huì)通過其get方法记某,而且不是直接拿到這個(gè)值司训。
map、collections
在 Groovy 中液南,定義一個(gè)列表是這樣的:
Listlist= [1,2,3,4,5]
遍歷一個(gè)列表是這樣的:
list.each() { element ->printlnelement}
定義一個(gè) map 是這樣的:
MappizzaPrices = [margherita:10, pepperoni:12]
獲取一個(gè)map 值是這樣的:
pizzaPrices.get('pepperoni')pizzaPrices['pepperoni']
閉包
在Groovy 中有一個(gè)閉包的概念豁遭。閉包可以理解為就是 Java 中的匿名內(nèi)部類。閉包支持類似lamda形式的語法調(diào)用贺拣。如下:
def square = {num->num*num}square8
如果只有一個(gè)參數(shù),我們甚至可以省略這個(gè)參數(shù)捂蕴,默認(rèn)使用it作為參數(shù)譬涡,最后代碼是這樣的:
Closuresquare = {it* it}square16
理解閉包的語法后,我們會(huì)發(fā)現(xiàn)啥辨,其實(shí)在我們之前的配置文件里涡匀,android,dependencies這些后面緊跟的代碼塊,都是一個(gè)閉包而已溉知。
Groovy in Gradle
了解完 groovy 的基本語法后陨瘩,我們來看看 gradle 里面的代碼就好理解多了腕够。
apply
applyplugin:'com.android.application'
這段代碼其實(shí)就是調(diào)用了project對(duì)象的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 的密碼明文寫在這里對(duì)于產(chǎn)品的安全性來說不太好尤慰,特別是如果該源碼開源,別人就可以用你的 id 去發(fā)布app荔泳。對(duì)于這種情況,我們需要構(gòu)建一個(gè)動(dòng)態(tài)加載任務(wù),在編譯release 源碼的時(shí)候從本地文件(未加入git)獲取keystore 信息,如下:
你還可以設(shè)置一個(gè)保險(xiǎn)措施昨登,萬一我們的沒有找到對(duì)應(yīng)的文件需要用戶從控制臺(tái)輸入密碼
最后設(shè)置最終值
然后設(shè)置release 任務(wù)依賴于我們剛剛設(shè)置的任務(wù)
通過 hook Android 編譯插件 重命名 apk
最后編譯出來的apk 名字類似app-debug-1.0.apk禽捆。