介紹
到目前為止囊嘉,我們已經(jīng)看到了很多Gradle構(gòu)建的屬性谨履,并且知道了怎么去執(zhí)行Tasks。這一章奏窑,會(huì)更多的了解這些屬性导披,并且創(chuàng)建我們自己的Task。一旦知道如何自定義Task之后埃唯,就可以完成更多的事情撩匕,并且自定義自己的插件,而在多工程中使用這些Task和Plugin墨叛。
之前我們看到了如何創(chuàng)建自定義Task止毕,并且了解了一些Groovy腳本。知道Groovy也幫我們理解Gradle如何工作漠趁,并且為什么構(gòu)建配置文件可以這樣配置扁凛。
這一章會(huì)從下面的角度來介紹:
- Understanding Groovy
- Getting started with tasks
- Hooking into the Android plugin
- Creating your own plugins
Understanding Groovy
Groovy對(duì)于Java開發(fā)者而言非常容易閱讀,但是如果沒有一個(gè)簡(jiǎn)單的介紹的話闯传,Groovy代碼也是一個(gè)比較難的任務(wù)谨朝。
Groovy基于Java并且在JVM中執(zhí)行。它的宗旨是變得更簡(jiǎn)單甥绿,更直接的語言字币,就像腳本語言一樣。而我們將Grovvy和Java對(duì)比共缕,可以讓我們更好的了解Groovy如何工作的洗出,并且更清楚的了解到這兩種語言的區(qū)別。
在Java中打印一個(gè)字符串如下:
System.out.println("Hello, world!");
而在Groovy中如下:
println 'Hello, world!'
我們可以立即發(fā)現(xiàn)一些關(guān)鍵的區(qū)別:
- 沒有System.out的命名空間
- 沒有參數(shù)路徑
- 結(jié)尾沒有分號(hào)
示例中使用單引號(hào)包圍著一個(gè)String图谷。你也可以使用單引號(hào)或者雙引號(hào)翩活,但是他們是有區(qū)別的。雙引號(hào)的String可以包含一些差值表達(dá)式蜓萄。差值表達(dá)式可以值或者函數(shù)來代替其中的占位符隅茎。而占位符表達(dá)式會(huì)包含多個(gè)值,并且通過$前綴來代表值嫉沽。例如:
def name = 'Andy'
def greeting = "Hello, $name!"
def name_size "Your name is ${name.size()} characters long."
greeting
值代表著Hello,Andy
字符串。并且name_size
的值為Your name is 4 characters long
俏竞。
字符串差值器允許我們執(zhí)行動(dòng)態(tài)代碼绸硕,比如說下面的代碼是打印正確的日期:
def method = 'toString'
new Date()."$method"()
這在Java中看起來很奇怪堂竟,但是在動(dòng)態(tài)語言的里面確實(shí)很平常的。
Classes and members
在Groovy中創(chuàng)建Class如下,包含一個(gè)成員和一個(gè)函數(shù):
class MyGroovyClass {
String greeting
String getGreeting() {
return 'Hello!'
}
}
注意到成員和函數(shù)都沒有類似于private ,public的訪問權(quán)限玻佩。而默認(rèn)的訪問權(quán)限和Java不同出嘹,Groovy中的類都是Public的,就和Method一樣咬崔,但是成員變量卻是私有的税稼。
如果要?jiǎng)?chuàng)建一個(gè)MyGroovyClass
變量,如下:
def instance = new MyGroovyClass()
instance.setGreeting 'Hello, Groovy!'
instance.getGreeting()
我們可以使用def
關(guān)鍵字來創(chuàng)建一個(gè)新的變量垮斯。一旦創(chuàng)建出了一個(gè)變量郎仆,就可以操作它的成員了。Groovy自動(dòng)添加了訪問權(quán)限兜蠕,你也可以重寫他們扰肌。就像我們定義了getGreeting
在MyGroovyClass
中。如果沒有指定的話熊杨,可以使用setter和getter方法來訪問成員變量曙旭。
如果你嘗試直接調(diào)用一個(gè)成員,那么需要調(diào)用getter方法即可晶府。也就是說桂躏,你不需要定義instance.getGreeting()
函數(shù),你可以直接調(diào)用instance.geeting
即可川陆。
println instance.getGreeting()
println instance.greeting
上面兩行代碼完成了相同的事情剂习。
Methods
就像變量一樣,我們不需要指定具體的返回類型給Method书劝。雖然為了比較清晰的能夠看清楚函數(shù)的結(jié)構(gòu)进倍,我們也會(huì)定義好返回值。另外一個(gè)不同的地方就是购对,Groovy默認(rèn)會(huì)有返回值猾昆,而不需要使用return
關(guān)鍵字。
例如Java中返回一個(gè)值的平方:
public int square(int num) {
return num * num;
}
square(2);
你需要指定函數(shù)為public骡苞,并且返回的類型垂蜗,參數(shù),以及返回對(duì)應(yīng)類型的值解幽。同樣的函數(shù)定義在Groovy中如下:
def square(def num) {
num * num
}
square 4
沒有返回類型贴见,沒有參數(shù)類型。通過使用def
關(guān)鍵字來代替一個(gè)具體的類型躲株,并且返回具體的值也沒有通過return返回片部。當(dāng)調(diào)用這個(gè)函數(shù)的時(shí)候,也不需要括號(hào)和分號(hào)霜定。另外一種Groovy的定義方式如下:
def square = { num ->
num * num
}
square 8
這不是一個(gè)常規(guī)的方法档悠,而是一個(gè)閉包廊鸥。閉包的概念和Java中不一樣,但是在Groovy和Gradle中尤為重要辖所。
Closures
閉包是匿名的代碼塊惰说,能夠接受參數(shù)并且返回一個(gè)值。它能夠被分配給變量缘回,也能夠作為參數(shù)傳遞給函數(shù)吆视。
你可以定義一個(gè)簡(jiǎn)單的閉包,在花括號(hào)中添加代碼塊即可酥宴。如果你希望它能夠更直接一些啦吧,那么可以在定義中添加類型,例如:
Closure square = {
it * it
}
square 16
通過添加Closure
定義讓每個(gè)人都知道這段代碼是閉包幅虑。如果你不想在閉包中指定參數(shù)具體的類型丰滑,Groovy會(huì)自動(dòng)添加一個(gè)。這個(gè)參數(shù)的名字就叫做it
倒庵。如果調(diào)用者沒有指定任何參數(shù)褒墨,那么這個(gè)參數(shù)就會(huì)是null。這可以使代碼更加簡(jiǎn)潔擎宝,但僅當(dāng)閉包只用一個(gè)參數(shù)時(shí)才有用郁妈。
在Gradle的上下文中,我們總是使用閉包绍申。例如噩咪,android
代碼塊以及dependencies
代碼塊都是閉包。
Collections
Gradle中有兩個(gè)比較重要的概念极阅,List和Map胃碾。
在Groovy中創(chuàng)建List很簡(jiǎn)單,不需要特殊的初始化:
List list = [1, 2, 3, 4, 5]
列表的迭代器也很簡(jiǎn)單筋搏。你可以通過each
方法來遍歷每個(gè)元素:
list.each() { element ->
println element
}
而each
函數(shù)可以讓你訪問List中的每個(gè)元素仆百。而我們也可以通過it
變量更方便的調(diào)用:
list.each() {
println it
}
另外一種類型就是Map。Map通常用在Gradle的設(shè)置和函數(shù)中奔脐。Map中保存著K-V的列表俄周。我們可以通過以下方式定義Map:
Map pizzaPrices = [margherita:10, pepperoni:12]
如果要訪問Map中的某一條,則使用get方法或者單引號(hào)訪問即可:
pizzaPrices.get('pepperoni')
pizzaPrices['pepperoni']
Groovy也為該功能提供了一個(gè)更簡(jiǎn)便的方法髓迎,你可以通過.的方式來訪問某個(gè)值:
pizzaPrices.pepperoni
Groovy in Gradle
打開一個(gè)Gradle的build.gradle
文件峦朗,看整個(gè)構(gòu)建中的Android Plugin應(yīng)用的地方:
apply plugin: 'com.android.application'
這段代碼是Groovy精簡(jiǎn)版,如果原版的Groovy代碼應(yīng)該是:
project.apply([plugin: 'com.android.application'])
重寫Groovy的精簡(jiǎn)的方法排龄,我們調(diào)用了Project
類的``apply方法波势。而
apply方法只有一個(gè)參數(shù),而該參數(shù)是一個(gè)Map,里面包含了Key為
plugin艰亮,Value為
com.android.application```闭翩。
另外一個(gè)例子挣郭,就是dependencies
代碼塊迄埃,之前我們定義dependencies
如下:
dependencies {
compile 'com.google.code.gson:gson:2.3'
}
我們現(xiàn)在知道這個(gè)代碼塊是一個(gè)閉包,調(diào)用了Project
對(duì)象的dependencies
函數(shù)兑障。這個(gè)閉包傳入的是一個(gè)DependencyHandler
對(duì)象侄非,而這個(gè)對(duì)象中存在add
函數(shù)。
這個(gè)函數(shù)接受了三個(gè)參數(shù)流译,一個(gè)String定義了配置逞怨,一個(gè)對(duì)象定義了依賴庫,以及一個(gè)閉包可以指定依賴的屬性福澡。全部展開如下:
project.dependencies({
add('compile', 'com.google.code.gson:gson:2.3', {
// Configuration statements
})
})
如果希望了解更多的Groovy在Gradle中的內(nèi)幕叠赦,最開始可以看看
Project
的官方文檔。地址為:http://gradle.org/docs/current/javadoc/org/gradle/api/Project.html
Getting started with tasks
自定義Gradle任務(wù)可以提升我們的開發(fā)效率革砸。Tasks可以操作已存在的構(gòu)建流程除秀,添加新的構(gòu)建步驟,并且影響構(gòu)建的輸出算利。我們可以執(zhí)行一些簡(jiǎn)單的任務(wù)册踩,比如說可以通過Hook Gradle的Android Plugin重命名一個(gè)已經(jīng)生成的APK。Tasks也允許你執(zhí)行更多復(fù)雜的代碼效拭,以至于我們可以在APK打包前生成多Density的圖片暂吉。例如,一旦你知道如何創(chuàng)建自定義Tasks了缎患,你就會(huì)發(fā)現(xiàn)你可以改變構(gòu)件流程了慕的。
Defining tasks
Tasks屬于Project對(duì)象,并且每個(gè)Task實(shí)現(xiàn)了Task
接口挤渔。定義一個(gè)Task最簡(jiǎn)單的方法就是使用Tasks的名字作為參數(shù)執(zhí)行Task方法即可肮街。例如:
task hello
這將創(chuàng)建出Task。但是不會(huì)做任何事情蚂蕴,如果我們希望添加一些事件低散,則可以通過以下方式:
task hello {
println 'Hello, world!'
}
當(dāng)執(zhí)行這個(gè)任務(wù)的時(shí)候,就會(huì)發(fā)現(xiàn):
$ gradlew hello
Hello, world!
:hello
從這個(gè)輸出骡楼,可以看出:Hello,world!
在任務(wù)執(zhí)行前被打印出來了熔号。回顧一下之前說的Gradle構(gòu)建流程鸟整,有三個(gè)階段:初始化階段引镊,配置階段,執(zhí)行階段。當(dāng)按照上述例子添加Task時(shí)候弟头,實(shí)際上是配置了這個(gè)Task吩抓。甚至如果你執(zhí)行其他的任務(wù),Hello,World!
這條消息仍然會(huì)出現(xiàn)赴恨。
如果你希望在執(zhí)行階段添加一些事件的話疹娶,則可以使用:
task hello << {
println 'Hello, world!'
}
唯一不同的是在閉包前加入了<<
。這告訴了Gradle代碼是在執(zhí)行階段伦连,而不是在配置階段雨饺。
為了證明這個(gè)區(qū)別,我們可以在build.gradle
中加入:
task hello << {
println 'Execution'
}
hello {
println 'Configuration'
}
我們定義了一個(gè)當(dāng)它執(zhí)行的時(shí)候會(huì)打印的Task惑淳。我們也定義了一個(gè)在Configuration階段打印的的Task额港。即使它在真正的Task之后定義的,也會(huì)首先執(zhí)行歧焦。輸出的結(jié)果如下:
$ gradlew hello
Configuration
:hello
Execution
由于Groovy有很多簡(jiǎn)潔定義的方式移斩,以下為一些示例:
task(hello) << {
println 'Hello, world!'
}
task('hello') << {
println 'Hello, world!'
}
tasks.create(name: 'hello') << {
println 'Hello, world!'
}
第一個(gè)和第二個(gè)代碼塊通過兩種不同的方式實(shí)現(xiàn)同一個(gè)效果。你可以使用單引號(hào)绢馍,也可以使用括號(hào)向瓷。在這兩個(gè)代碼塊中,我們調(diào)用的是task()
函數(shù)痕貌,它會(huì)有兩個(gè)參數(shù)风罩,一個(gè)是Task的名字,另外一個(gè)是一個(gè)閉包舵稠。task()
函數(shù)就是Gradle中Project
類中的一部分超升。
最后一個(gè)代碼塊則不是使用task()
函數(shù)。它用的是一個(gè)名為tasks
的對(duì)象哺徊,而這個(gè)對(duì)象則是TaskContainer
的實(shí)例室琢。并且,這個(gè)實(shí)例代表著每一個(gè)Project落追。它提供了create
函數(shù)盈滴,而這個(gè)函數(shù)會(huì)通過一個(gè)Map對(duì)象和一個(gè)閉包作為參數(shù),并且返回一個(gè)Task對(duì)象轿钠。
Anatomy of a task
Task
接口是所有Task巢钓,以及定義一系列Properties和Methods的基礎(chǔ)。所有的這些都被一個(gè)默認(rèn)的Class實(shí)現(xiàn)了疗垛,它的名字叫做DefaultTask
症汹。這是標(biāo)準(zhǔn)的Task類型的實(shí)現(xiàn),當(dāng)創(chuàng)建一個(gè)新的Task的時(shí)候贷腕,它會(huì)基于DefaultTask
背镇。
每個(gè)Task都包含了一系列Action
對(duì)象咬展。當(dāng)Task被執(zhí)行的時(shí)候,這些Action都會(huì)按照順序執(zhí)行瞒斩。我們可以使用doFirst
和doLast
函數(shù)來添加Action破婆。這些方法都添加一個(gè)閉包作為參數(shù),并且把他們包裝到一個(gè)Action對(duì)象中胸囱。
你只需要通過doFirst()
和doLast()
來在Execution階段來執(zhí)行代碼祷舀。而<<符號(hào)則其實(shí)代表著在doFirst
中定義了Action。舉例如下:
task hello {
println 'Configuration'
doLast {
println 'Goodbye'
}
doFirst {
println 'Hello'
}
}
當(dāng)我們執(zhí)行hello
這個(gè)任務(wù)時(shí)旺矾,則會(huì)打印出:
$ gradlew hello
Configuration
:hello
Hello
Goodbye
即使打印Goodbye
那行代碼定義在Hello
之前蔑鹦,它也會(huì)在Task執(zhí)行的時(shí)候,按照正確的位置打印出來箕宙。你也可以多次使用doFirst()
和doLast()
,例如:
task mindTheOrder {
doFirst {
println 'Not really first.'
}
doFirst {
println 'First!'
}
doLast {
println 'Not really last.'
}
doLast {
println 'Last!'
}
}
執(zhí)行完這個(gè)任務(wù)铺纽,就會(huì)打印出:
$ gradlew mindTheOrder
:mindTheOrder
First!
Not really first.
Not really last.
Last!
注意柬帕,doFirst()
函數(shù)會(huì)加在Task的Action集合最開始的地方,而doLast()
添加的Action則在最后的位置狡门。這也就意味著陷寝,我們使用這些函數(shù)的時(shí)候需要很小心,尤其注意它的順序其馏。
如果它依賴于某個(gè)順序執(zhí)行的任務(wù)的話凤跑,那么可以使用mustRunAfter()
函數(shù)。這個(gè)函數(shù)允許你影響Gradle構(gòu)建的Dependency的DAG叛复。當(dāng)你使用mustRunAfter
時(shí)仔引,需要指定兩個(gè)任務(wù),其中一個(gè)必須在另外一個(gè)之前執(zhí)行:
task task1 << {
println 'task1'
}
task task2 << {
println 'task2'
}
task2.mustRunAfter task1
執(zhí)行task1和task2將會(huì)得到task1在task2之前執(zhí)行褐奥,而忽略你所指定順序咖耘。
$ gradlew task2 task1
:task1
task1
:task2
task2
mustRunAfter()
函數(shù)不會(huì)在兩個(gè)Task之間添加依賴關(guān)系。它可以在Task1不執(zhí)行的情況下撬码,仍然可以執(zhí)行Task2儿倒。如果你希望添加兩個(gè)Task之間的依賴關(guān)系的話,那么需要使用dependsOn()
呜笑。例如:
task task1 << {
println 'task1'
}
task task2 << {
println 'task2'
}
task2.dependsOn task1
而當(dāng)你只執(zhí)行task2夫否,而不執(zhí)行task1的時(shí)候:
$ gradlew task2
:task1
task1
:task2
task2
使用mustRunAfter()
的時(shí)候,同時(shí)執(zhí)行task2和task1叫胁,并且在task2優(yōu)先執(zhí)行的時(shí)候凰慈,他們還是會(huì)有執(zhí)行的依賴關(guān)系。而dependsOn()
的話曹抬,task2必須和task1掛鉤溉瓶,即使沒有明確的說明急鳄。這是一個(gè)很重要的點(diǎn)。
Using a task to simplify the release process
在發(fā)布App之前堰酿,你需要對(duì)APK進(jìn)行簽名疾宏。而簽名前,需要?jiǎng)?chuàng)建自己的keystore
触创,其中包含了很多private keys坎藐。當(dāng)你創(chuàng)建完keystore
后,你可以在Gradle中定義簽名的配置了哼绑。例如:
android {
signingConfigs {
release {
storeFile file("release.keystore")
storePassword "password"
keyAlias "ReleaseKey"
keyPassword "password"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
這種方式的缺點(diǎn)就是密碼會(huì)被銘文保存在倉庫中岩馍。如果你正在為開源做奮斗的話,那不要用這種方式抖韩。任何一個(gè)擁有keystore文件和password的人都可以使用你的ID發(fā)布App蛀恩。
為了避免這種情況,你可以創(chuàng)建一個(gè)Task茂浮,在每次打Release包的時(shí)候詢問Release的Password双谆。這會(huì)有一點(diǎn)麻煩,而且在自動(dòng)持續(xù)集成構(gòu)建Release包的情況下也是不可能的席揽。一種比較好的解決方案就是顽馋,創(chuàng)建一個(gè)配置文件保存keystore的密碼,而這個(gè)配置文件不在倉庫中幌羞。
我們可以在根目錄下提供一個(gè)名為private.properties
的文件寸谜,并且添加:
release.password = thepassword
我們假設(shè)Keystore和key的密碼相同。如果你有兩個(gè)不同的密碼属桦,那么則可以創(chuàng)建第二個(gè)屬性熊痴。一旦設(shè)置完成,你可以定義一個(gè)新的Task地啰,名為getReleasePassword
:
task getReleasePassword << {
def password = ''
if (rootProject.file('private.properties').exists()) {
Properties properties = new Properties();
properties.load(rootProject.file('private.properties').newDataInputStream())
password = properties.getProperty('release.password')
}
}
這個(gè)任務(wù)會(huì)在根目錄尋找一個(gè)名為private.properties
的文件愁拭。如果文件存在,那么Task會(huì)加載所有的properties亏吝。并且properties.load()
函數(shù)會(huì)查找Key-Value對(duì)岭埠,就像我們?cè)趐roperties文件中定義的release.password一樣。
為了保證沒有private properties
文件的人也可以運(yùn)行這個(gè)腳本,或者處理如果文件存在,但是password屬性不存在的情況祝懂,我們可以添加一個(gè)fallback枫匾。如果password仍然為空吏颖,那么可以在console中詢問Password:
if (!password?.trim()) {
password = new String(System.console().readPassword ("\nWhat's the secret password? "))
}
在Groovy中檢查字符串是否為空是一個(gè)很簡(jiǎn)單的操作。用?
標(biāo)志的password?.trim()
檢查了password是否為null,并且使用trim()
避免password為空。
使用new String
是必須的乾巧,因?yàn)?code>System.readPassword()會(huì)返回一個(gè)字符數(shù)組句喜,然后通過String來轉(zhuǎn)換成字符串。一旦我們擁有了keystore的密碼沟于,我們就可以在release構(gòu)建中配置簽名:
android.signingConfigs.release.storePassword = password
android.signingConfigs.release.keyPassword = password
現(xiàn)在我們已經(jīng)完成我們的任務(wù)咳胃,我們需要確認(rèn)當(dāng)執(zhí)行一次Release構(gòu)建的時(shí)候是否成功,接下來在build.gradle
中添加這幾行:
tasks.whenTaskAdded { theTask ->
if (theTask.name.equals("packageRelease")) {
theTask.dependsOn "getReleasePassword"
}
}
這段代碼Hook進(jìn)了Gradle旷太,并且在運(yùn)行的時(shí)候Android Plugin會(huì)把閉包加入到Dependency Graph中展懈。直到packageRelease
執(zhí)行之前,password都不是必須的供璧,所以我們需要確保packageRelease
任務(wù)依賴于我們的getReleasePassword
任務(wù)存崖。我們不能直接使用packageRelease.dependsOn()
的原因是Android Plugin會(huì)基于Build Variant動(dòng)態(tài)的生成打包的Tasks。這也就意味著睡毒,packageRelease
任務(wù)直到Android Plugin掃描完所有的Build Variants之前来惧,都不會(huì)存在。而發(fā)現(xiàn)的過程在Build之前就已經(jīng)開始了吕嘀。
在添加了Task的構(gòu)建Hook之后违寞,執(zhí)行gradlew assembleRelease
任務(wù)的結(jié)果如下:
就像上面截圖所示,private.properties
文件不可用偶房,所以task在console中詢問password。這種情況下军浆,我們需要添加一些提醒棕洋,如何創(chuàng)建properties
文件,并且添加password屬性讓未來的構(gòu)建更賤簡(jiǎn)單乒融。一旦我們的Task選擇了keystore的密碼掰盘,Gradle就可以開始打包我們的APP并且完成構(gòu)建了。
為了讓這個(gè)Task可以正常運(yùn)轉(zhuǎn)赞季,它本質(zhì)就是Hook到Gradle和Android Plugin中愧捕。
Hooking into the Android plugin
當(dāng)開發(fā)Android App的時(shí)候,我們希望修改的任務(wù)大多都是與Android Plugin相關(guān)的申钩。之前的例子次绘,我們可以看到如何在一個(gè)自定義的Task中添加依賴。在這一屆撒遣,我們來看看如何進(jìn)行Android特殊的構(gòu)建Hook邮偎。
一種Hook到Android Plugin的方法是操作Build Varian。我們只需要在遍歷Variant的時(shí)候义黎,完成我們的任務(wù)即可禾进。
android.applicationVariants.all { variant ->
// Do something
}
為了得到所有的Build Variants,我們可以使用applicationVariants
對(duì)象廉涕。一旦我們引用到了一個(gè)具體的Build Variant泻云,我們就可以訪問它的屬性艇拍,并且操作它的屬性,比如說名字宠纯,描述等等卸夕。如果你希望在Android Library中加入相同的邏輯,那么使用libraryVariants
來替代applicationVariants
即可征椒。
這種Hook可以用來修改APK的名字娇哆,并且在文件名后添加版本號(hào)。這樣可以更簡(jiǎn)單的生成一個(gè)帶版本的APK名勃救,而不需要手動(dòng)修改文件名碍讨。接下來則看看如何實(shí)現(xiàn)
Automatically renaming APKs
在打包完后,我們來重命名APK蒙秒。我們可以遍歷App的Build Variants勃黍,并且修改outputFile
屬性。如下代碼所示:
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def file = output.outputFile
output.outputFile = new File(file.parent,file.name.replace(".apk", "-${variant.versionName}.apk"))
}
}
每個(gè)Build Variant的輸出都是一個(gè)APK文件晕讲。variant.outputs對(duì)象都會(huì)有一個(gè)屬性名為outputFile
覆获,而它則是File
類型的。一旦我們知道了output的路徑后瓢省,我們就可以操縱它了弄息。
如上所示,我們?cè)谖募刑砑恿税姹咎?hào)勤婚,而APK的名字也會(huì)從app-debug.apk
修改為app-debug-1.0.apk
摹量。接下來,我們來看看如何為每一個(gè)Build Variant創(chuàng)建一個(gè)Task馒胆。
Dynamically creating new tasks
由于Gradle工作方式以及Tasks的構(gòu)建缨称,我們可以在Configuration階段基于Build Variant創(chuàng)建我們自己的Task。
為了解釋這個(gè)強(qiáng)大的概念祝迂,我們會(huì)創(chuàng)建一個(gè)Task睦尽,但不是安裝,而是運(yùn)行Android App的某一個(gè)Build Variant型雳。Install Task只是Android Plugin中的一部分当凡,但是如果你通過命令行的installDebug
任務(wù)安裝了Apk的話,當(dāng)安裝完成后四啰,需要手動(dòng)啟動(dòng)App才行宁玫。而我們創(chuàng)建的這個(gè)Task則會(huì)把最后一步去掉。
通過Hook Application Variant中的屬性:
android.applicationVariants.all { variant ->
if (variant.install) {
tasks.create(name: "run${variant.name.capitalize()}",
dependsOn: variant.install) {
description "Installs the ${variant.description} and runs the main launcher activity."
}
}
}
對(duì)于每個(gè)Variant柑晒,我們檢查它是否有install這個(gè)任務(wù)欧瘪。因?yàn)槲覀冃枰蕾?code>install任務(wù),所以必須要檢查這個(gè)任務(wù)是否存在匙赞。一旦我們確定了install任務(wù)存在佛掖,我們就可以創(chuàng)建一個(gè)新的Task妖碉,并且基于Variant的名字賦予這個(gè)Task名字。我們需要將我們新建的任務(wù)依賴variant.install
芥被。這會(huì)在我們的任務(wù)執(zhí)行前打開install任務(wù)欧宜。而在tasks.create()
的閉包中,我們添加了一個(gè)description拴魄,可以幫助我們?cè)趫?zhí)行gradlew tasks
的時(shí)候展示日志冗茸。
在添加完description之后,我們也會(huì)添加真正的Task Action匹中。在這個(gè)例子中夏漱,我們希望啟動(dòng)APP。你可以通過Android Debug Tool(ADB)在已經(jīng)連接的設(shè)備或者模擬器中啟動(dòng)APP顶捷。
$ adb shell am start -n com.package.name/com.package.name.Activity
Gradle有一個(gè)函數(shù)叫做exec()
挂绰,這個(gè)函數(shù)可以讓我們?cè)诿钚袌?zhí)行命令。為了確保exec()
可以正常工作服赎,我們需要提供一個(gè)可執(zhí)行的環(huán)境變量葵蒂。我們也需要傳遞一些參數(shù),例如:
doFirst {
exec {
executable = 'adb'
args = ['shell', 'am', 'start', '-n',"${variant.applicationId}/.MainActivity"]
}
}
為了得到包名重虑,我們使用Build Variant中帶有后綴的Application ID践付。而如果我們加了后綴,Activity的classpath仍然相同缺厉。例如:
android {
defaultConfig {
applicationId 'com.gradleforandroid'
}
buildTypes {
debug {
applicationIdSuffix '.debug'
}
}
}
包名為com.gradleforandroid.debug
荔仁,但是Activity的路徑還是com.gradleforandroid.Activity
。為了保證我們得到正確的Activity類芽死,我們從ApplicationId中帶入后綴:
doFirst {
def classpath = variant.applicationId
if(variant.buildType.applicationIdSuffix) {
classpath -= "${variant.buildType.applicationIdSuffix}"
}
def launchClass = "${variant.applicationId}/${classpath}.MainActivity"
exec {
executable = 'adb'
args = ['shell', 'am', 'start', '-n', launchClass]
}
}
首先,我們創(chuàng)建了一個(gè)變量為classpath
次洼,該值為applicationId关贵。然后我們通過buildType
找到后綴。在Groovy中卖毁,我們可以通過-=
運(yùn)算符來從String中減去一個(gè)String揖曾。這些修改可以保證在安裝過后,使用后綴的APP也不會(huì)打開失敗亥啦。
Creating your own plugins
如果你有一系列的Gradle的Tasks希望在多個(gè)Project中重用炭剪,那我們可以考慮把這些Task添加到一個(gè)自定義的插件中去。這樣可以讓我們自己的構(gòu)建邏輯與別人共享翔脱。
Plugin也可以使用Groovy編寫奴拦,Java或者Scala也都可以,只要是基于JVM的語言都可以届吁。實(shí)際上错妖,大部分的Android Plugin都是Java與Groovy混編的绿鸣。
Creating a simple plugin
為了從已經(jīng)保存到build.gradle
中的構(gòu)建邏輯提取出來,我們可以在build.gradle
中創(chuàng)建一個(gè)Plugin暂氯。這是最簡(jiǎn)單的方法潮模。
為了創(chuàng)建一個(gè)Plugin,我們需要?jiǎng)?chuàng)建一個(gè)新的Class痴施,實(shí)現(xiàn)Plugin
接口擎厢。我們也將使用我們之前動(dòng)態(tài)創(chuàng)建Tasks的代碼。我們的Plugin類如下:
class RunPlugin implements Plugin<Project> {
void apply(Project project) {
project.android.applicationVariants.all { variant -> if (variant.install) {
project.tasks.create(name: "run${variant.name.capitalize()}", dependsOn: variant.install) {
// Task definition
}
}
}}
}
Plugin
接口定義了apply()
函數(shù)辣吃。Gradle會(huì)在插件使用的時(shí)候动遭,調(diào)用這個(gè)函數(shù)。Project
對(duì)象會(huì)作為參數(shù)傳遞齿尽,并且可以在Plugin中配置該project對(duì)象沽损,并且使用它的函數(shù)以及屬性。
在之前的例子中循头,我們需要首先需要訪問project
對(duì)象绵估,需要注意我們需要在build.gradle
中Apply這個(gè)Plugin才行,否則會(huì)導(dǎo)致異常卡骂。
為了保證這個(gè)Plugin在我們的構(gòu)建配置中被Apply国裳,需要在build.gradle
中添加以下這一行:
apply plugin: RunPlugin
Distributing plugins
為了發(fā)布一個(gè)Plugin,我們需要把它移動(dòng)到一個(gè)單獨(dú)的Module或者Project中全跨。一個(gè)單獨(dú)的Plugin擁有它自己的build.gradle
文件來配置dependencies缝左。這個(gè)Module會(huì)產(chǎn)生一個(gè)Jar文件,包括包含了Plugin的classes和屬性浓若。我們可以使用這個(gè)JAR文件將插件應(yīng)用到多個(gè)模塊和項(xiàng)目中渺杉,并與其他模塊共享。而Gradle工程挪钓,則需要?jiǎng)?chuàng)建一個(gè)build.gradle
文件進(jìn)行配置:
apply plugin: 'groovy'
dependencies {
compile gradleApi()
compile localGroovy()
}
一旦使用Groovy寫Plugin后是越,我們就需要應(yīng)用groovy
這個(gè)插件。Groovy Plugin集成自Java Plugin碌上,并且能讓我們構(gòu)建以及打包Groovy的類倚评。Groovy和Java都可以支持,所以我們可以混編馏予。你甚至可以使用Groovy來繼承一個(gè)Java類天梧。甚至你都感覺不到在使用Groovy。
為了開始我們單獨(dú)模塊的代碼霞丧,我們首先需要保證正確的目錄結(jié)構(gòu):
plugin
└── src
└── main
├── groovy
│ └─com
│ └─package
│ └── RunPlugin.groovy
└── resources
└── META-INF
└── gradle-plugins
對(duì)于任意的Gradle模塊呢岗,我們需要提供一個(gè)src/main
目錄。因?yàn)檫@是Groovy工程,main的子目錄使用groovy來替代java敷燎。而另外一個(gè)子目錄叫做resources
暂筝,用來指定我們Plugin的屬性。我們創(chuàng)建了一個(gè)文件名為:RunPlugin.groovy
在``package```目錄下硬贯,而這個(gè)目錄下我們會(huì)定義我們Plugin的類:
package com.gradleforandroid
import org.gradle.api.Project
import org.gradle.api.Plugin
class RunPlugin implements Plugin<Project> {
void apply(Project project) {
project.android.applicationVariants.all { variant ->
// Task code
}
}
}
為了Gradle能夠查找到這個(gè)Plugin焕襟,我們需要提供一個(gè)properties
的文件。并且將該文件放到src/main/resources/META-INF/gradle-plugins/
這個(gè)目錄下饭豹。這個(gè)文件的名字需要匹配Plugin的ID鸵赖。例如:RunPlugin
,這個(gè)文件名稱就叫做com.gradleforandroid.run.properties
拄衰,該文件的內(nèi)容為:
implementation-class=com.gradleforandroid.RunPlugin
這個(gè)properties
文件中唯一的東西就是包名以及Plugin具體實(shí)現(xiàn)的類名它褪。當(dāng)Plugin和Properties文件準(zhǔn)備完成,我們就可以通過gradlew assemble
命令來構(gòu)建Plugin了翘悉。這會(huì)在構(gòu)建的output
目錄下創(chuàng)建一個(gè)Jar文件茫打。如果你希望把這個(gè)插件發(fā)布到Maven倉庫上的話,你需要應(yīng)用Maven Plugin
apply plugin: 'maven'
然后配置uploadArchives
任務(wù):
uploadArchives {
repositories {
mavenDeployer {
repository(url: uri('repository_url'))
}
}
}
uploadArchives
是一個(gè)已經(jīng)定義過的Task妖混。一旦你配置了任務(wù)中的倉庫老赤,你就可以執(zhí)行它發(fā)布你的Plugin。
Using a custom plugin
為了使用一個(gè)Plugin制市,我們需要在buildscript中添加它作為dependency抬旺。首先,我們需要配置一個(gè)新的repository
祥楣。這個(gè)配置決定了Plugin如何被共享开财。然后,我們需要在dependencies
中配置Plugin的classpath误褪。如果你想包含一個(gè)Jar文件的話责鳍,我們可以定義flatDir
倉庫:
buildscript {
repositories {
flatDir { dirs 'build_libs' }
}
dependencies {
classpath 'com.gradleforandroid:plugin'
}
}
如果我們已經(jīng)在Maven或者Ivy倉庫中上傳了該插件的話,那么它就會(huì)有一點(diǎn)不一樣兽间。在我們?cè)O(shè)置了dependency之后薇搁,我們就可以應(yīng)用該P(yáng)lugin了:
apply plugin: com.gradleforandroid.RunPlugin
當(dāng)使用了apply()
方法,Gradle會(huì)創(chuàng)建一個(gè)Plugin的實(shí)例渡八,然后執(zhí)行Plugin的apply()
方法。