1. 程式人生 > >Gradle for Android-建立task和plugin

Gradle for Android-建立task和plugin

迄今為止,我們已經為gradle build手動編輯了屬性和學習瞭如何執行task。本章節中,我們將會對這些屬性進行深度理解,並開始建立我們自己的task。一旦我們知道如何書寫自己的task,便可更進一步,瞭解如何製作我們自己的可以在幾個專案中重用的plugin。

在瞭解如何建立自定義task前,需要學習一些重要的Groovy概念。因為對Goovy如何工作有個基本的理解使得自定義task和plugin更加容易。瞭解Groovy也幫助理解gradle是如何工作的以及build配置檔案是如何發揮作用。

本章節,我們將瞭解以下主題:

  • 理解Groovy
  • task入門
  • 連線到Android plugin
  • 建立自己的plugin

理解Groovy

大部分的Android開發者也是熟練的Java開發者,瞭解Groovy較之於Java如何工作是非常有趣的。如果你是一位Java開發者的話閱讀Groovy是很容易的,但是寫Groovy程式碼就會是很難的任務瞭如果沒有簡短的介紹的話。

介紹

Groovy基於Java而生並運行於Java虛擬機器上。它的目標就是成為一門更簡單、更直接的既可作為指令碼語言也可作為成熟的程式語言使用的語言。貫穿本節,我們將會比較Groovy和Java使得更易把握Groovy如何工作以及清晰地看到兩種語言間的差異。

在Java中,列印一個字串到螢幕上程式碼如下:

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

在Groovy中,使用如下程式碼可實現相同功能:

println 'Hello, world!'

可以立刻注意到幾個差異之處:

  • 沒有System.out名稱空間
  • 方法引數周圍沒有圓括號
  • 句末沒有分號

這個例子在字串周圍使用了單引號。對於字串既可使用單引號也能使用雙引號,但是它們有不同的用處。雙引號也能包括內插值表示式。插值法是評估包含佔位符的字串,並用它們的值替換這些佔位符。這些佔位符表示式可以是變數或方法。包含方法或多個變數的佔位符表示式需要用大括號包圍並新增字首。包含單個變數的佔位符表示式可以只新增字首$就可以了。這有一些Groovy中內插值字串的例子:

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 character long”。

字串插值法也允許你動態執行程式碼。例子就是列印當前資料的正當的程式碼:

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

在Java中使用看起來非常陌生,但是在動態程式語言中是正常的語法和行為。

類和成員

在Groovy中建立一個類和在Java中很相像。這裡是個簡單的包含一個成員的類:

class MyGroovyClass {
    String greeting
    String getGreeting() {
        return 'Hello!'
    }
}

注意類和成員都沒有明確的訪問修飾符。Groovy中預設的訪問修飾符與Java不同。類自身都是公共的,就像方法一樣,但是類成員都是私有的。

使用MyGroovyClass建立一個新的例項:

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

可以使用關鍵詞def定義新的變數。一旦有了新的類的例項,可以操作它的成員。訪問符被Groovy自動新增。你仍然可以重寫它們,就如我們在MyGroovyClass的定義裡的getGreeting()做的一樣。如無明確說明,仍然可以對類中的每個成員使用getter和setter方法

如果嘗試直接呼叫成員,事實上會呼叫getter方法。這意味著不需要輸出instance.getGreeting
(),可以使用更簡短的instance.greeting代替:

println instance.getGreeting()
println instance.greeting

兩行程式碼列印的是相同的內容。

方法

與變數類似,不需要為方法定義特定的返回值。無論如何你可以自由行事,即使是為了清晰性。另Java和Groovy方法的另一個差異之處在於Groovy中,方法的最後一行預設被返回,即使沒有使用return關鍵詞。

為了演示Java和Groovy的差異,思考一下這個Java返回平方數方法的例子:

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

需要指明方法是公共可訪問的、返回型別以及引數型別。在方法最後,需要返回一個返回型別值。

Groovy中相同的方法定義如下所觀:

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

