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

目前為止,我們已經學習了如何修改Gradle構建屬性,以及如何運行任務。本章,我們會深入了解這些屬性,并且創建我們自己的任務。一旦我們學會了創建任務,就可以更進一步,學習如何創建Gradle插件。

在學習創建任務之前,我們需要學習一些重要的Groovy概念。因為了解一些Groovy基礎有助于我們自定義任務和插件。學習Groovy也可以幫助我們了解Gradle的原理,以及構建文件的書寫格式。

本章內容有:

  • 了解Groovy
  • 學習任務
  • 接入Android插件
  • 創建插件

了解Groovy

由于多數Android開發者精通Java,所以將Groovy和Java對比學習相對要容易一些。Groovy對于Java開發者來說很容易閱讀,但如果不簡單了解一下的話,編寫起來還是有一定難度的。

使用Groovy Console來學習Groovy是一個很好的途徑。這個應用包含在Groovy SDK中,可以立即執行Groovy代碼得到結果。Groovy Console也可以運行純Java代碼,可以更加方便的對比Java和Groovy代碼。你可以從http://groovy-lang.org/download.html下載帶有Groovy Console的Groovy SDK。

簡介

Groovy源自Java,并在JVM上運行。Groovy是一種簡單、直接的語言,既是腳本,也是一種成熟的編程語言。本節我們通過對比來了解Groovy的原理以及和Java的不同。

Java打印字符串代碼如下:

System.out.println("Hello, world!");

而Groovy如下:

println 'Hello, world!'

兩者有幾個關鍵的不同之處:

  • 沒有System.out命名空間
  • 方法的參數沒有加圓括號
  • 語句末尾沒有分號

示例同樣在字符串的兩側使用了單引號。你也可以使用雙引號,但兩者作用不完全一樣。雙引號的字符串可以包含插值表達式。插值是計算包含占位符的字符串的過程,并將占位符替換為真實值。占位符表達式可以是變量或者方法。包含方法或者多個變量的占位符表達式需要放入{}中,并以$作為前綴;僅包含單個變量的占位符表達式只需要以$為前綴。下面是一些例子:

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."。

字符串插值也可以允許你動態執行代碼,如下:

def method = 'toString'
new Date()."$method"()

如果你已經習慣了java,這看起來會很陌生,但這是動態編程語言的正常的語法和行為。

類和成員

Groovy創建類和Java很相似。下面是包含一個成員的類:

class MyGroovyClass {
    String greeting

    String getGreeting() {
        return 'Hello!'
    }
}

注意類和成員都沒有明確的訪問修飾符。Groovy默認的訪問修飾符和Java不一樣。類和方法默認是public的,成員默認是private的。

下面創建MyGroovyClass的實例:

def instance = new MyGroovyClass()
instance.setGreeting 'Hello, Groovy!'
instance.getGreeting()

你可以使用def關鍵字來創建新的變量。在有了類的示例之后,你就可以操作類成員了。Groovy會自動添加成員的訪問器。你也可以覆寫它們,比如這里我們覆寫了getGreeting()方法。

你也可以直接調用成員,這實際也是在調用getter方法。也就是說你可以輸入instance.greeting來代替instance.getGreeting()

println instance.getGreeting()
println instance.greeting

兩種方式結果一樣。

方法

和變量相似,你不需要為方法指定返回類型。你可以這么做,哪怕只是為了簡潔。Groovy和Java方法的另一個不同之處是最后一行默認是返回值,即使沒有使用return關鍵字。

為了展示Java和Groovy方法的不同,參照下面的Java方法:

public int square(int num) {
    return num * num;
}
square(2);

你需要指定方法的訪問類型、返回值類型、參數類型,并在最有一行使用return關鍵字。

在Groovy中,這個方法如下:

def square(def num) {
    num * num
}
square 4

返回值類型和參數類型不必指明。def關鍵字取代了明確的類型,返回值不需要return關鍵字。盡管如此,為了清晰,還是推薦使用return關鍵字。調用方法時,你不必為它添加圓括號。

下面是Groovy更簡潔的定義方法的形式:

def square = { num ->
    num * num
}
square 8

這不是一個常規的方法,而是一個閉包。Java沒有類似閉包的概念。閉包在Groovy和Gradle中有非常重要的作用。

Closures(閉包)

閉包是一個可以有輸入和輸出的匿名代碼塊。它可以用來給變量賦值,也可以作為方法的參數。

