Gradle For Android(7)--創(chuàng)建Task以及Plugin

介紹

到目前為止囊嘉,我們已經(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)限兜蠕,你也可以重寫他們扰肌。就像我們定義了getGreetingMyGroovyClass中。如果沒有指定的話熊杨,可以使用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í)行瞒斩。我們可以使用doFirstdoLast函數(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é)果如下:

Hook Android Plugin

就像上面截圖所示,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()方法。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末传货,一起剝皮案震驚了整個(gè)濱河市屎鳍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌问裕,老刑警劉巖逮壁,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異粮宛,居然都是意外死亡窥淆,警方通過查閱死者的電腦和手機(jī)卖宠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忧饭,“玉大人扛伍,你說我怎么就攤上這事〈士悖” “怎么了刺洒?”我有些...
    開封第一講書人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吼砂。 經(jīng)常有香客問我逆航,道長,這世上最難降的妖魔是什么渔肩? 我笑而不...
    開封第一講書人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任因俐,我火速辦了婚禮,結(jié)果婚禮上周偎,老公的妹妹穿的比我還像新娘抹剩。我一直安慰自己,他們只是感情好栏饮,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開白布吧兔。 她就那樣靜靜地躺著,像睡著了一般袍嬉。 火紅的嫁衣襯著肌膚如雪境蔼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,806評(píng)論 1 290
  • 那天伺通,我揣著相機(jī)與錄音箍土,去河邊找鬼。 笑死罐监,一個(gè)胖子當(dāng)著我的面吹牛吴藻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弓柱,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼沟堡,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了矢空?” 一聲冷哼從身側(cè)響起航罗,我...
    開封第一講書人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎屁药,沒想到半個(gè)月后粥血,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年复亏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了趾娃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缔御,死狀恐怖抬闷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情刹淌,我是刑警寧澤饶氏,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站有勾,受9級(jí)特大地震影響疹启,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蔼卡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一喊崖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雇逞,春花似錦荤懂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至掉蔬,卻和暖如春廊宪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背女轿。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來泰國打工箭启, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蛉迹。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓傅寡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親北救。 傳聞我的和親對(duì)象是個(gè)殘疾皇子荐操,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 這篇文章講給大家?guī)韌radle打包系列中的高級(jí)用法-自己動(dòng)手編寫gradle插件。我們平常在做安卓開發(fā)時(shí)珍策,都會(huì)在...
    呆萌狗和求疵喵閱讀 15,976評(píng)論 22 80
  • 說明 本文主要從實(shí)現(xiàn)原理和代碼層面介紹Gradle開發(fā)相關(guān)知識(shí)淀零。關(guān)于本文中提到的、Gradle中的基本概念等內(nèi)容膛壹,...
    jzj1993閱讀 7,897評(píng)論 1 33
  • 目前為止,我們已經(jīng)學(xué)習(xí)了如何修改Gradle構(gòu)建屬性,以及如何運(yùn)行任務(wù)模聋。本章肩民,我們會(huì)深入了解這些屬性,并且創(chuàng)建我們...
    sollian閱讀 2,602評(píng)論 0 8
  • http://www.reibang.com/p/7c288a17cda8 總的來說链方,Android的系統(tǒng)體系結(jié)...
    燕京博士閱讀 1,188評(píng)論 0 6
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理持痰,服務(wù)發(fā)現(xiàn),斷路器祟蚀,智...
    卡卡羅2017閱讀 134,633評(píng)論 18 139