返回型別和引數型別都沒有明確定義。使用了def關鍵詞代替明確型別,而且方法沒有使用return關鍵詞模糊的返回了一個值。然而,為了清晰性還是建議使用return關鍵詞。當你呼叫方法時,不需要圓括號或分號。

這也是Groovy中另一種甚至更簡短的定義新方法的方式。相同的square方法如是下觀:

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

這不是常規的方法,而是閉包。閉包的概念在Java中不存在,但是在Groovy和Gradle中扮演著重要的角色。

閉包

閉包是可以接受引數和返回一個值的匿名程式碼塊。它們可被分配給變數和作為引數傳給方法。

可以通過在大括號間新增程式碼塊簡單地定義一個閉包,正如之前的例子所看到的。如果想要更明確,可以新增定義型別,如下:

Closure square = {
    it * it
}
square 16

新增Closure型別使得每個使用該程式碼的人都很清晰定義了一個閉包。之前的例子也介紹了模糊無型別引數it的概念。如果沒有明確新增引數到閉包中,Groovy將自動新增一個。該引數總是被稱作it,而且可以在所有閉包中使用它。如果呼叫者沒有指定任何引數,it就是null或empty。這樣可以使得程式碼更簡明,但是隻有在閉包只有一個引數時才有用。

在Gradle環境下,我們一直使用閉包。本書中,目前為止,我們已經把閉包視為塊。這意味著,比如android塊和dependencies塊都是閉包。

集合

在Gradle環境下使用Groovy有兩個重要的集合概念:list和map。

在Groovy中建立一個新的list很容易。不需要特殊的初始化,可以如下簡單地建立一個list:

List list = [1, 2, 3, 4, 5]

迭代list也是非常容易的。可以使用each方法迭代list中的每個元素:

list.each() { element ->
    println element
}

each方法使得你能夠訪問list中的每個元素。可以使用之前提到的it變數使得程式碼更簡短:

list.each() {
    println it
}

Gradle環境下另一個重要的集合概念就是Map。Map在幾個Gradle settings和方法中被使用到。map簡而言之就是包含了一個鍵值對的list。可以如下定義一個map:

Map pizzaPrices = [margherita:10, pepperoni:12]

為了訪問map中指定的項,使用get方法或中括號。

pizzaPrices.get('pepperoni')
pizzaPrices['pepperoni']

Groovy針對這個功能也有捷徑。可以針對map元素使用點記法,使用關鍵詞索引值:

pizzaPrices.get('pepperoni')
pizzaPrices['pepperoni']

Gradle中的Groovy

既然已經瞭解了Groovy基礎,掉回頭學習一個Gradle build檔案並閱讀是個有意思的體驗。注意理解為什麼配置語法看起來如它的樣子變得更加容易了。例如,看看Android plugin被應用到build的這行程式碼:

apply plugin: 'com.android.application'

這個程式碼塊是充滿了Groovy快捷法的。不過不使用任何快捷方式書寫,如下所觀:

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

不使用Groovy快捷方式重寫這行程式碼使得apply()是Project類的一個方法更加清晰,這是每個Gradle build的基礎構建塊。apply()方法帶有一個引數,該引數是帶有key為plugin,值為com.android.application的Map。

另一個例子就是dependecies塊。以前我們定義的dependencies如下:

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

我們現在知道這個塊是個閉包,被傳到一個Project物件的dependencies方法中。這個閉包被傳到一個DependencyHandler中,它包含了add()方法。該方法接收三個引數:一個字串定義配置,一個物件定義依賴符和一個包含了該依賴特定屬性的閉包。全寫出來如下:

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

應該開始對迄今為止我們已經看到的build配置檔案有更多的理解,既然你已經知道了它背後的原理。

task入門

自定義Gradle task可以極大的提升開發者的日常生活。task可以操作已存在的build程序、新增新的build步驟或影響build的輸出。可以實現簡單的task,例如通過勾入Gradle的Android plugin重新命名一個APK。task使得你能夠執行更復雜的程式碼,可以為幾種解析度生成不同的圖片。一旦知道如何建立自己的task,你會發現自己被允許改變build程序的每個方面。這尤其如此當你學習如何掛鉤Anroid plugin時。

定義task