你可以在花括號中添加代碼塊來定義簡單的閉包。如果你想更明確一點,你可以將類型添加到定義中:

Closure square = {
    it * it
}
square 16

Closure顯示指明了代碼塊的類型為閉包。前面的示例還介紹了隱式無類型參數it的概念。如果你沒有為閉包顯示添加參數,Groovy會自動添加一個,參數名為it,你可以在所有閉包中使用它。如果調用者沒有指定任何參數,it的值為null。這使代碼稍微簡潔一些,但只在有單個參數時有效。

在Gradle中,我們每時每刻都在使用閉包。本書中,目前為止我們說的塊就是指閉包。也就是說,android塊和dependencies塊都是閉包。

集合

在Gradle中使用Groovy時,有兩個集合類型:list和map。

Groovy創建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

現在我們已經了解了Groovy的基礎,是時候回過頭來重新看下Gradle的構建文件了。我們可以比較容易地理解配置語法。比如,Android插件是如何被引入構建的:

apply plugin: 'com.android.application'

這塊代碼使用了Groovy的簡寫。完全不簡寫的話,它是這個樣子的:

project.apply([plugin: 'com.android.application'])

這樣理解起來局容易多了。applyProject類的一個方法,接收一個map參數,其中鍵為plugin,值為com.android.application

另一個例子是dependencies塊:

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

現在我們知道了這個塊是一個閉包,作為Projectdependencies()方法的參數。這個閉包會傳遞給DependencyHandler對象,該對象有add()方法。這個方法由三個參數:一個定義配置的字符串,一個定義依賴符號的對象,一個包含這個依賴的屬性的閉包。它的全寫如下:

project.dependencies({
    add('compile', 'com.google.code.gson:gson:2.3', {
        // Configuration statements
    })
})

到現在為止你應該已經可以看懂這些配置文件了。

如果你想學習更多Gradle是如何使用Groovy的,你可以從看Project的官方文檔看起,地址為: http://gradle.org/docs/current/javadoc/org/gradle/api/Project.html

學習任務

自定義Gradle任務可以顯著提高開發者的效率。任務可以操作已有的構建過程,添加新的構建階段,或者影響構建的輸出。你可以執行簡單的任務,比如重命名生成的APK。任務可以使你運行更為復雜的代碼,比如在app打包前,生成幾個不同密度的圖像。在學會創建任務之后,你就有能力去改變構建過程的方方面面。當你學習如何使用Android插件時,這一點尤其正確。

定義任務

任務屬于一個Project對象,每個任務都實現了Task接口。最簡單的定義任務的方法是執行task方法,并將任務名稱傳遞給它:

task hello

這就創建了一個任務,但是執行它不會做任何事情。想要創建一個稍微有點作用的任務,你需要為它添加動作。初學者常犯的一個錯誤是這樣創建任務:

task hello {
    println 'Hello, world!'
}

執行的結果如下:

$ gradlew hello
Hello, world!
:hello

從結果看,你可能認為這個任務做事情了,但實際上,"Hello,world!"是在任務執行之前打印出來的。為了理解這個地方發生了什么,我們需要回過頭來看下基礎的東西。在第一章,我們學習了Gradle構建的生命周期:初始化階段、配置階段和執行階段。在你像上例中那樣添加任務時,你實際上設置了任務的配置。即使你執行其他的任務,"Hello,world!"也會打印。

如果你想在執行階段為任務添加動作,使用如下方式:

task hello << {
    println 'Hello, world!'
}

唯一的區別是閉包前的<<符號。這會告知Gradle這段代碼是為執行階段準備的,而不是配置階段。

為了展示它們的區別,看下面的構建文件:

task hello << {
    println 'Execution'
}

hello {
    println 'Configuration'
}

我們定義了任務hello,在執行時會打印"Execution"。我們也為hello任務的配置階段定義了代碼,即打印"Configuration"。即使配置塊是在真正的任務定義代碼之后定義的,它也會先被執行。上例的輸出為:

$ gradlew hello
Configuration
:hello
Execution

錯誤的使用配置階段導致任務失敗是一個很常見的錯誤。在創建任務時需要牢記。

由于Groovy有很多簡寫,Gradle定義任務有如下幾種形式:

task(hello) << {
    println 'Hello, world!'
}

task('hello') << {
    println 'Hello, world!'
}

tasks.create(name: 'hello') << {
    println 'Hello, world!'
}

