Gradle外掛編寫概述
文章來源自作者的Android進階計劃(ofollow,noindex">https://github.com/SusionSuc/AdvancedAndroid )
本文不會太具體講編寫Gradle外掛中用到的API,只是大致梳理一下如何編寫一個Gradle外掛。
這和是官方對於外掛編寫的介紹:https://docs.gradle.org/4.3/userguide/custom_plugins.html 。 本文的內容基本是對官方文件的翻譯。
編寫自定義外掛
在Gradle中,外掛是用來模組化和重用的元件。我們可以在外掛中定義一些常用的方法,以及一些自定義Task
。在build.gradle
中可以使用apply plugin : 'xxx'
來引入一個外掛。
我們用的最多的就是 android gradle 外掛。
buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.1.3' } } apply plugin: 'com.android.application'
編寫一個簡單的外掛
我們知道在build.gradle
檔案中是可以直接寫groovy程式碼的。如果一個外掛的功能很簡單,我們可以直接把這個外掛定義在一個xx.gradle
檔案中:
//GreetingPlugin.gradle class GreetingPlugin implements Plugin<Project> { void apply(Project project) { project.task('hello') { doLast { println 'Hello from the GreetingPlugin' } } } }
這個外掛很簡單,在當前工程下建立一個名為hello
的task。這個任務就是列印一個簡單的問候。在工程的build.gradle
我們就可以引用這個外掛:
apply from : 'GreetingPlugin.gradle'
執行這個任務:gradle -q hello
。 輸出為:Hello from the GreetingPlugin
即定義一個自定義外掛我們只需要實現Plugin<Project>
介面。
獲取外掛的配置
什麼是外掛的配置呢?比如我們常使用的 android gradle外掛:
android { compileSdkVersion 27 }
這裡compileSdkVersion
就是android gradle為android
這個域提供的一些可配置的屬性。那麼自定義一個外掛如何可配置呢?
其實Gradle的Project
關聯了一個ExtensionContainer
,ExtensionContainer
中包含所有的外掛的設定和屬性,我們可以通過Project
的API來新增一個extension object
到ExtensionContainer
中。這樣我們就可以在build.gralde
中配置這個extension object
了。如下:
class GreetingPluginExtension {//一個簡單的 java bean String message = 'Hello from GreetingPlugin' //..當然可以新增更多屬性 } class GreetingPlugin implements Plugin<Project> { void apply(Project project) { //新增 greeting extension, 在apply外掛, ExtensionContainer中就會有greeting這個extension def extension = project.extensions.create('greeting', GreetingPluginExtension) project.task('hello') { doLast { println extension.message//可以訪問到在 build.gradle中配置的值 } } } } apply plugin: GreetingPlugin //配置 greeting這個extension greeting.message = 'Hi from Gradle'
有個疑問: 我們建立的project.extensions.create('greeting', GreetingPluginExtension)
是什麼時候放入到ExtensionContainer
?
Gradle的整個構建分為3個階段:初始化階段、配置階段、執行階段。https://www.jianshu.com/p/a45286b08db0
我們自定義的greeting
就是在配置階段放入到ExtensionContainer
中的。
複雜的自定義配置
當我們想將我們的外掛共享給其他人或者我們的外掛程式碼越來越多,我們希望把程式碼放在一個單獨的工程時。我們可以建立一個單獨的工程來管理我們的自定義外掛。我們可以編譯出一個jar
包,或把這個jar
包上傳到maven給其他人使用。
步驟也非常簡單:
使用gradle構建一個groovy工程,然後依賴gradle api
//build.gradle apply plugin: 'groovy' dependencies { compile gradleApi() compile localGroovy() }
包含這兩個依賴後,我們就可以用它們提供的API來編寫我們自定義的gradle外掛了。
宣告外掛的實現類
對於自定義的外掛,Gradle有一個約定,我們需要在META-INF/gradle-plugins
提供一個我們外掛實現類的宣告:
比如我們在src/main/resources/META-INF/gradle-plugins/org.samples.greeting.properties
下定義了我們自定義外掛的實現類為org.gradle.GreetingPlugin
implementation-class=org.gradle.GreetingPlugin
需要注意:檔案的名字必須是外掛的id ,並且官方文件指明,外掛的id應該與包名相同。這樣可以避免衝突。
編寫外掛實現
實現很簡單,就是把我們上面編寫的簡單的外掛程式碼,以groovy檔案的形式放在我們這個單獨的工程中就可以了。
釋出外掛到maven倉庫
我們可以使用maven
外掛提供的uploadArchives
任務來把我們的外掛上傳到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' } } } }
使用自定義的外掛
將外掛釋出到maven後,就可以使用自定的外掛了。
buildscript { ... dependencies { classpath group: 'org.gradle', name: 'customPlugin', version: '1.0-SNAPSHOT' } } apply plugin: 'org.samples.greeting'
為外掛提供一個可以配置的DSL
內嵌一個 java bean
前面我們已經知道,我們可以建立一個包含一些簡單屬性的extension object
(java bean)到ExtensionContainer
。可是如果我們這個java bean
中內嵌一個其他的java bean呢? 那麼我們還可以這麼簡單的在build.gradle
中簡單訪問內嵌java bean 的屬性呢?
答案是不能的,我們需要使用其他一些API來完成這個事情: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' } }
內嵌一個java bean集合
Project.container(java.lang.Class)
可以例項化一個NamedDomainObjectContainer
。傳入的這個Class
必須要有一個name
屬性,並且必須提供一個含義name
引數的建構函式。
NamedDomainObjectContainer
實際上實現了一個Set
。可以理解為一個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, 它是一個set books.all { //遍歷books中的每一個 Book, 並修改 sourceFile sourceFile = project.file("src/docs/$name") } //將容器新增為extension project.extensions.add('books', books) //project.extensions.books = books向ExtensionContainer 中新增一個`books`. 它是`NamedDomainObjectContainer<Book>` } } apply plugin: DocumentationPlugin // Configure the container,books是在外掛被apply的時候新增到 ExtensionContainer中的 books { quickStart {// quickStart作為 Book 的建構函式 name的引數 sourceFile = file('src/docs/quick-start') } userGuide { } developerGuide { } } task books { doLast { books.each { book -> println "$book.name -> $book.sourceFile" } } }
上面程式碼可能會對books.all
這個方法有疑問: 看task的執行結果是對每一個 Book都修改了sourceFile
。 可是剛建立的NamedDomainObjectContainer
,裡面並沒有物件呀?
我們可以看一下all
這個API,其實它是DomainObjectCollection
的API.NamedDomainObjectContainer
為其子類:
all(Closure action) : Executes the given closure against all objects in this collection, and any objects subsequently added to this collection.
即: 對此集合中的所有物件以及隨後新增到此集合的所有物件執行給定的閉包。
對於Gradle外掛的編寫就簡單介紹到這裡。