文章來源自作者的Android進(jìn)階計(jì)劃(https://github.com/SusionSuc/AdvancedAndroid)
本文不會(huì)太具體講編寫Gradle插件中用到的API,只是大致梳理一下如何編寫一個(gè)Gradle插件辜昵。
這和是官方對(duì)于插件編寫的介紹:https://docs.gradle.org/4.3/userguide/custom_plugins.html 术唬。 本文的內(nèi)容基本是對(duì)官方文檔的翻譯激挪。
編寫自定義插件
在Gradle中台腥,插件是用來模塊化和重用的組件。我們可以在插件中定義一些常用的方法掀序,以及一些自定義Task
八回。在build.gradle
中可以使用apply plugin : 'xxx'
來引入一個(gè)插件岩遗。
我們用的最多的就是 android gradle 插件萄焦。
buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
}
}
apply plugin: 'com.android.application'
編寫一個(gè)簡(jiǎn)單的插件
我們知道在build.gradle
文件中是可以直接寫groovy代碼的。如果一個(gè)插件的功能很簡(jiǎn)單,我們可以直接把這個(gè)插件定義在一個(gè)xx.gradle
文件中:
//GreetingPlugin.gradle
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.task('hello') {
doLast {
println 'Hello from the GreetingPlugin'
}
}
}
}
這個(gè)插件很簡(jiǎn)單拂封,在當(dāng)前工程下創(chuàng)建一個(gè)名為hello
的task茬射。這個(gè)任務(wù)就是打印一個(gè)簡(jiǎn)單的問候。在工程的build.gradle
我們就可以引用這個(gè)插件:
apply from : 'GreetingPlugin.gradle'
運(yùn)行這個(gè)任務(wù):gradle -q hello
冒签。 輸出為:Hello from the GreetingPlugin
即定義一個(gè)自定義插件我們只需要實(shí)現(xiàn)Plugin<Project>
接口在抛。
獲取插件的配置
什么是插件的配置呢?比如我們常使用的 android gradle插件:
android {
compileSdkVersion 27
}
這里compileSdkVersion
就是android gradle為android
這個(gè)域提供的一些可配置的屬性萧恕。那么自定義一個(gè)插件如何可配置呢?
其實(shí)Gradle的Project
關(guān)聯(lián)了一個(gè)ExtensionContainer
,ExtensionContainer
中包含所有的插件的設(shè)置和屬性刚梭,我們可以通過Project
的API來添加一個(gè)extension object
到ExtensionContainer
中。這樣我們就可以在build.gralde
中配置這個(gè)extension object
了票唆。如下:
class GreetingPluginExtension { //一個(gè)簡(jiǎn)單的 java bean
String message = 'Hello from GreetingPlugin'
//..當(dāng)然可以添加更多屬性
}
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
//添加 greeting extension, 在apply插件, 如果ExtensionContainer中就會(huì)有g(shù)reeting這個(gè)extension
def extension = project.extensions.create('greeting', GreetingPluginExtension)
project.task('hello') {
doLast {
println extension.message //可以訪問到在 build.gradle中配
}
}
}
}
apply plugin: GreetingPlugin
//配置 greeting這個(gè)extension
greeting.message = 'Hi from Gradle'
有個(gè)疑問: 我們創(chuàng)建的project.extensions.create('greeting', GreetingPluginExtension)
是什么時(shí)候放入到ExtensionContainer
朴读?
Gradle的整個(gè)構(gòu)建分為3個(gè)階段: 初始化階段、配置階段走趋、執(zhí)行階段衅金。 http://www.reibang.com/p/a45286b08db0
我們自定義的greeting
就是在配置階段放入到ExtensionContainer
中的。
復(fù)雜的自定義配置
當(dāng)我們想將我們的插件共享給其他人或者我們的插件代碼越來越多簿煌,我們希望把代碼放在一個(gè)單獨(dú)的工程時(shí)氮唯。我們可以創(chuàng)建一個(gè)單獨(dú)的工程來管理我們的自定義插件。我們可以編譯出一個(gè)jar
包姨伟,或把這個(gè)jar
包上傳到maven給其他人使用惩琉。
步驟也非常簡(jiǎn)單:
使用gradle構(gòu)建一個(gè)groovy工程,然后依賴gradle api
//build.gradle
apply plugin: 'groovy'
dependencies {
compile gradleApi()
compile localGroovy()
}
包含這兩個(gè)依賴后夺荒,我們就可以用它們提供的API來編寫我們自定義的gradle插件了瞒渠。
聲明插件的實(shí)現(xiàn)類
對(duì)于自定義的插件,Gradle有一個(gè)約定般堆,我們需要在META-INF/gradle-plugins
提供一個(gè)我們插件實(shí)現(xiàn)類的聲明:
比如我們?cè)?code>src/main/resources/META-INF/gradle-plugins/org.samples.greeting.properties下定義了我們自定義插件的實(shí)現(xiàn)類為org.gradle.GreetingPlugin
implementation-class=org.gradle.GreetingPlugin
需要注意: 文件的名字必須是插件的id 在孝,并且官方文檔指明,插件的id應(yīng)該與包名相同淮摔。這樣可以避免沖突私沮。
編寫插件實(shí)現(xiàn)
實(shí)現(xiàn)很簡(jiǎn)單,就是把我們上面編寫的簡(jiǎn)單的插件代碼和橙,以groovy文件的形式放在我們這個(gè)單獨(dú)的工程中就可以了仔燕。
發(fā)布插件到maven倉庫
我們可以使用maven
插件提供的uploadArchives
來把我們的插件上傳到maven
,比如:
apply plugin : 'maven'
uploadArchives {
repositories {
mavenDeployer {
repository(url: 'xxx') {
authentication(userName: 'xx', password: 'xxx')
}
snapshotRepository(url: 'xxx') {
authentication(userName: 'xx', password: 'xxx')
}
pom.project {
artifactId = libArtifactId
version = libVersion
name = libArtifactId
groupId = 'cxxxx'
}
}
}
}
發(fā)布插件到本地
如果我們沒有maven倉庫的話,我們也可以先把插件放到本地魔招,然后依賴本地maven倉庫
//plugin.gradle
//定義上傳的坐標(biāo)
group 'com.susion.plugin'
version '0.0.4'
//artifactId 默認(rèn)為工程名
//把這個(gè)插件上傳的 “l(fā)ocalRepository/libs” 目錄下
uploadArchives {
repositories {
flatDir {
name "localRepository"
dir "localRepository/libs"
}
}
}
//主工程的build.gradle
buildscript {
repositories {
flatDir { // 本地的maven倉庫
name 'localRepository'
dir "library/localRepository/libs"
}
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
classpath 'com.susion.plugin:library:0.0.4' //插件的上傳時(shí)的坐標(biāo)
}
}
這樣我們就可以在其他的工程中 apply 我們的插件了:
apply plugin: 'plugin id'
使用自定義的插件
將插件發(fā)布到maven后晰搀,就可以使用自定的插件了。
buildscript {
...
dependencies {
classpath group: 'org.gradle', name: 'customPlugin', version: '1.0-SNAPSHOT'
}
}
apply plugin: 'org.samples.greeting'
為插件提供一個(gè)可以配置的DSL
內(nèi)嵌一個(gè) java bean
前面我們已經(jīng)知道办斑,我們可以創(chuàng)建一個(gè)包含一些簡(jiǎn)單屬性的extension object
(java bean)到ExtensionContainer
外恕「硕海可是如果我們這個(gè)java bean
中內(nèi)嵌一個(gè)其他的java bean呢? 那么我們還可以這么簡(jiǎn)單的在build.gradle
中簡(jiǎn)單訪問內(nèi)嵌java bean 的屬性呢?
答案是不能的鳞疲,我們需要使用其他一些API來完成這個(gè)事情:ObjectFactory
罪郊。我么還是直接看官方demo的使用吧:
class Person {
String name
}
class GreetingPluginExtension {
String message
final Person greeter
@javax.inject.Inject
GreetingPluginExtension(ObjectFactory objectFactory) {
greeter = objectFactory.newInstance(Person)
}
void greeter(Action<? super Person> action) {
action.execute(greeter)
}
}
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
// Create the extension, passing in an ObjectFactory for it to use
def extension = project.extensions.create('greeting', GreetingPluginExtension, project.objects)
project.task('hello') {
doLast {
println "${extension.message} from ${extension.greeter.name}"
}
}
}
}
apply plugin: GreetingPlugin
greeting {
message = 'Hi'
greeter {
name = 'Gradle'
}
}
內(nèi)嵌一個(gè)java bean集合
Project.container(java.lang.Class)
可以實(shí)例化一個(gè)NamedDomainObjectContainer
。傳入的這個(gè)Class
必須要有一個(gè)name
屬性尚洽,并且必須提供一個(gè)含義name
參數(shù)的構(gòu)造函數(shù)悔橄。
NamedDomainObjectContainer
實(shí)際上實(shí)現(xiàn)了一個(gè)Set
∠俸粒可以理解為一個(gè)set集合癣疟。
class Book {
final String name
File sourceFile
Book(String name) {
this.name = name
}
}
class DocumentationPlugin implements Plugin<Project> {
void apply(Project project) {
def books = project.container(Book) // 傳入book class,返回 NamedDomainObjectContainer, 它是一個(gè)set
books.all {
//遍歷books中的每一個(gè) Book, 并修改 sourceFile
sourceFile = project.file("src/docs/$name")
}
//將容器添加為extension
project.extensions.add('books', books)
//project.extensions.books = books 向 ExtensionContainer 中添加一個(gè)`books`. 它是`NamedDomainObjectContainer<Book>`
}
}
apply plugin: DocumentationPlugin
// Configure the container, books是在插件被apply的時(shí)候添加到 ExtensionContainer中的
books {
quickStart { // quickStart作為 Book 的構(gòu)造函數(shù) name的參數(shù)
sourceFile = file('src/docs/quick-start')
}
userGuide {
}
developerGuide {
}
}
task books {
doLast {
books.each { book ->
println "$book.name -> $book.sourceFile"
}
}
}
上面代碼可能會(huì)對(duì)books.all
這個(gè)方法有疑問: 看task的執(zhí)行結(jié)果是對(duì)每一個(gè) Book都修改了 sourceFile
潮酒。 可是剛創(chuàng)建的NamedDomainObjectContainer
,里面并沒有對(duì)象呀睛挚?
我們可以看一下all
這個(gè)API,其實(shí)它是DomainObjectCollection
的API. NamedDomainObjectContainer
為其子類:
all(Closure action) : Executes the given closure against all objects in this collection, and any objects subsequently added to this collection.
即: 對(duì)此集合中的所有對(duì)象以及隨后添加到此集合的所有對(duì)象執(zhí)行給定的閉包澈灼。