前兩個塊是Groovy實現相同功能的兩種不同方式。你可以使用圓括號,但不是必須的。你也可以不用單引號。在這兩個塊中,我們調用了task()方法,它需要兩個參數:一個任務名稱和一個閉包。task()方法是Project類的方法。

最有一個塊沒有使用task()方法,而是使用了Project類的tasks對象,該對象是TaskContainer的實例。這個類提供了一個create()方法,接收一個Map和一個閉包作為參數,返回一個Task

使用簡寫是非常便捷的書寫方式,很多在線的示例和教程都是使用的簡寫。而全寫對初次學習非常有用,這樣,Gradle才看起來不那么神奇,理解起來更加容易。

任務剖析

Task接口是所有任務的基礎,定義了屬性和方法的集合。所有這些被DefaultTask類實現。這是一個標準的任務類型的實現,在你定義一個新的任務時,它是基于DefaultTask的。

從技術上講,DefaultTask并不是真正的Task接口所有方法的實現類。Gradle有一個名為AbstractTask的內部類,包含所有方法的實現。因為AbstractTask是內部類,我們不能繼承它。所以我們才關注繼承自AbstractTaskDefaultTask類,并覆寫它。

每個任務有一個Action對象的集合。在執行任務時,所有的動作會被順序執行。你可以使用doFirst()doLast()方法添加動作。這兩個方法都以閉包作為參數,然后將其封裝成一個Action對象。

你只有使用doFirst()或者doLast()方法才能為任務添加在執行階段運行的代碼。我們之前使用的左移操作符<<doFirst()方法的簡寫。

下面是使用這兩個方法的例子:

task hello {
    println 'Configuration'

    doLast {
        println 'Goodbye'
    }

    doFirst {
        println 'Hello'
    }
}

執行hello任務,輸出如下:

$ gradlew hello
Configuration
:hello
Hello
Goodbye

你也可以多次調用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()總是加到末尾。你在使用這樣的方法時需要謹慎,尤其是對順序有要求時。

對于需要順序執行的任務,你可以使用mustRunAfter()方法。這個方法允許你改變Gradle構建的依賴圖。

task task1 << {
    println 'task1'
}

task task2 << {
    println 'task2'
}

task2.mustRunAfter task1

同時執行兩個任務,task1總會在task2之前運行:

$ gradlew task2 task1
:task1
task1
:task2
task2

mustRunAfter()方法并沒有在任務間添加依賴,你仍然可以單獨執行task2,而不必執行task1。如果你需要讓一個任務依賴另一個任務,可以使用dependsOn()方法。mustRunAfter()dependsOn()的區別可以從下面的示例提現:

task task1 << {
    println 'task1'
}

task task2 << {
    println 'task2'
}

task2.dependsOn task1

單獨執行task2,得到的結果如下:

$ gradlew task2
:task1
task1
:task2
task2

使用mustRunAfter(),在同時執行task1、task2時,task1總是在task2之前執行,但是它們仍然可以單獨執行。而使用dependsOn(),單獨執行task2總會先觸發執行task1。這是一個明顯的不同之處。

使用任務來簡化發布過程

在你將應用發布到Google Play商店之前,你需要用證書進行簽名。你需要有自己的keystore,它包含一組私鑰。在你有了keystore和私鑰之后,可以在配置文件中定義簽名配置:

android {
    signingConfigs {
        release {
            storeFile file("release.keystore")
            storePassword "password"
            keyAlias "ReleaseKey"
            keyPassword "password"
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

這種方法的缺點是,你的keystore的密碼以純文本的方式保存在倉庫中。如果你是在做開源項目,這是絕對不行的;任何一個有keystore文件和私鑰的人可以用你的身份發布應用。為了防止這種情況的發生,你需要創建一個任務,在每次生成發布包時,查詢發布密碼。雖然這有點麻煩,但它確實能使構建服務器自動生成發布版本。一個不錯的保存keystore密碼的解決方案是創建一個不保存在倉庫中的配置文件。

在項目的根目錄創建一個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時,不會調用trim()方法。我們不用進行明確的空判斷,因為空的字符串在if語句中會返回false

new String()是必須的,因為System.readPassword()會返回一個字節數組,我們需要顯示轉換為字符串。

在有了keystore的密碼之后,我們就可以為release版本配置簽名配置了。

android.signingConfigs.release.storePassword = password
android.signingConfigs.release.keyPassword = password

任務寫完了,我們還要確保被執行。在build.gradle文件中添加如下代碼:

tasks.whenTaskAdded { theTask ->
    if (theTask.name.equals("packageRelease")) {
        theTask.dependsOn "getReleasePassword"
    }
}

這段代碼會將一個閉包與Gradle和Android插件掛鉤,在任務檢入依賴圖時,代碼會被執行。packageRelease執行之前,不會要求密碼,所以我們確保packageRelease依賴getReleasePassword任務。我們不可以僅僅使用packageRelease.dependsOn(),因為打包任務是Android插件動態生成的,依賴于構建變體。也就是說,在Android插件查詢到所有的構建變體之前,packageRelease任務是不存在的。這個查找過程在構建之前開始。

想要這個任務工作,跟Gradle和Android插件掛鉤是必須的。這是一個強有力的概念,我們會探索更多的細節。

與Android插件掛鉤

在Android開發中,我們想要影響的多數任務都和Android插件相關。可以通過掛鉤構建過程來增加任務的行為。上例我們學習到了在常規構建中,怎樣向一個Android任務添加新的依賴任務。本節,我們將研究一些Android特有構建的鉤子。

一個掛鉤Android插件的方法是修改構建變體。這么做非常直接,你只需要用下面的代碼片段就可以遍歷所有的構建變體:

android.applicationVariants.all { variant ->
    // Do something
}

你可以使用applicationVariants對象來獲取構建變體的集合。拿到構建變體的引用后,你就可以訪問和更改它的屬性了,比如name,description等。如果你想在Android library上使用相同的邏輯,可以使用libraryVariants對象。

注意我們遍歷構建變體時,使用的是all()而不是each()方法。因為each()的觸發是在evaluation階段,在Android插件創建構建變體之前。all()方法在每次集合加入新元素時就會觸發。

這個鉤子可以用來在APK保存之前修改名稱,或者在文件名中添加版本號。這就使維護APK變得容易,因為你不需要手動編輯文件名。下一節我們會學習如何實現它。

自動重命名APK

一個常見的修改構建構成的應用場景是在APK打包后,重命名以包含版本信息。你可以遍歷應用的構建變體,修改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"))
    }
}

每個構建變體有一個輸出的集合。一個Android應用的輸出是一個APK。每個輸出對象有一個File類型的屬性outputFile。知道了輸出的路徑之后,你就可以修改它。本例中我們在文件名中加入了版本信息。

強大的Android插件鉤子和簡潔的Gradle任務的結合給了我們無限的可能。下一節,我們學習如何為應用的每個構建變體創建一個任務。

動態創建任務

基于Gradle的工作方式和任務的構造方式,在Android構建變體的基礎上,我們可以在配置階段創建新的任務。為了展示這個強大的概念,我們將創建一個任務,用來安裝、運行Android應用的任何一個構建變體。install任務是Android插件定義的,但是如果你是用命令行運行installDebug任務來安裝應用,安裝結束后,你需要手動啟動它。本節我們將創建的任務會解決最后一步的問題。

首先與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任務。這是必需的,因為我們要創建的新的任務需要依賴于它。確定install任務存在后,我們基于變體的名稱創建一個新任務,該任務依賴于variant.install,執行時會觸發install任務。task.create()的閉包中添加了描述字段,在執行gradlew tasks時會顯示出來。

除了添加描述外,我們還需要添加任務執行的動作。本例中,我們想要啟動應用。Android Debug Tool(ADB)提供了啟動真機或者模擬器應用的命令:

$ adb shell am start -n com.package.name/com.package.name.Activity

Gradle有一個exec()的方法,可以執行命令行。為使exec()工作,我們需要加到PATH環境變量中。我們同樣需要把所有的參數傳遞給args屬性:

doFirst {
    exec {
        executable = 'adb'
        args = ['shell', 'am', 'start', '-n',"${variant.applicationId}/.MainActivity"]
    }
}

為了獲取包名,可以使用構建變體的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]
    }
}

首先,我們創建了一個classpath變量,并賦值為applicationId。接下來,查找后綴。在Groovy中,字符串可以使用-做減法。這些更改確保在添加后綴的情況下,應用安裝完成后可以自動運行。

創建插件

如果你有一些想要在多個工程中復用的任務,可以考慮將任務提取到自定義的插件中。這樣你就可以復用構建邏輯,或者將它分享出去。

插件可以由Groovy編寫,也可以基于JVM的其它語言,比如Java、Scala。實際上,Gradle Android插件的大部分是有Java和Groovy混合編寫的。

創建一個簡單的插件

為了提取構建文件中的構建邏輯,你可以在build.gradle文件中創建一個插件。這是最簡單的自定義插件的方式。