Task屬於Project物件,每個task都實現了Task介面。定義一個新的task的最容易的方式就是用task名稱作為引數執行task方法。

task hello

這定義了task,但執行時不會做任何事。為了建立一個多少有點用的task,需要新增一些action。一個常見的開發者錯誤就是如下建立task:

task hello {
    println 'Hello, world!'
}

當執行這個task,輸出如下:

$ gradlew hello
Hello, world!
:hello

從輸出可能得到這個task的結果,但事實上,“Hello world”甚至可能在task被執行前列印。為了理解發生了什麼,我們需要重返基礎。在第一章,我們討論了Gradle構建的生命週期。在任何Gradle構建中都有三個階段:初始化階段、配置階段和執行階段。當以如上面例子相同的方式新增程式碼到task中,實際上建立了task的配置。即使你要執行一個不同的task,“Hello World”訊息還是會顯示出來。

如果想在執行階段新增action到task中,使用這個符號:

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

唯一的差別在於<<在閉包前面。告訴Gradle這塊程式碼是針對執行階段而非配置階段的。

為了延時差異,請看如下build檔案:

task hello << {
    println 'Execution'
}
hello {
    println 'Configuration'
}

我們定義了task hello,當被執行時會列印到螢幕上。我們也為hello task的配置階段定義了程式碼,會列印Configuration到螢幕上。雖然配置塊在實際的task程式碼定義之後被定義,但仍會率先執行。以上例子輸出如下:

$ gradlew hello
Configuration
:hello
Execution

因為Groovy有許多快捷方式,在Gradle中有好幾種定義task的方式:

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

前兩個塊是使用Groovy實現同一事物的兩種不同方式。可以使用圓括號,但是不需要。也不需要在引數周圍有單引號。這兩個塊中,我們呼叫task()方法,有兩個引數:一個標識task名稱的字串和一個閉包。task()方法是Gradle的Project類的一部分。

最後一個塊沒有使用task()方法。代之,利用了一個名為tasks的物件,它是一個TaskContainer例項,而且在每個Project物件中都有。這個類提供了一個create()方法使用一個Map和一個閉包作為引數並返回一個Task。

寫成簡短形式很方便而且大部分線上的例子和教程都會使用它們。然而,寫成更長的形式在學習的時候非常有用。按照這種方式,Gradle更少變化,而且更容易理解發生了什麼。

task剖析

Task介面是所有task的基礎而且定義了許多屬性和方法。這些都被一個叫做DefaultTask的類實現。這是標準的task型別實現,而且當建立一個新的task時,也是基於DefaultTask的。

從技術上講,DefaultTask並非是真的實現了Task介面中所有方法的類。Gradle有個內部類叫做AbstractTask,包含了Task介面的所有方法的實現。但因為AbStractTask是內部的,我們無法重寫。因此,我們集中於DefaultTask,它是繼承AbstarctTask的,而且我們能夠重寫。

每個Task都包含了許多Action物件。當一個task被執行時,所有的action都會依次被執行。為了新增action到task中,可以使用doFirst()和doLast()方法。這些方法都採用了閉包作為引數,而且將之傳到Action物件中。

你總是需要使用doFirst()或doLast()新增程式碼到task中如果想要該程式碼成為執行階段的一部分。我們之前用來定義task的左移操作符<<是doFirst()方法的快捷方式。

這有一個使用doFirst()和doLast()方法的例子:

task hello {
    println 'Configuration'
    doLast {
        println 'Goodbye'
    }
    doFirst {
        println 'Hello'
    }
}

當執行hello task,輸出如下:

$ gradlew hello
Configuration
:hello
Hello
Goodbye

雖然列印“Goodbye”的這行程式碼先於列印“Hello”的那行程式碼被定義,但是當task被執行時都是以正確的順序結束。你甚至可以多次使用doFirst()和doLast(),如下:

task mindTheOrder {
    doFirst {
        println 'Not really first.'
    }
    doFirst {
        println 'First!'
    }
    doLast {
        println 'Not really last.'
    }
    doLast {
        println 'Last!'
    }
}

執行該task會返回以下輸出:

$ gradlew mindTheOrder
:mindTheOrder
First!
Not really first.
Not really last.
Last!

注意doFirst()總是新增一個action到task的起點,doLast()總是新增一個action到終點。這意味著你當使用這些方法時需要注意,尤其當順序很重要時。

說到有序task,可以使用mustRunAfter方法。該方法允許你去影響gradle如何構造依賴圖。當使用mustRunAfter時,如果有個task被執行,你指定了一個必須先於另外一個被執行。

task task1 << {
    println 'task1'
}
task task2 << {
    println 'task2'
}
task2.mustRunAfter task1

同時執行task1和task2將總是導致task1先於task2執行,而不管你指定的順序:

$ gradlew task2 task1
:task1
task1
:task2
task2

mustRunAfter方法並不在task間新增依賴;仍然有可能執行task2而不執行task1.如果需要一個task依賴另一個,使用dependsOn()方法代替。mustRunAfter和dependsOn()的差異用個例子解釋最好:

task task1 << {
    println 'task1'
}
task task2 << {
    println 'task2'
}
task2.dependsOn task1

當執行task2,而沒執行task1時如下所觀:

$ gradlew task2
:task1
task1
:task2
task2

使用mustRunAfter(),當同時執行二者時,task1總是先於task2執行,但二者可被獨立執行。使用dependsOn(),task2的執行總是觸發task1,雖然沒有明確提及。這是一個重要的差異。

使用task簡化釋出過程

釋出一個Anroid app到Google Play store之前,需要使用證書對之簽名。為了實現簽名,需要建立自己的keystore,它包含了一套私鑰。應用有了自己的keystore和私鑰後,可在gradle中定義簽名配置,如下:

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

這個方法的缺點是你的keystore密碼在倉庫中是以明文形式儲存的。如果你致力於一個開源專案,這是肯定不行的。每個擁有keystore檔案和密碼的人都能使用你的身份釋出app。為了避免如此,可以建立一個task,每次打包釋出安裝包時詢問釋出密碼。雖然有點笨,而且使得你的build伺服器無法自動生成釋出build。一個儲存keystore密碼的好的解決方案就是建立一個不包含在倉庫中的配置檔案。

通過在專案根目錄下建立一個private.properties檔案開始,並新增如下程式碼:

release.password = thepassword

我們假設keystore和key的密碼都是相同的。如果有兩個不同的密碼,很容易新增一個第二屬性。

一旦完成之後,可以定義一個名為getReleasePassword的新的task:

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')
    }
}

這個task將會在專案的根目錄下尋找名為private.properties的檔案。如果檔案存在,task將會從其內容中載入所有屬性。properties.load()方法尋找鍵值對,例如我們在屬性檔案中定義的release.password。

為了確保所有人都能不用私有屬性檔案執行指令碼,或者處理屬性檔案儲存位置,但密碼屬性沒有出現,新增一個回撥。如果密碼仍然是空的,在控制檯詢問密碼:

if (!password?.trim()) {
    password = new String(System.console().readPassword
("\nWhat's the secret password? "))
}

使用Groovy檢測一個字串是否為空是非常簡單的事。password?.trim()中的問號做了空檢查而且將會阻止trim()方法呼叫如果password是空的話。我們不需要明確的檢查null或empty,因為null和empty字串都等於false在if語句環境下。

new String()是必須的因為System.readPassword返回一個位元組陣列,需要被轉換成字串。

一旦有了keystore密碼,可以為release build配置簽名配置:

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

既然已經完成了task,需要確認當執行一個release build時它是被執行了的。為了實現,在buidl.gradle中新增如下:

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

當task被新增到依賴圖時,通過新增一個需要被執行的閉包掛鉤到Gradle和Android plugin。密碼不被要求知道packageRelease task被執行,所以我們確保packageRelease依賴於getReleasePassword task。不能僅使用packageRelease.dependsOn()的原因是Gradle Android plugin基於build變體,動態生成打包task。這意味著這packageRelease task不存在直到Android plugin發現了所有的build變體。發現的過程始於每個單獨構建之前。

新增task並構建掛鉤之後,執行gradlew assembleRelease的結果如下:

這裡寫圖片描述

正如在過程截圖看到的,private.properties檔案不可用,所以task在控制檯中詢問密碼。這種情況下,我們也添加了一條訊息解釋如何建立屬性檔案和新增密碼屬性使得以後的build更加容易。一旦我們的task選擇了keystore密碼,gradle就能夠打包app並完成build。

為了使得task工作,勾入gradle和Android plugin是必不可少的。這是個非常強大的概念,所以我們會詳細探索。

連線到Android Plugin

Android開發時,我們想要改變的大部分task都是與Android plugin相關的。擴充套件task的行為是可能的通過勾入build程序。在之前的例子中,我們已經看到如何在自定義的task上新增依賴和在常規的build程序中新增一個新的task。在本段落,我們將會了解一些Android特定的build 掛鉤的可能性。

勾入到Android plugin的一種方式就是修改build變體。這種方式非常直接;只需要把以下程式碼迭代到app所有的build變體中:

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

為了獲得build變體集合,可以使用applicationVariants物件。一旦引用一個build變體,可以訪問並修改它的屬性,例如名稱等等。如果你想對同一個Android library使用相同的邏輯,使用libraryVariants代替applicationVariants。

注意我們使用all()方法代替我們之前提到each()方法迭代build變體。這是必須的因為each()是在build變體被Android plugin建立之前的評估階段被觸發。all()方法是每當一個新的項被新增到集合中時就會觸發。

掛鉤可用於改變APK的名稱在它被儲存之前,新增版本號到檔名中。這使得不用手動編輯檔名稱,也很容易去維護APK的存檔。在下一段落,我們將會看到如何實現。

自動重新命名APK

操縱build程序的一個常用案例就是重新命名APK名稱包含版本號當它們被打包之後。可以通過迭代app的build變體和改變它的輸出的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"))
    }
}

每個build變體都有一個輸出集合。Android app的輸出就是一個APK。輸出物件都有一個型別屬性檔案叫做outputFile。一旦知道輸出路徑,可以改變它。這個例子中,我們新增變體的版本名稱到檔名稱中。這將定義一個APK被命名成app-debug-1.0.apk代替app-debug.apk.

使用gradle task的簡化版,為Android plugin組合build掛鉤的能力打開了一個充滿可能性的時間。在下一段落中,我們將會看到如何為app的每個build變體建立一個task。

動態建立新task

因為gradle工作和task被構造的方式,我們可以容易地在配置階段基於Android build變體建立自己的task。為了示範這個強大的概念,我們將會建立一個task不僅安裝而且執行app的任意的build變體。install task是Android plugin的一部分,但是如果你從命令列介面中使用installDebug task安裝app,將仍然需要手動啟動它當安裝結束時。我們將在本節建立的task將會將會消除最後一步。

通過勾入我們之前使用過的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 task。它必須要有因為我們將要建立的run task將依賴這個install task,而且基於變體名稱對之命名。我們也會使得新的task依賴variant.install。這將觸發install task在我們的task被執行之前。在tasks.create()閉包內部,通過新增一個描述開始,它將被展示當執行gradlew tasks時。

除了新增描述之外,我們也需要新增task action。在此例中,我們想要匯入app。可以在一個已連線的裝置或模擬器上匯入一個app使用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"]
    }
}

為了得到全包名,使用build變體的application ID,它包括一個字尾,如果被提供的話。但是使用字尾有個問題。雖然添加了字尾,activity的類路徑仍然相同。例如,思考如下配置:

android {
    defaultConfig {
        applicationId 'com.gradleforandroid'
    }
    buildTypes {
        debug {
            applicationIdSuffix '.debug'
        }
    }

包名是com.gradlefroandroid.debug,但activity的路徑仍然是com.gradleforandroid.Activity。為了確保得到activity的正確的類,從application ID中除去後綴:

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]
    }
}

首先,基於application ID,建立一個名為classpath的變數。然後發現被buildType.applicationIdSuffix屬性提供的字尾。在Groovy中,使用減號操作符從一個字串中減去另一個字串是可能的。這些改變確保了安裝之後執行app不會失敗當字尾被使用的時候。

