相信作為一個Android開發(fā)者,對Gradle這個強大的構建工具一定不陌生。Gradle插件是Gradle作為優(yōu)秀的構建工具很大的一個特點。在Android中不同的模塊下看到的apply plugin:xxx表達式就是插件的使用。一直都是使用別人的插件,今天我們就自己動手來實現(xiàn)一下簡單的插件。
為什么要使用Gradle插件?
Gradle插件主要的作用是可以實現(xiàn)代碼的構建代碼的重用。
腳本插件和對象插件
腳本插件就是通過把重用的構建邏輯單獨寫一個gradle文件中實現(xiàn)重用。
對象插件則是通過實現(xiàn)org.gradle.api.Plugin來插件的編寫。嚴格意思上來說,這個才能叫真正的插件,前面方式只能叫做一種邏輯的封裝。
腳本插件
現(xiàn)在執(zhí)行下面命令:
mkdir GradleProject # 創(chuàng)建我們的工作目錄
mkdir ScriptPlugin # 創(chuàng)建腳本插件的項目目錄
touch build.gradle hello.gradle # 創(chuàng)建兩個gradle文件
hello.gradle的內容:
task("hello") {
doLast {
println "Hello, Script Plugin!"
}
}
沒什么說的,就是創(chuàng)建一個簡單的Task。
build.gradle的內容:
apply from: "hello.gradle"
gradle中通過apply函數進行文件引用、插件引用等。這里使用from作為key進行hello.gradle文件的引用。
最后執(zhí)行:
gradle hello
可以看到成功地執(zhí)行了名為hello的Task。
buildSrc目錄
在講對象插件之前,先說一下這個目錄。每個項目中這個目錄中的類會在gradle進行構建的是進行代碼的編譯。使用對象插件一種簡單的方式就是在此目錄下寫插件相關的邏輯。
只不過這樣做有一個很大的缺點就是該項目中進行插件的引用。
對象插件
對象插件可以用任何的JVM語言進行編寫,比如說:Groovy、Java、Kotlin等都行。本篇博客中將使用Groovy進行編寫。
buildSrc中實現(xiàn)
文件結構總覽:

回到GradleProject目錄下,執(zhí)行下面的命令:
mkdir Plugin
cd Plugin
mkdir -p buildSrc/src/main/groovy/com/anriku/plugin/
mkdir -p buildSrc/src/main/resources/META-INF/gradle-plugins/
// 進行文件的創(chuàng)建
touch build.gradle
buildSrc/src/main/groovy/com/anriku/plugin/HelloPlugin.groovy buildSrc/src/main/groovy/com/anriku/plugin/HelloTask.groovy
buildSrc/src/main/groovy/com/anriku/plugin/HelloExtension.groovy
buildSrc/src/main/resources/META-INF/gradle-plugins/plugin.properties
HelloTask.groovy:
package com.anriku.plugin
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction
class HelloTask extends DefaultTask{
// @Input指出Task接收的輸入,當對應輸入為null將會拋異常。解決方法是加上@Optional注解。
@Input String firstName
@Input String lastName
// @TaskAction指明Task的Action
@TaskAction
void greet() {
// 這里只能調用getFirstName()和getLastName()(不能使用firstName、lastName)是因為在后面的插件
// 中使用的是約定映射conventionMapping
println "Hello," + getFirstName() + " " + getLastName()
}
}
這是一個自定義的Task類。接收firstName、lastName進行一個問候語打印。
HelloTaskExtension.groovy:
package com.anriku.plugin
class HelloTaskExtension {
String extFirstName
String extLastName
}
此類為擴展對象,主要作用是為了給插件設置一些在構建腳本中可以設定的屬性
HelloPlugin.groovy:
package com.anriku.plugin
import org.gradle.api.Plugin
import org.gradle.api.Project
class HelloPlugin implements Plugin<Project> {
// 擴展對象在構建腳本中的名字
static final String EXTENTION_NAME = "helloExtension"
@Override
void apply(Project project) {
// 給project創(chuàng)建擴展對象
project.extensions.create(EXTENTION_NAME, HelloTaskExtension)
project.task("hello", type: HelloTask) { task ->
// 讀取擴展對象的中設置的值并賦值給Task對應的約定映射
def extension = project.extensions.findByName(EXTENTION_NAME)
conventionMapping.firstName = { extension.extFirstName }
conventionMapping.lastName = { extension.extLastName }
}
}
}
自定義的Plugin繼承自org.gradle.api.Plugin,覆寫的apply方法用于寫實現(xiàn)邏輯。
這里主要完成了兩個任務:
- 創(chuàng)建一個名為
helloExtension的擴展對象。用于在構建腳本中進行值的設定。 - 創(chuàng)建一個名為
hello的Task。
conventionMapping叫做約定映射,每個繼承于DefalutTask的類都有,它的主要作用就是確保擴展屬性可以在運行時賦值。
PS:約定映射設置的屬性在Task中必須要顯示的使用getter進行獲取??梢曰仡^看下自定義的Task的實現(xiàn)。
plugin.properties
implementation-class=com.anriku.plugin.HelloPlugin
此文件實現(xiàn)很簡單,它作用就是簡化apply plugin的使用。
假設文件名為xxx.properties。
那么可以使用apply plugin: 'xxx' -> apply plugin: 類的全限定名
PS:其中前面的方式是字符串,后面的方式是類名
build.gradle
// 之所以能這么使用就是上面的文件進行了設定。
apply plugin: "plugin"
// 下面是擴展對象完成的功能
helloExtension {
extFirstName = "anriku"
extLastName = "wen"
}
現(xiàn)在在/.../Plugin目錄下執(zhí)行gradle hello,沒有出錯應該是能成功引用插件。
獨立的對象插件
對于上面在buildSrc中實現(xiàn)對象插件的方式有一個問題就是這個對象插件只能夠被當前項目所使用。如果想要給多個項目使用這時候就需要獨立的對象插件。
文件總覽:

現(xiàn)在仍然會到GradleProject目錄下執(zhí)行下面的命令
# 創(chuàng)建項目
mkdir StandalonePlugin
cd StandalonePlugin
# 創(chuàng)建目錄
mkdir Application
mkdir -p Plugin/src/main/groovy/com/anriku/standaloneplugin
mkdir -p Plugin/src/main/resources/META-INF/gradle-plugins
# 創(chuàng)建文件
touch Plugin/build.gradle Application/build.gradle
Plugin/src/main/groovy/com/anriku/standaloneplugin/HelloPlugin.groovy
Plugin/src/main/groovy/com/anriku/standaloneplugin/HelloTask.groovy
Plugin/src/main/groovy/com/anriku/standaloneplugin/HelloTaskExtension.groovy
Plugin/src/main/reources/META-INF/gradle-plugins/standaloneplugin.properties
HelloPlugin.groovy、HelloTask.groovy、HelloTaskExtension.groovy和上面一節(jié)中的基本一樣(只是把包名改一下);standaloneplugin.properties的內容和上面一小節(jié)的類似(不過同樣要把包名改一下)。
Plugin中的build.gradle
plugins {
id 'groovy'
// maven插件用于將上傳插件,這里我們進行的是本地插件的部署
id 'maven'
}
// 插件的classpath將會由下面的參數決定。下面組成的classpath將是com.anriku:standaloneplugin:1.0
group 'com.anriku'
version '1.0'
archivesBaseName = 'standaloneplugin'
dependencies {
// 由于在插件的編寫中使用到了Gradle相關的類。比如說:org.gradle.api.Plugin。因此這里需要添加對
// Gradle API的支持。上面在buildSrc目錄可以直接使用Gradle API。
implementation gradleApi()
}
// maven預定的發(fā)布插件的Task
uploadArchives {
repositories {
mavenDeployer {
repository(url: "file://$projectDir/../repo")
}
}
}
在Plugin目錄下的的build.gradle主要是一些發(fā)布本地插件相關的構建邏輯。
在Plugin目錄下執(zhí)行gradle uploadArchives命令沒有出錯的話,應該會產生一個與Plugin目錄同級的repo目錄。本地插件就以jar包的形式放在此目錄下。
Application中的build.gradle
// 在buildScript中添加對剛才發(fā)布的插件的依賴
buildscript {
repositories {
// 插件的位置
maven { url "file://$projectDir/../repo" }
}
dependencies {
// 插件的classpath,就是剛才對group、version、archivesBaseName的設置。
classpath 'com.anriku:standaloneplugin:1.0'
}
}
// 使用插件。當然也可以使用apply com.anriku.standaloneplugin.HelloPlugin
apply plugin: 'standaloneplugin'
// hello Task的擴展對象
helloExtension {
extFirstName = "anriku"
extLastName = "wen"
}
在Application目錄下執(zhí)行gradle hello。成功出現(xiàn)Hello,anriku wen就說明成功了。
總結
Gradle插件分為兩種:腳本插件和對象插件。
它們的優(yōu)缺點分別是:
腳本插件:
優(yōu)點:
- 編寫簡單。只需要把重用的邏輯單獨放在一個gradle腳本文件中就行。
缺點:
- 添加的task越多,腳本插件越復雜,并且越難以維護
- 不能進行單元測試以及集成測試。
對象插件:
優(yōu)點:
- 通過繼承與特定的類以及實現(xiàn)特定的接口來實現(xiàn)。插件更容易管理和封裝。
-
獨立的對象插件(buildSrc中實現(xiàn)的對象插件只能在當前項目中使用)可以上傳到網上的倉庫中給各個項目以及不同開發(fā)者重用。 - 更容易進行測試。
缺點:
- 編寫較為復雜