為了創建插件,首先需要創建一個實現了Plugin接口的類。我們將使用本章已寫過的動態創建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()方法。構建文件使用插件時,Gradle會調用這個方法。project會作為參數傳遞過來,這樣插件就可以配置項目或者使用項目的方法和屬性。在上一個例子中,我們不能從Android插件直接調用屬性,而是應該首先訪問project對象。需要注意的是,這要求Android插件在我們的插件之前被引入到項目中,否則,project.android會引發異常。

任務的代碼和之前是一樣的,只是exec()需要用project.exec()替換。

將該插件引入到構建文件中:

apply plugin: RunPlugin

分發插件

為了分發插件并分享給其他人,你需要將它移動到一個單獨的模塊(或項目)中。單獨的插件有它自己的構建文件來配置依賴和分發的方法。這個模塊生成一個JAR文件,包含插件類和屬性。你可以使用這個JAR文件將插件應用到模塊或者項目中,也可以分享給他人。

和其他Gradle項目一樣,創建一個build.gradle來配置構建:

apply plugin: 'groovy'

dependencies {
    compile gradleApi()
    compile localGroovy()
}

由于我們是用Groovy寫的插件,所以需要引入Groovy插件。Groovy插件繼承自Java插件,使我們可以構建和打包Groovy類。Groovy同樣支持Java,你可以混合使用。你甚至可以使用Groovy擴展Java類。這使Groovy很容易上手。

gradleApi()依賴用于在我們的插件中訪問Gradle的命名空間。
localGroovy()依賴是Groovy SDK在Gradle中安裝的發行版。
這兩個依賴是Gradle自動添加的。

如果你計劃公開發布你的插件,請確保在構建文件中定義了group和version信息,例如
group = 'com.gradleforandroid'
version = '1.0'

為了使用獨立模塊的代碼,我們還需要確保正確的目錄結構:

plugin
└── src
     └── main
         ├── groovy
         │    └── com
         │        └── package
         │             └── name
         └── resources
              └── META-INF
                  └── gradle-plugins

和其他的Gradle模塊一樣,我們需要提供一個src/main目錄。因為這是一個Groovy工程,所以main的子目錄是groovy而不是java。main的另一個子目錄是resources,用來指定插件的屬性。

我們在包目錄創建了一個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,它的內容是:

implementation-class=com.gradleforandroid.RunPlugin

屬性文件內容僅僅是實現了Plugin接口的類的包名和類名。

準備好接口和屬性文件后,我們可以使用gradlew assemble命令來構建插件,這會在構建輸出目錄創建一個JAR文件。如果你想將插件上傳到Maven倉庫,你首先需要引入Maven插件:

apply plugin: 'maven'

然后,你需要配置uploadArchives任務:

uploadArchives {
    repositories {
        mavenDeployer {
            repository(url: uri('repository_url'))
        }
    }
}

uploadArchives是預定義的任務。為這個任務配置了倉庫之后,你就可以運行它來發布你的插件。本書不會講述如何設置Maven倉庫。如果你想讓你的插件公開可用,可以發布到Gradleware的插件門戶(https://plugins.gradle.org)。插件門戶有一大批Gradle插件(不僅僅是Android的),如果你想擴展Gradle的默認行為,可以去瀏覽一下。你在https://plugins.gradle.org/docs/submit網站可以學習如何發布插件。

為自定義插件寫測試不是本書的內容,如果你想公開發布插件,強烈建議進行測試。你可以在Gradle用戶指南https://gradle.org/docs/current/userguide/custom_plugins.html#N16CE1學習編寫測試

使用自定義插件

要使用插件,需要在buildscript塊將其添加為依賴。首先,我們需要配置一個新倉庫。倉庫的配置依賴于插件發布的途徑。其次,我們需要在dependencies塊配置插件的類路徑。

如果你想引入上例創建的JAR文件,可以定義一個flatDir倉庫:

buildscript {
    repositories {
        flatDir { dirs 'build_libs' }
    }

    dependencies {
        classpath 'com.gradleforandroid:plugin'
    }
}

如果已經將插件上傳到Maven或者Ivy倉庫,配置會有所不同。我們在第3章講了配置管理,所以我們將不再講述不同之處。

配置好之后,我們需要應用插件:

apply plugin: com.gradleforandroid.RunPlugin

在使用apply()方法的時候,Gradle會創建一個插件類的實例,并執行插件自己的apply()方法。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容

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