建立自己的plugin

如果有個你想要在好幾個專案中重用的Gradle task集合,把這些task提取到一個自定義的plugin中是有意義的。這使得重用你自己的build邏輯和與他人共享該邏輯都是可能的。

plugin可使用Groovy重寫,也可用其他使用了JVM的語言,例如Java和Scala。事實上,大部分的Gradle Android plugin都是用Java與Groovy混合編寫的。

建立一個簡單的plugin

為了提取已儲存於你的build配置檔案中的build邏輯,可以在build.gradle檔案中建立一個plugin。這是自定義plugin最容易開始的方式。

為了建立一個plugin,建立一個新的實現了Plugin介面的類。我們將使用本章裡之前寫的程式碼,它動態建立一個run task。我們的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()方法。當plugin在build檔案中被使用時,gradle會呼叫這個方法。專案被做傳遞為引數傳遞以便plugin可以配置專案或使用它的方法和屬性。在前面的例子中,我們不能直接從Android plugin中呼叫屬性。代之,我們首先需要訪問專案物件。記住這要求Android plugin要在我們自定義的plugin被應用之前被應用到專案中。否則,project.android將會導致問題。

task的程式碼與之前相同,除了一個方法呼叫外:代之呼叫exec(),我們現在需要呼叫project.exec().

為了確保plugin被應用到我們的build配置中,新增以下程式碼到build.gradle中:

apply plugin: RunPlugin

分發plugin

為了釋出一個plugin並與他人共享,需要把它移到一個獨立模組(或專案)中。一個獨立的plugin有其自己的build檔案去配置依賴和分發方式。這個模組生成一個JAR檔案,包含了plugin類和屬性。你可以使用這個JAR檔案在幾個模組和專案中應用該plugin,並與他人共享。

正如任意Gradle專案一樣,建立一個build.gradle檔案配置build:

apply plugin: 'groovy'
dependencies {
    compile gradleApi()
    compile localGroovy()
}

既然用Groovy編寫了plugin,我們需要應用該Groovy plugin。Groovy plugin繼承了Java plugin,而且使得我們能夠構建和打包Groovy類。Groovy和簡單的Java都是被支援的,所以我們可以混合它們如果你喜歡的話。你甚至可以使用Groovy繼承一個Java類,或其他方式。這使得它很容易入門,雖然你沒有信心使用Groovy對於任何事物。

我們的build配置檔案包含兩個依賴:gradleApi()和localGroovy()。Gradle API被要求從我們自定義的plugin中訪問Gradle名稱空間,而且localGroovy()是伴隨Gradle安裝的Groovy SDK的一個分發。為了便利Gradle預設提供這些依賴。如果gradle沒有提供這些立即可用的依賴,我們要手動下載和引用它們。

如果計劃公開地分發plugin,確認要指定group和version資訊在build配置檔案中,如下:

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,我們用它指定我們的plugin屬性。

我們在包目錄下建立一個叫做RunPlugin.groovy的檔案,在其中我們為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能夠發現plugin,我們需要提供一個屬性檔案。新增屬性檔案到src/main/resources/META-INF/gradle-plugins/目錄下。檔名稱需要匹配我們的plugin的ID。對於RunPlugin,檔案被命名為com.gradleforandroid.run.properties,而且內容如下:

implementation-class=com.gradleforandroid.RunPlugin

屬性檔案唯一包含的就是包和實現了Plugin介面的類的名稱。

當plugin和屬性檔案具備時,可以使用gradlew assemble命令構建plugin。這條命令在build輸出目錄建立了一個JAR檔案。如果想要把plugin推送到Maven倉庫,需要應用應用Maven plugin:

apply plugin: 'maven'

接下來,需要配置uploadArchives task,如下:

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

uploadArchives task是一個預定義的task。一旦在這個task中配置了一個倉庫,可以執行它去釋出你的plugin。本書中我們不會討論如何建立Maven倉庫。

