目前為止,我們已經(jīng)學習了如何修改Gradle構(gòu)建屬性步藕,以及如何運行任務惦界。本章,我們會深入了解這些屬性咙冗,并且創(chuàng)建我們自己的任務沾歪。一旦我們學會了創(chuàng)建任務,就可以更進一步雾消,學習如何創(chuàng)建Gradle插件灾搏。
在學習創(chuàng)建任務之前,我們需要學習一些重要的Groovy概念立润。因為了解一些Groovy基礎有助于我們自定義任務和插件狂窑。學習Groovy也可以幫助我們了解Gradle的原理,以及構(gòu)建文件的書寫格式桑腮。
本章內(nèi)容有:
- 了解Groovy
- 學習任務
- 接入Android插件
- 創(chuàng)建插件
了解Groovy
由于多數(shù)Android開發(fā)者精通Java泉哈,所以將Groovy和Java對比學習相對要容易一些。Groovy對于Java開發(fā)者來說很容易閱讀破讨,但如果不簡單了解一下的話丛晦,編寫起來還是有一定難度的。
使用Groovy Console來學習Groovy是一個很好的途徑提陶。這個應用包含在Groovy SDK中烫沙,可以立即執(zhí)行Groovy代碼得到結(jié)果。Groovy Console也可以運行純Java代碼隙笆,可以更加方便的對比Java和Groovy代碼锌蓄。你可以從http://groovy-lang.org/download.html下載帶有Groovy Console的Groovy SDK。
簡介
Groovy源自Java仲器,并在JVM上運行煤率。Groovy是一種簡單、直接的語言乏冀,既是腳本蝶糯,也是一種成熟的編程語言。本節(jié)我們通過對比來了解Groovy的原理以及和Java的不同辆沦。
Java打印字符串代碼如下:
System.out.println("Hello, world!");
而Groovy如下:
println 'Hello, world!'
兩者有幾個關鍵的不同之處:
- 沒有
System.out
命名空間 - 方法的參數(shù)沒有加圓括號
- 語句末尾沒有分號
示例同樣在字符串的兩側(cè)使用了單引號昼捍。你也可以使用雙引號,但兩者作用不完全一樣肢扯。雙引號的字符串可以包含插值表達式妒茬。插值是計算包含占位符的字符串的過程,并將占位符替換為真實值蔚晨。占位符表達式可以是變量或者方法乍钻。包含方法或者多個變量的占位符表達式需要放入{}
中肛循,并以$
作為前綴;僅包含單個變量的占位符表達式只需要以$
為前綴银择。下面是一些例子:
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."。
字符串插值也可以允許你動態(tài)執(zhí)行代碼浩考,如下:
def method = 'toString'
new Date()."$method"()
如果你已經(jīng)習慣了java夹孔,這看起來會很陌生,但這是動態(tài)編程語言的正常的語法和行為析孽。
類和成員
Groovy創(chuàng)建類和Java很相似搭伤。下面是包含一個成員的類:
class MyGroovyClass {
String greeting
String getGreeting() {
return 'Hello!'
}
}
注意類和成員都沒有明確的訪問修飾符。Groovy默認的訪問修飾符和Java不一樣袜瞬。類和方法默認是public的怜俐,成員默認是private的。
下面創(chuàng)建MyGroovyClass
的實例:
def instance = new MyGroovyClass()
instance.setGreeting 'Hello, Groovy!'
instance.getGreeting()
你可以使用def
關鍵字來創(chuàng)建新的變量邓尤。在有了類的示例之后佑菩,你就可以操作類成員了。Groovy會自動添加成員的訪問器裁赠。你也可以覆寫它們殿漠,比如這里我們覆寫了getGreeting()
方法。
你也可以直接調(diào)用成員佩捞,這實際也是在調(diào)用getter方法绞幌。也就是說你可以輸入instance.greeting
來代替instance.getGreeting()
:
println instance.getGreeting()
println instance.greeting
兩種方式結(jié)果一樣。
方法
和變量相似一忱,你不需要為方法指定返回類型莲蜘。你可以這么做,哪怕只是為了簡潔帘营。Groovy和Java方法的另一個不同之處是最后一行默認是返回值票渠,即使沒有使用return
關鍵字。
為了展示Java和Groovy方法的不同芬迄,參照下面的Java方法:
public int square(int num) {
return num * num;
}
square(2);
你需要指定方法的訪問類型问顷、返回值類型、參數(shù)類型禀梳,并在最有一行使用return
關鍵字杜窄。
在Groovy中,這個方法如下:
def square(def num) {
num * num
}
square 4
返回值類型和參數(shù)類型不必指明算途。def
關鍵字取代了明確的類型塞耕,返回值不需要return
關鍵字。盡管如此嘴瓤,為了清晰扫外,還是推薦使用return
關鍵字莉钙。調(diào)用方法時,你不必為它添加圓括號筛谚。
下面是Groovy更簡潔的定義方法的形式:
def square = { num ->
num * num
}
square 8
這不是一個常規(guī)的方法胆胰,而是一個閉包。Java沒有類似閉包的概念刻获。閉包在Groovy和Gradle中有非常重要的作用。
Closures(閉包)
閉包是一個可以有輸入和輸出的匿名代碼塊瞎嬉。它可以用來給變量賦值蝎毡,也可以作為方法的參數(shù)。
你可以在花括號中添加代碼塊來定義簡單的閉包氧枣。如果你想更明確一點沐兵,你可以將類型添加到定義中:
Closure square = {
it * it
}
square 16
Closure
顯示指明了代碼塊的類型為閉包。前面的示例還介紹了隱式無類型參數(shù)it
的概念便监。如果你沒有為閉包顯示添加參數(shù)扎谎,Groovy會自動添加一個,參數(shù)名為it
烧董,你可以在所有閉包中使用它毁靶。如果調(diào)用者沒有指定任何參數(shù),it
的值為null逊移。這使代碼稍微簡潔一些预吆,但只在有單個參數(shù)時有效。
在Gradle中胳泉,我們每時每刻都在使用閉包拐叉。本書中,目前為止我們說的塊就是指閉包扇商。也就是說凤瘦,android
塊和dependencies
塊都是閉包。
集合
在Gradle中使用Groovy時案铺,有兩個集合類型:list和map蔬芥。
Groovy創(chuàng)建list非常簡單。只需要:
List list = [1, 2, 3, 4, 5]
list的遍歷也非常簡單控汉“泳ィ可以使用each
方法:
list.each() { element ->
println element
}
你也可以使用it
來精簡代碼:
list.each() {
println it
}
map在Gradle的一些配置和方法中使用。map是一個鍵值對的集合暇番,可以如下定義:
Map pizzaPrices = [margherita:10, pepperoni:12]
可以使用get
方法或者方括號來訪問map嗤放。
pizzaPrices.get('pepperoni')
pizzaPrices['pepperoni']
Groovy還有一個更加簡單的方式”诔辏可以直接使用map.key
的語法來得到相應的值:
pizzaPrices.pepperoni
Groovy in Gradle
現(xiàn)在我們已經(jīng)了解了Groovy的基礎次酌,是時候回過頭來重新看下Gradle的構(gòu)建文件了恨课。我們可以比較容易地理解配置語法。比如岳服,Android插件是如何被引入構(gòu)建的:
apply plugin: 'com.android.application'
這塊代碼使用了Groovy的簡寫剂公。完全不簡寫的話,它是這個樣子的:
project.apply([plugin: 'com.android.application'])
這樣理解起來局容易多了吊宋。apply
是Project
類的一個方法纲辽,接收一個map
參數(shù),其中鍵為plugin
璃搜,值為com.android.application
拖吼。
另一個例子是dependencies
塊:
dependencies {
compile 'com.google.code.gson:gson:2.3'
}
現(xiàn)在我們知道了這個塊是一個閉包,作為Project
類dependencies()
方法的參數(shù)这吻。這個閉包會傳遞給DependencyHandler
對象吊档,該對象有add()
方法。這個方法由三個參數(shù):一個定義配置的字符串唾糯,一個定義依賴符號的對象怠硼,一個包含這個依賴的屬性的閉包。它的全寫如下:
project.dependencies({
add('compile', 'com.google.code.gson:gson:2.3', {
// Configuration statements
})
})
到現(xiàn)在為止你應該已經(jīng)可以看懂這些配置文件了移怯。
如果你想學習更多Gradle是如何使用Groovy的香璃,你可以從看Project的官方文檔看起,地址為: http://gradle.org/docs/current/javadoc/org/gradle/api/Project.html
學習任務
自定義Gradle任務可以顯著提高開發(fā)者的效率舟误。任務可以操作已有的構(gòu)建過程增显,添加新的構(gòu)建階段,或者影響構(gòu)建的輸出脐帝。你可以執(zhí)行簡單的任務同云,比如重命名生成的APK。任務可以使你運行更為復雜的代碼堵腹,比如在app打包前炸站,生成幾個不同密度的圖像。在學會創(chuàng)建任務之后疚顷,你就有能力去改變構(gòu)建過程的方方面面旱易。當你學習如何使用Android插件時,這一點尤其正確腿堤。
定義任務
任務屬于一個Project
對象阀坏,每個任務都實現(xiàn)了Task
接口。最簡單的定義任務的方法是執(zhí)行task
方法笆檀,并將任務名稱傳遞給它:
task hello
這就創(chuàng)建了一個任務忌堂,但是執(zhí)行它不會做任何事情。想要創(chuàng)建一個稍微有點作用的任務酗洒,你需要為它添加動作士修。初學者常犯的一個錯誤是這樣創(chuàng)建任務:
task hello {
println 'Hello, world!'
}
執(zhí)行的結(jié)果如下:
$ gradlew hello
Hello, world!
:hello
從結(jié)果看枷遂,你可能認為這個任務做事情了,但實際上棋嘲,"Hello,world!"是在任務執(zhí)行之前打印出來的酒唉。為了理解這個地方發(fā)生了什么,我們需要回過頭來看下基礎的東西沸移。在第一章哥倔,我們學習了Gradle構(gòu)建的生命周期:初始化階段肛捍、配置階段和執(zhí)行階段斩狱。在你像上例中那樣添加任務時驹马,你實際上設置了任務的配置。即使你執(zhí)行其他的任務笆制,"Hello,world!"也會打印。
如果你想在執(zhí)行階段為任務添加動作涣达,使用如下方式:
task hello << {
println 'Hello, world!'
}
唯一的區(qū)別是閉包前的<<
符號在辆。這會告知Gradle這段代碼是為執(zhí)行階段準備的,而不是配置階段度苔。
為了展示它們的區(qū)別匆篓,看下面的構(gòu)建文件:
task hello << {
println 'Execution'
}
hello {
println 'Configuration'
}
我們定義了任務hello
,在執(zhí)行時會打印"Execution"寇窑。我們也為hello
任務的配置階段定義了代碼鸦概,即打印"Configuration"。即使配置塊是在真正的任務定義代碼之后定義的甩骏,它也會先被執(zhí)行窗市。上例的輸出為:
$ gradlew hello
Configuration
:hello
Execution
錯誤的使用配置階段導致任務失敗是一個很常見的錯誤。在創(chuàng)建任務時需要牢記饮笛。
由于Groovy有很多簡寫咨察,Gradle定義任務有如下幾種形式:
task(hello) << {
println 'Hello, world!'
}
task('hello') << {
println 'Hello, world!'
}
tasks.create(name: 'hello') << {
println 'Hello, world!'
}
前兩個塊是Groovy實現(xiàn)相同功能的兩種不同方式。你可以使用圓括號福青,但不是必須的摄狱。你也可以不用單引號。在這兩個塊中无午,我們調(diào)用了task()
方法媒役,它需要兩個參數(shù):一個任務名稱和一個閉包。task()
方法是Project
類的方法宪迟。
最有一個塊沒有使用task()
方法酣衷,而是使用了Project
類的tasks
對象,該對象是TaskContainer
的實例次泽。這個類提供了一個create()
方法鸥诽,接收一個Map
和一個閉包作為參數(shù)商玫,返回一個Task
。
使用簡寫是非常便捷的書寫方式牡借,很多在線的示例和教程都是使用的簡寫拳昌。而全寫對初次學習非常有用,這樣钠龙,Gradle才看起來不那么神奇炬藤,理解起來更加容易。
任務剖析
Task
接口是所有任務的基礎碴里,定義了屬性和方法的集合沈矿。所有這些被DefaultTask
類實現(xiàn)。這是一個標準的任務類型的實現(xiàn)咬腋,在你定義一個新的任務時羹膳,它是基于DefaultTask
的。
從技術(shù)上講根竿,
DefaultTask
并不是真正的Task
接口所有方法的實現(xiàn)類陵像。Gradle有一個名為AbstractTask
的內(nèi)部類,包含所有方法的實現(xiàn)寇壳。因為AbstractTask
是內(nèi)部類醒颖,我們不能繼承它。所以我們才關注繼承自AbstractTask
的DefaultTask
類壳炎,并覆寫它泞歉。
每個任務有一個Action
對象的集合。在執(zhí)行任務時匿辩,所有的動作會被順序執(zhí)行腰耙。你可以使用doFirst()
和doLast()
方法添加動作。這兩個方法都以閉包作為參數(shù)铲球,然后將其封裝成一個Action
對象沟优。
你只有使用doFirst()
或者doLast()
方法才能為任務添加在執(zhí)行階段運行的代碼。我們之前使用的左移操作符<<
是doFirst()
方法的簡寫睬辐。
下面是使用這兩個方法的例子:
task hello {
println 'Configuration'
doLast {
println 'Goodbye'
}
doFirst {
println 'Hello'
}
}
執(zhí)行hello任務挠阁,輸出如下:
$ gradlew hello
Configuration
:hello
Hello
Goodbye
你也可以多次調(diào)用doFirst()
和doLast()
:
task mindTheOrder {
doFirst {
println 'Not really first.'
}
doFirst {
println 'First!'
}
doLast {
println 'Not really last.'
}
doLast {
println 'Last!'
}
}
該任務的輸出為:
$ gradlew mindTheOrder
:mindTheOrder
First!
Not really first.
Not really last.
Last!
注意doFirst()
總是將動作加到任務的開頭,doLast()
總是加到末尾溯饵。你在使用這樣的方法時需要謹慎侵俗,尤其是對順序有要求時。
對于需要順序執(zhí)行的任務丰刊,你可以使用mustRunAfter()
方法隘谣。這個方法允許你改變Gradle構(gòu)建的依賴圖。
task task1 << {
println 'task1'
}
task task2 << {
println 'task2'
}
task2.mustRunAfter task1
同時執(zhí)行兩個任務,task1總會在task2之前運行:
$ gradlew task2 task1
:task1
task1
:task2
task2
mustRunAfter()
方法并沒有在任務間添加依賴寻歧,你仍然可以單獨執(zhí)行task2掌栅,而不必執(zhí)行task1。如果你需要讓一個任務依賴另一個任務码泛,可以使用dependsOn()
方法猾封。mustRunAfter()
和dependsOn()
的區(qū)別可以從下面的示例提現(xiàn):
task task1 << {
println 'task1'
}
task task2 << {
println 'task2'
}
task2.dependsOn task1
單獨執(zhí)行task2,得到的結(jié)果如下:
$ gradlew task2
:task1
task1
:task2
task2
使用mustRunAfter()
噪珊,在同時執(zhí)行task1晌缘、task2時,task1總是在task2之前執(zhí)行痢站,但是它們?nèi)匀豢梢詥为殘?zhí)行磷箕。而使用dependsOn()
,單獨執(zhí)行task2總會先觸發(fā)執(zhí)行task1阵难。這是一個明顯的不同之處岳枷。
使用任務來簡化發(fā)布過程
在你將應用發(fā)布到Google Play商店之前,你需要用證書進行簽名呜叫。你需要有自己的keystore空繁,它包含一組私鑰。在你有了keystore和私鑰之后怀偷,可以在配置文件中定義簽名配置:
android {
signingConfigs {
release {
storeFile file("release.keystore")
storePassword "password"
keyAlias "ReleaseKey"
keyPassword "password"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
這種方法的缺點是家厌,你的keystore的密碼以純文本的方式保存在倉庫中播玖。如果你是在做開源項目椎工,這是絕對不行的;任何一個有keystore文件和私鑰的人可以用你的身份發(fā)布應用蜀踏。為了防止這種情況的發(fā)生维蒙,你需要創(chuàng)建一個任務,在每次生成發(fā)布包時果覆,查詢發(fā)布密碼颅痊。雖然這有點麻煩,但它確實能使構(gòu)建服務器自動生成發(fā)布版本局待。一個不錯的保存keystore密碼的解決方案是創(chuàng)建一個不保存在倉庫中的配置文件斑响。
在項目的根目錄創(chuàng)建一個private.properties
文件,添加如下代碼:
release.password = thepassword
我們假設keystore和key本身的密碼相同钳榨。如果你的密碼不同舰罚,也可以添加第二個屬性。
設置好這些薛耻,你可以定義一個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')
}
}
這個任務會在項目根目錄查詢一個名額外private.properties
的文件营罢。如果文件存在,該任務會加載所有的屬性信息饼齿。properties.load()
方法會查詢鍵值對饲漾,比如我們定義的release.password
蝙搔。
為了確保任何人在沒有private.properties
文件或者屬性文件存在,但沒有release.password
屬性的情況下都可以運行腳本考传,需要添加一個反饋吃型。如果密碼為空,在控制臺要求輸入密碼:
if (!password?.trim()) {
password = new String(System.console().readPassword("\nWhat's the secret password? "))
}
Groovy檢查字符串是否為空是非常簡單的伙菊。password?.trim()
中的問號是一個null檢查败玉,在password為null時,不會調(diào)用trim()
方法镜硕。我們不用進行明確的空判斷运翼,因為空的字符串在if
語句中會返回false
。
new String()
是必須的兴枯,因為System.readPassword()
會返回一個字節(jié)數(shù)組血淌,我們需要顯示轉(zhuǎn)換為字符串。
在有了keystore的密碼之后财剖,我們就可以為release版本配置簽名配置了悠夯。
android.signingConfigs.release.storePassword = password
android.signingConfigs.release.keyPassword = password
任務寫完了,我們還要確保被執(zhí)行躺坟。在build.gradle
文件中添加如下代碼:
tasks.whenTaskAdded { theTask ->
if (theTask.name.equals("packageRelease")) {
theTask.dependsOn "getReleasePassword"
}
}
這段代碼會將一個閉包與Gradle和Android插件掛鉤沦补,在任務檢入依賴圖時,代碼會被執(zhí)行咪橙。packageRelease
執(zhí)行之前夕膀,不會要求密碼,所以我們確保packageRelease
依賴getReleasePassword
任務美侦。我們不可以僅僅使用packageRelease.dependsOn()
产舞,因為打包任務是Android插件動態(tài)生成的,依賴于構(gòu)建變體菠剩。也就是說易猫,在Android插件查詢到所有的構(gòu)建變體之前,packageRelease
任務是不存在的具壮。這個查找過程在構(gòu)建之前開始准颓。
想要這個任務工作,跟Gradle和Android插件掛鉤是必須的棺妓。這是一個強有力的概念攘已,我們會探索更多的細節(jié)。
與Android插件掛鉤
在Android開發(fā)中涧郊,我們想要影響的多數(shù)任務都和Android插件相關贯被。可以通過掛鉤構(gòu)建過程來增加任務的行為。上例我們學習到了在常規(guī)構(gòu)建中彤灶,怎樣向一個Android任務添加新的依賴任務看幼。本節(jié),我們將研究一些Android特有構(gòu)建的鉤子幌陕。
一個掛鉤Android插件的方法是修改構(gòu)建變體诵姜。這么做非常直接,你只需要用下面的代碼片段就可以遍歷所有的構(gòu)建變體:
android.applicationVariants.all { variant ->
// Do something
}
你可以使用applicationVariants
對象來獲取構(gòu)建變體的集合搏熄。拿到構(gòu)建變體的引用后棚唆,你就可以訪問和更改它的屬性了,比如name,description等心例。如果你想在Android library上使用相同的邏輯宵凌,可以使用libraryVariants
對象。
注意我們遍歷構(gòu)建變體時止后,使用的是
all()
而不是each()
方法瞎惫。因為each()
的觸發(fā)是在evaluation階段,在Android插件創(chuàng)建構(gòu)建變體之前译株。all()
方法在每次集合加入新元素時就會觸發(fā)瓜喇。
這個鉤子可以用來在APK保存之前修改名稱,或者在文件名中添加版本號歉糜。這就使維護APK變得容易乘寒,因為你不需要手動編輯文件名。下一節(jié)我們會學習如何實現(xiàn)它匪补。
自動重命名APK
一個常見的修改構(gòu)建構(gòu)成的應用場景是在APK打包后伞辛,重命名以包含版本信息。你可以遍歷應用的構(gòu)建變體叉袍,修改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òu)建變體有一個輸出的集合始锚。一個Android應用的輸出是一個APK刽酱。每個輸出對象有一個File
類型的屬性outputFile
喳逛。知道了輸出的路徑之后,你就可以修改它棵里。本例中我們在文件名中加入了版本信息润文。
強大的Android插件鉤子和簡潔的Gradle任務的結(jié)合給了我們無限的可能。下一節(jié)殿怜,我們學習如何為應用的每個構(gòu)建變體創(chuàng)建一個任務典蝌。
動態(tài)創(chuàng)建任務
基于Gradle的工作方式和任務的構(gòu)造方式,在Android構(gòu)建變體的基礎上头谜,我們可以在配置階段創(chuàng)建新的任務骏掀。為了展示這個強大的概念,我們將創(chuàng)建一個任務,用來安裝截驮、運行Android應用的任何一個構(gòu)建變體笑陈。install
任務是Android插件定義的,但是如果你是用命令行運行installDebug
任務來安裝應用葵袭,安裝結(jié)束后涵妥,你需要手動啟動它。本節(jié)我們將創(chuàng)建的任務會解決最后一步的問題坡锡。
首先與applicationVariants
屬性掛鉤:
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."
}
}
}
對每一個變體蓬网,檢查它是否有install
任務。這是必需的鹉勒,因為我們要創(chuàng)建的新的任務需要依賴于它帆锋。確定install
任務存在后,我們基于變體的名稱創(chuàng)建一個新任務禽额,該任務依賴于variant.install
窟坐,執(zhí)行時會觸發(fā)install
任務。task.create()
的閉包中添加了描述字段绵疲,在執(zhí)行gradlew tasks
時會顯示出來哲鸳。
除了添加描述外,我們還需要添加任務執(zhí)行的動作盔憨。本例中徙菠,我們想要啟動應用。Android Debug Tool(ADB)提供了啟動真機或者模擬器應用的命令:
$ adb shell am start -n com.package.name/com.package.name.Activity
Gradle有一個exec()
的方法郁岩,可以執(zhí)行命令行婿奔。為使exec()
工作,我們需要加到PATH環(huán)境變量中问慎。我們同樣需要把所有的參數(shù)傳遞給args
屬性:
doFirst {
exec {
executable = 'adb'
args = ['shell', 'am', 'start', '-n',"${variant.applicationId}/.MainActivity"]
}
}
為了獲取包名萍摊,可以使用構(gòu)建變體的applicationId,它可能包含一個后綴(如果提供的話)如叼。在這個例子中冰木,后綴會有一個問題。即使我們添加了后綴笼恰,activity的路徑也是不會變的踊沸,比如:
android {
defaultConfig {
applicationId 'com.gradleforandroid'
}
buildTypes {
debug {
applicationIdSuffix '.debug'
}
}
}
包名是com.gradleforandroid.debug
,但是activity的路徑依然是com.gradleforandroid.Activity
。為了得到正確的路徑社证,我們需要去掉后綴:
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)建了一個classpath
變量,并賦值為applicationId追葡。接下來腺律,查找后綴奕短。在Groovy中,字符串可以使用-
做減法匀钧。這些更改確保在添加后綴的情況下篡诽,應用安裝完成后可以自動運行。
創(chuàng)建插件
如果你有一些想要在多個工程中復用的任務榴捡,可以考慮將任務提取到自定義的插件中杈女。這樣你就可以復用構(gòu)建邏輯,或者將它分享出去吊圾。
插件可以由Groovy編寫达椰,也可以基于JVM的其它語言,比如Java项乒、Scala啰劲。實際上,Gradle Android插件的大部分是有Java和Groovy混合編寫的檀何。
創(chuàng)建一個簡單的插件
為了提取構(gòu)建文件中的構(gòu)建邏輯蝇裤,你可以在build.gradle
文件中創(chuàng)建一個插件。這是最簡單的自定義插件的方式频鉴。
為了創(chuàng)建插件栓辜,首先需要創(chuàng)建一個實現(xiàn)了Plugin
接口的類。我們將使用本章已寫過的動態(tài)創(chuàng)建run
任務的代碼垛孔。插件類如下:
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()
方法藕甩。構(gòu)建文件使用插件時,Gradle會調(diào)用這個方法周荐。project
會作為參數(shù)傳遞過來狭莱,這樣插件就可以配置項目或者使用項目的方法和屬性。在上一個例子中概作,我們不能從Android插件直接調(diào)用屬性腋妙,而是應該首先訪問project對象。需要注意的是讯榕,這要求Android插件在我們的插件之前被引入到項目中骤素,否則,project.android
會引發(fā)異常瘩扼。
任務的代碼和之前是一樣的谆甜,只是exec()
需要用project.exec()
替換垃僚。
將該插件引入到構(gòu)建文件中:
apply plugin: RunPlugin
分發(fā)插件
為了分發(fā)插件并分享給其他人集绰,你需要將它移動到一個單獨的模塊(或項目)中。單獨的插件有它自己的構(gòu)建文件來配置依賴和分發(fā)的方法谆棺。這個模塊生成一個JAR文件栽燕,包含插件類和屬性罕袋。你可以使用這個JAR文件將插件應用到模塊或者項目中,也可以分享給他人碍岔。
和其他Gradle項目一樣浴讯,創(chuàng)建一個build.gradle
來配置構(gòu)建:
apply plugin: 'groovy'
dependencies {
compile gradleApi()
compile localGroovy()
}
由于我們是用Groovy寫的插件,所以需要引入Groovy插件蔼啦。Groovy插件繼承自Java插件榆纽,使我們可以構(gòu)建和打包Groovy類。Groovy同樣支持Java捏肢,你可以混合使用奈籽。你甚至可以使用Groovy擴展Java類。這使Groovy很容易上手鸵赫。
gradleApi()
依賴用于在我們的插件中訪問Gradle的命名空間衣屏。
localGroovy()
依賴是Groovy SDK在Gradle中安裝的發(fā)行版。
這兩個依賴是Gradle自動添加的辩棒。
如果你計劃公開發(fā)布你的插件狼忱,請確保在構(gòu)建文件中定義了group和version信息,例如
group = 'com.gradleforandroid'
version = '1.0'
為了使用獨立模塊的代碼一睁,我們還需要確保正確的目錄結(jié)構(gòu):
plugin
└── src
└── main
├── groovy
│ └── com
│ └── package
│ └── name
└── resources
└── META-INF
└── gradle-plugins
和其他的Gradle模塊一樣钻弄,我們需要提供一個src/main
目錄。因為這是一個Groovy工程者吁,所以main的子目錄是groovy
而不是java
斧蜕。main的另一個子目錄是resources
,用來指定插件的屬性砚偶。
我們在包目錄創(chuàng)建了一個RunPlugin.groovy
的類文件:
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能夠找到插件批销,我們需要提供一個屬性文件。將屬性文件添加到src/main/resources/META-INF/gradle-plugins/
目錄染坯。文件名需要匹配插件的ID均芽。對于RunPlugin
來說,屬性文件名稱為com.gradleforandroid.run.properties
单鹿,它的內(nèi)容是:
implementation-class=com.gradleforandroid.RunPlugin
屬性文件內(nèi)容僅僅是實現(xiàn)了Plugin接口的類的包名和類名掀宋。
準備好接口和屬性文件后,我們可以使用gradlew assemble
命令來構(gòu)建插件仲锄,這會在構(gòu)建輸出目錄創(chuàng)建一個JAR文件劲妙。如果你想將插件上傳到Maven倉庫,你首先需要引入Maven插件:
apply plugin: 'maven'
然后儒喊,你需要配置uploadArchives
任務:
uploadArchives {
repositories {
mavenDeployer {
repository(url: uri('repository_url'))
}
}
}
uploadArchives
是預定義的任務镣奋。為這個任務配置了倉庫之后,你就可以運行它來發(fā)布你的插件怀愧。本書不會講述如何設置Maven倉庫侨颈。如果你想讓你的插件公開可用余赢,可以發(fā)布到Gradleware的插件門戶(https://plugins.gradle.org)。插件門戶有一大批Gradle插件(不僅僅是Android的)哈垢,如果你想擴展Gradle的默認行為妻柒,可以去瀏覽一下。你在https://plugins.gradle.org/docs/submit網(wǎng)站可以學習如何發(fā)布插件耘分。
為自定義插件寫測試不是本書的內(nèi)容举塔,如果你想公開發(fā)布插件,強烈建議進行測試求泰。你可以在Gradle用戶指南https://gradle.org/docs/current/userguide/custom_plugins.html#N16CE1學習編寫測試
使用自定義插件
要使用插件啤贩,需要在buildscript
塊將其添加為依賴。首先拜秧,我們需要配置一個新倉庫痹屹。倉庫的配置依賴于插件發(fā)布的途徑。其次枉氮,我們需要在dependencies
塊配置插件的類路徑志衍。
如果你想引入上例創(chuàng)建的JAR文件,可以定義一個flatDir
倉庫:
buildscript {
repositories {
flatDir { dirs 'build_libs' }
}
dependencies {
classpath 'com.gradleforandroid:plugin'
}
}
如果已經(jīng)將插件上傳到Maven或者Ivy倉庫聊替,配置會有所不同楼肪。我們在第3章講了配置管理,所以我們將不再講述不同之處惹悄。
配置好之后春叫,我們需要應用插件:
apply plugin: com.gradleforandroid.RunPlugin
在使用apply()
方法的時候,Gradle會創(chuàng)建一個插件類的實例泣港,并執(zhí)行插件自己的apply()
方法暂殖。