Gradle for Android(七) 創(chuàng)建任務和插件

目前為止,我們已經(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'])

這樣理解起來局容易多了吊宋。applyProject類的一個方法纲辽,接收一個map參數(shù),其中鍵為plugin璃搜,值為com.android.application拖吼。

另一個例子是dependencies塊:

dependencies {
    compile 'com.google.code.gson:gson:2.3'
}

現(xiàn)在我們知道了這個塊是一個閉包,作為Projectdependencies()方法的參數(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)部類醒颖,我們不能繼承它。所以我們才關注繼承自AbstractTaskDefaultTask類壳炎,并覆寫它泞歉。

每個任務有一個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()方法暂殖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市当纱,隨后出現(xiàn)的幾起案子呛每,更是在濱河造成了極大的恐慌,老刑警劉巖坡氯,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晨横,死亡現(xiàn)場離奇詭異,居然都是意外死亡箫柳,警方通過查閱死者的電腦和手機手形,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悯恍,“玉大人库糠,你說我怎么就攤上這事∑夯” “怎么了曼玩?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵鳞骤,是天一觀的道長窒百。 經(jīng)常有香客問我黍判,道長,這世上最難降的妖魔是什么篙梢? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任顷帖,我火速辦了婚禮,結(jié)果婚禮上渤滞,老公的妹妹穿的比我還像新娘贬墩。我一直安慰自己,他們只是感情好妄呕,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布陶舞。 她就那樣靜靜地躺著,像睡著了一般绪励。 火紅的嫁衣襯著肌膚如雪肿孵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天疏魏,我揣著相機與錄音停做,去河邊找鬼。 笑死大莫,一個胖子當著我的面吹牛蛉腌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播只厘,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼烙丛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了羔味?” 一聲冷哼從身側(cè)響起蜀变,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎介评,沒想到半個月后库北,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡们陆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年寒瓦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坪仇。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡杂腰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出椅文,到底是詐尸還是另有隱情喂很,我是刑警寧澤惜颇,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站少辣,受9級特大地震影響凌摄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜漓帅,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一锨亏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧忙干,春花似錦器予、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至施戴,卻和暖如春反浓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背暇韧。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工勾习, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人懈玻。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓耕蝉,卻偏偏與公主長得像付鹿,于是被迫代替她去往敵國和親棚品。 傳聞我的和親對象是個殘疾皇子傅寡,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,498評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)湾盒,斷路器湿右,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • 1.介紹 如果你正在查閱build.gradle文件的所有可選項,請點擊這里進行查閱:DSL參考 1.1新構(gòu)建系統(tǒng)...
    Chuckiefan閱讀 12,118評論 8 72
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評論 6 342
  • 一別便是一生
    玉梅_901d閱讀 97評論 0 0