如果你想要使得plugin公開可用,考慮一下把它釋出到Gradleware’s plugin portal(https://plugins.gradle.org/)。這個plugin portal有大量的gradle plugin(不僅僅針對於Android開發)而且也是你要繼承Gradle的預設行為的地方。可以發現如何釋出一個plugin的資訊在文件https://plugins.gradle.org/docs/submit中。

使用自定義的plugin

為了使用一個plugin,需要把它新增到buildscript塊中作為一個依賴。首先,需要配置一個新的倉庫。倉庫的配置依賴於plugin被分發的方式。其次,需要配置plugin的類路徑在dependencies塊中。

如果想要新增我們之前例子中建立的JAR檔案,可以定義一個flatDir倉庫:

buildscript {
    repositories {
        flatDir { dirs 'build_libs' }
    }
    dependencies {
        classpath 'com.gradleforandroid:plugin'
    }
}

如果我們想把plugin上傳到了Maven或Ivy倉庫,這將會有點不同。我們已經在第三章學習過了依賴管理,所以我們不會重複不同的選項。

建立依賴之後,我們需要應用這個plugin:

apply plugin: com.gradleforandroid.RunPlugin

使用apply()方法時,gradle建立一個plugin類的例項,並執行這個plugin自己的apply()方法。

總結

本章節中,我們發現了Groovy是如何不同於Java以及Groovy是如何在Gradle中使用的。我們看來的如何建立自己的task和如何掛鉤到Android plugin中,後者給了我們很大的權利操作build程序或動態新增我們自己的task。

本章最後一部分,我們瞭解了建立plugin和確保我們可以在幾個專案中重用它們通過建立一個獨立的plugin。關於plugin還有更多需要了解,但不幸地是,我們不能在本書中全部瞭解。幸運地是,gradle使用者指南有完整的描述https://gradle.org/docs/current/userguide/custom_plugins.html

下一章,我們將會討論持續整合(Continuous Integration:CI)的重要性。適時有個好的CI系統,我們可以使用一次點選構建、測試和配置app與library。總之持續整合因此是自動化構建的很重要的一部分。

相關推薦

Gradle for Android-建立taskplugin

迄今為止,我們已經為gradle build手動編輯了屬性和學習瞭如何執行task。本章節中,我們將會對這些屬性進行深度理解,並開始建立我們自己的task。一旦我們知道如何書寫自己的task,便可更進一步,瞭解如何製作我們自己的可以在幾個專案中重用的plugin

Gradle for Android——依賴管理

依賴管理是Gradle最閃耀的地方,最好的情景是,僅僅只需新增一行程式碼在你的build檔案,Gradle會自動從遠端倉庫為你下載相關的jar包,並且保證你能正確使用它們。當你在工程裡添加了多個相同的依賴,gradle會為你排除掉相同的jar包。 倉庫 當我

Gradle for Android——構建變體

當你在開發一個app,通常你會有幾個版本。大多數情況是你需要一個開發版本,用來測試app和弄清它的質量,然後還需要一個生產版本。這些版本通常有不同的設定,例如不同的URL地址。更可能的是你可能需要一個免費版和收費版本。基於上述情況,你需要處理不同的版本:開發

android 建立檔案建立資料夾、將assets下檔案複製到指定目錄下

1.獲取APP當前目錄路徑: public String getPath(){ File fileDire = getFilesDir();//獲取../data/應用的包名/fil

Gradle for Android 第七篇( Groovy入門 )

迄今為止,我們已經學些了眾多gradle構建的概念以及如何執行tasks。在這一章,我們將對這些概念有一個更深的理解,然後開始構建我們自己的tasks。一旦我們掌握瞭如何編寫自定義tasks,那麼我們就可以試著編寫自己的外掛,以達到在多個專案中複用的目的。 在我們建立傳

Gradle For Android系列2:自定義Build配置

在上一章節中我們學習了Gradle的用法,以及如何建立Android專案以及如何從Eclipse中將專案轉換到Android Studio中。這一章節將介紹構建檔案配置的更多細節,以及一些有用的構建任務,並深入Gradle的Android外掛。 在本章中,

Gradle for Android(二)全域性設定、自定義BuildConfig、混淆

全域性設定 如果有很多專案,可以設定全域性來統一管理版本號或依賴庫,根目錄下build.gradle下: 1 2 3 4 5 6 ext { compileSdkVersion = 23 buildToolsVersion = "23.0.2"

VLC for Android原始碼下載編譯 (包含其他開源專案,很全面!!!!)

Project Hosting on 點選開啟連結  from:http://dingran.iteye.com/blog/1717711 1.vlc for android  已經發布了,開源的地址是: http://wiki.videolan.or

Gradle for Android 系列:初識 Gradle 檔案

讀完本文你將瞭解到: 我們用 Android Studio 新建立一個專案時,會自動生成 3 個 Gradle 檔案: 接下來介紹這三個檔案的作用。 1. setting.gradle 一個 Gradle 構建通常包括三個階段:初

Gradle for Android:依賴管理

Android專案的依賴管理是通過gradle來配置的,下面的程式碼我們在我們的專案中見的很多,那這些gradle都是什麼意思呢? 1.compile 'com.android.support:appcompat-v7:23.4.0' 2.provided

Gradle for Android系列之三 tasks

  在之前第一篇文章中說過,Gradle最重要的概念是project和tasks,而一個Project也可以說只是包含了多個task的容器,所以在gradle中tasks的重要性不言而喻了。為了更好的介紹Android中常用的tasks,先來介紹task的基本知

Gradle For Android(四)Gradle編譯中神祕的混淆

http://www.jianshu.com/p/ecc820630ec1 http://www.jianshu.com/p/ecc820630ec1 http://www.jianshu.com/p/ecc820630ec1 http://www.jianshu.com/

安卓build variant ----Gradle for Android( 構建變體 )

當你在開發一個app,通常你會有幾個版本。大多數情況是你需要一個開發版本,用來測試app和弄清它的質量,然後還需要一個生產版本。這些版本通常有不同的設定,例如不同的URL地址。更可能的是你可能需要一個免費版和收費版本。基於上述情況,你需要處理不同的版本:開發免費版,開發

Gradle for Android ( 構建變體 )

有時候我們一個app需要有不同的版本,不同的版本又會使用不同的配置,我們可以使用gradle進行管理。 Build types Product flavors Build variants Signing configurations     一、構建版本Bu

自定義 gradle plugin,教你如何 hook 系統 task 位元組碼

一、開源背景 大家在自己寫 library 的時候估計也遇到過這種困惑:一個 library 中的某個類中有些方法或類只想給該 library 中的類使用,並不想暴露出去,但是由於專案的包的層級關係,不得不把方法寫為 public ,導致暴露給了外界!!! 當時這個問題確實困惑了我一段時間,總不

以太坊錢包(BIP44) for Android 錢包的建立錢包的匯入功能 (一)

實現如下2個功能: 1.建立錢包 實現效果如下: 2.匯入錢包 a. 助記詞匯入效果: b.keystore 匯入效果: c.私鑰匯入效果如下 如上親測沒問題,密碼那我設定的是固定值,當真的實現

Android關於Task的一些實踐之SingleTask, SingleInstanceTaskAffinity

conda tag 我們 創建 真理 enter 設置 com lms 上一篇文章粗略地介紹了一下關於Android中Task的基本知識。只是實踐才是檢驗真理的唯一標準,所以。今天就來試驗一下Task中的launchMode是否真的實現了文檔所說的那樣。 首先。定義三個

更新 是 可用的 針對 安卓 軟件開發包工具 Updates are available for android software development packages and tools

安卓 模擬器 軟件 ide software ava -m android 設置 作者:韓夢飛沙 Author:han_meng_fei_sha 郵箱:[email protected]/* */ E-mail: 313134555 @qq.com

VS生成Cordova for Android應用之Gradle

studio tails 文件夾 release 自動化 apach ise phoenix 整理 原文:VS生成Cordova for Android應用之Gradle

Android studio中gradle配置打jar包生成Javadoc文件

Android studio 中生成Javadoc 使用Android studio提供的生成Javadoc的方法(之前沒有想要使用這種方式的原因是因為當時以為使用這種方式的話每次生成都需要選擇想要生成的java檔案, 過於麻煩), 最後發現, 使用這種方式只有配置過一次規則之後, 這個規則