前言
學(xué)習(xí)Gradle也有一段時(shí)間了捆姜,感覺(jué)知道了很多,但是還是有些朦朦朧朧,這時(shí)候就該寫(xiě)點(diǎn)代碼來(lái)融會(huì)貫通一下, 于是便決定做一個(gè)簡(jiǎn)單的插件來(lái)真正理解一下Gradle
插件開(kāi)發(fā)
在開(kāi)始之前羹奉,我們要知道,插件是做什么的约计,Gradle的插件類(lèi)似java中的jar包尘奏,主要用于代碼復(fù)用和邏輯封裝,復(fù)雜的如同java插件病蛉,提供了一整套的java編譯系統(tǒng),簡(jiǎn)單的也可以就僅僅只是封裝一些共有的方法瑰煎、task之類(lèi)铺然,這里我們就由淺入深,從最簡(jiǎn)單的插件說(shuō)起酒甸。
語(yǔ)言選擇
Gradle User Guide里面的原話(huà)是“You can implement a custom plugin in any language you like”魄健,雖然是這么說(shuō),但是我也見(jiàn)過(guò)用groovy插勤,js沽瘦,java這些語(yǔ)言實(shí)現(xiàn)的插件革骨,并沒(méi)有見(jiàn)過(guò)c++實(shí)現(xiàn)的插件,本文選擇使用的是java析恋,畢竟groovy不熟
插件形式
在Gradle中良哲,一個(gè)插件并不是什么很神奇的東西,它其實(shí)就是Class助隧,只是實(shí)現(xiàn)的一個(gè)plugin的接口筑凫,有了一個(gè)apply的方法,能夠被apply到構(gòu)建腳本中并村,它可以直接寫(xiě)在build.gradle的腳本中(當(dāng)然這樣就只能使用java或是groovy的語(yǔ)法)巍实,可以寫(xiě)在/src/main這樣的資源目錄下,當(dāng)然也可以作為一個(gè)jar包導(dǎo)入 哩牍,考慮到插件的用途棚潦,把插件打包成一個(gè)jar也算是必需的
簡(jiǎn)單插件
首先是開(kāi)發(fā)工具的選擇,這里我使用的是AndroidStudio(據(jù)說(shuō)更好的是使用IntelliJ Idea膝昆,但是我在嘗試了幾個(gè)小時(shí)之后放棄了丸边,還是Studio好),首先創(chuàng)建一個(gè)工程外潜,然后新建個(gè)Module(類(lèi)型無(wú)所謂原环,反正我們只用用這個(gè)目錄而已),然后開(kāi)始目錄改造处窥,該刪的刪嘱吗,該建的建,最后的目錄結(jié)構(gòu)如下:
res和androidTest這兩個(gè)目錄直接刪掉滔驾,然后新建
src/main/resources/META-INF/gradle-plugins
目錄谒麦,這個(gè)目錄算是插件id的索引,只有通過(guò)這個(gè)目錄定義了插件的id和與之關(guān)聯(lián)的插件類(lèi)哆致,比如上面绕德,我們定義了一個(gè)com.mime.houyi.helloworld.preperties
的文件,那我們的插件id就是com.mime.houyi.helloworld
摊阀,至于關(guān)聯(lián)的類(lèi)后面再細(xì)說(shuō)耻蛇。做完上面這里,下面就直接上代碼了胞此,首先是build.gradle這里我原本建的是一個(gè)Android Library的Module臣咖,所以原本的內(nèi)容全部刪掉,新建內(nèi)容如下
apply plugin: 'java'//導(dǎo)入java插件用于漱牵,編譯打包我們的插件
apply plugin: 'maven'//maven插件夺蛇,用于上傳插件到倉(cāng)庫(kù)
//uploadArchives 類(lèi)型是upload,這個(gè)task不是'maven'創(chuàng)建的
//而是'maven'定義了一個(gè)rule,而后由我們自己創(chuàng)建的酣胀,關(guān)于rule刁赦,請(qǐng)看后面內(nèi)容
uploadArchives{
//本地倉(cāng)庫(kù)的一種
repositories{
flatDir{
name "localRepository"
dir "localRepository/libs"
}
}
}
group = "com.mime.houyi"http://project屬性
version = "1.0"http://project屬性
dependencies {
//導(dǎo)入Gradle的api娶聘,要寫(xiě)插件,肯定要使用Gradle的api
compile gradleApi()
}
我們先分析一下上面代碼甚脉,很多東西在注釋中已經(jīng)提到丸升,我就不一一贅述了,uploadArchives
這個(gè)Task雖然不是maven插件創(chuàng)建的,但是這里可以不做太多關(guān)注宦焦,后面對(duì)于這種會(huì)更詳細(xì)講解发钝,這里姑且認(rèn)為它就是maven插件創(chuàng)建的,用于上傳整個(gè)工程的jar包波闹。這里我們主要說(shuō)明一下repositories的幾種倉(cāng)庫(kù)定義(這個(gè)屬于題外話(huà)酝豪,既然用到了就解釋一下,方便更好的理解)
幾種倉(cāng)庫(kù)說(shuō)明
首先是上面的使用flatDir方法創(chuàng)建一個(gè)倉(cāng)庫(kù)精堕,定義比較簡(jiǎn)單孵淘,屬性也就上面兩個(gè),name和dir歹篓,使用類(lèi)似如下
repositories {
flatDir name: 'libs', dirs: "$projectDir/libs"
flatDir dirs: ["$projectDir/libs1", "$projectDir/libs2"]
}
第二種是ivy倉(cāng)庫(kù)瘫证,ivy是Apache Ant的子項(xiàng)目,一種類(lèi)似maven的倉(cāng)庫(kù)庄撮,搭建和使用請(qǐng)自行Google背捌,這里配置方法也很簡(jiǎn)單
repositories {
ivy {
//這里url可以是遠(yuǎn)程地址,也可以是本地地址
url "http://repo.mycompany.com/repo"
}
}
第三種是maven倉(cāng)庫(kù)洞斯,使用和ivy倉(cāng)庫(kù)一樣
repositories {
maven {
url "http://repo.mycompany.com/maven2"
}
}
第四種是jcenter毡庆,Bintray的JCenter倉(cāng)庫(kù),這個(gè)用的多烙如,不多說(shuō)
第五種是mavenCentral么抗,這個(gè)類(lèi)似jcenter,也不多說(shuō)
第六種是mavenLocal亚铁,這個(gè)是一個(gè)默認(rèn)的本地倉(cāng)庫(kù)蝇刀,具體位置可以進(jìn)行配置,如果沒(méi)有配置默認(rèn)是在(user)/.m2/repository
插件實(shí)現(xiàn)
如果僅僅只是實(shí)現(xiàn)一個(gè)插件,那是非常的簡(jiǎn)單徘溢,在java目錄下創(chuàng)建一個(gè)class吞琐,實(shí)現(xiàn)Plugin接口
public class HelloWorldPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getTasks().create("hello", DefaultTask.class);
}
}
可以看到我們上面的代碼幾乎沒(méi)有做什么,僅僅只是在project中創(chuàng)建了一個(gè)名為hello的task然爆,TaskType是DefaultTask顽分,不過(guò)也可以看出來(lái),我們?nèi)pply一個(gè)插件施蜜,事實(shí)上是把我們編譯腳本的project對(duì)象作為一個(gè)參數(shù)傳給了插件
做完上面這一步,事實(shí)上Gradle并不能找到我們的插件雌隅,這時(shí)候就需要META-INF/gradle-plugins
這個(gè)目錄下的properties文件了翻默,我們已經(jīng)建好了缸沃,然后
implementation-class=com.mime.houyi.HelloWorldPlugin
我們的插件就已經(jīng)完成了,雖然這個(gè)插件沒(méi)有什么功能修械,接下來(lái)趾牧,我們使用運(yùn)行uploadArchives這個(gè)task把我們的插件打包成jar,上傳到我們先前的那個(gè)flatDir創(chuàng)建的倉(cāng)庫(kù)肯污。接下來(lái)可以實(shí)測(cè)一下我們的插件是否有問(wèn)題
首先在整個(gè)工程的build.gradle下添加
buildscript {
repositories {
jcenter()
flatDir name:'localRepository',dir:"helloworld/localRepository/libs'
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.mime.houyi:helloworld:1.0'//命名是我們的groupId:moduleName:version
}
}
然后在app的build.gradle下添加
apply plugin: 'com.mime.houyi.helloworld'
可以看到翘单,在Gradle的task視窗里面:app/other下多了一個(gè)hello的task,到此最最簡(jiǎn)單的插件就完成的蹦渣。Then Next
自定義Task
在開(kāi)始之前我們?cè)俅螌?duì)Task的做一個(gè)介紹哄芜,Gradle中所有的Task都是繼承自DefaultTask,我們可以把它理解成Task中的Object類(lèi)柬唯,所有的Task都是繼承自DefaultTask(雖然DefaultTask也是實(shí)現(xiàn)了一個(gè)Task接口认臊,但是我們不會(huì)去直接實(shí)現(xiàn)Task接口),Task有什么锄奢,是做什么的失晴,這里我們用Gradle文檔里面的一張圖來(lái)做一個(gè)解釋
在正常情況下,一個(gè)task都是有一些輸入拘央,處理然后產(chǎn)出一些輸出涂屁,我們之前也在build.gradle中寫(xiě)過(guò)簡(jiǎn)單的task,使用doLast灰伟,但是這種類(lèi)型的task沒(méi)有輸入輸出的概念拆又,真正要實(shí)現(xiàn)一個(gè)類(lèi)似于java中的jar這種的插件,我們就得去寫(xiě)一個(gè)自定義的Task
基本概念
在開(kāi)始之前袱箱,有幾個(gè)基本概念需要認(rèn)識(shí)一下
- 輸出:Task的目標(biāo)遏乔,我們的task都是為了,達(dá)到一個(gè)目的发笔,這就是輸出盟萨,比如jar的輸出是jar文件,copy的輸出是目標(biāo)目錄
- 輸入:只有會(huì)影響一個(gè)或多個(gè)輸出結(jié)果的才算是輸入
- 屬性:會(huì)影響task的執(zhí)行過(guò)程了讨,但是不會(huì)影響結(jié)果
- action:這個(gè)官方文檔沒(méi)有提到捻激,我自己加的,具體的處理過(guò)程
為什么Gradle中的Task會(huì)有這幾種的類(lèi)型區(qū)別前计,主要是在Gradle中支持Incremental Build
什么胞谭?你不知道這是什么東西,打開(kāi)AndroidStudio的Gradle Console你就會(huì)看到一堆的UP-TO-DATE男杈,這就是Incremental Build丈屹,為了加快構(gòu)建的速度,Gradle每次執(zhí)行一個(gè)task之前就會(huì)檢查task的輸入和輸出,如果和上次的相比都沒(méi)沒(méi)有變化旺垒,那么Gradle就會(huì)認(rèn)為這個(gè)task是up to date的彩库,從而跳過(guò)這個(gè)task,以此來(lái)加快構(gòu)建的速度先蒋。這也就是在Gradle Console中出現(xiàn)的UP-TO-DATE骇钦。
關(guān)于輸入輸出,Gradle支持三種類(lèi)型的輸入輸出
- 簡(jiǎn)單類(lèi)型
String竞漾,int幾種簡(jiǎn)單類(lèi)型必須是可以的眯搭,但這里只是的任何實(shí)現(xiàn)了Serializable的類(lèi) - 文件類(lèi)型
包括java中的File和Files以及Gradle中的FileCollection類(lèi)型 - Nested類(lèi)型
自定義的類(lèi)型,不屬于上面兩種业岁,但是類(lèi)型里的屬性都屬于上面兩種
具體實(shí)現(xiàn)
首先是我們Task類(lèi)
public class WriteHelloManTask extends DefaultTask {
private HelloManData helloMan;
private File targetDirectory;
private String fileName;
@Nested
public HelloManData getHelloMan(){
return helloMan;
}
@OutputFile
public File getTargetFile(){
return new File(targetDirectory, fileName);
}
@Input
public String getFileName(){
return fileName;
}
@InputDirectory
public File getTargetDirectory() {
return targetDirectory;
}
@TaskAction
public void writeObject(){
File targetFile = new File(targetDirectory, fileName);
try {
FileOutputStream fos = new FileOutputStream(targetFile);
byte[] bytes = helloMan.toString().getBytes();
fos.write(bytes);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void setHelloMan(HelloManData helloMan) {
this.helloMan = helloMan;
}
public void setTargetDirectory(File targetDirectory) {
this.targetDirectory = targetDirectory;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
}
HelloManData 類(lèi)
public class HelloManData {
private String name;
private int age;
public HelloManData(String name, int age){
this.name = name;
this.age = age;
}
@Input
public String getName() {
return name;
}
@Input
public int getAge() {
return age;
}
@Override
public String toString() {
return "Hello "+age+" years old "+name+" !";
}
}
上面的代碼鳞仙,一眼就能看到諸如Input
、Output
叨襟、TaskAction
之類(lèi)的注解繁扎,這些注解就是用來(lái)標(biāo)記輸入,輸出以及action的糊闽,因?yàn)镚radle的Incremental Build機(jī)制梳玫,我們必須把輸入都標(biāo)記上才能正確的運(yùn)行(如果沒(méi)有標(biāo)記,可能會(huì)出現(xiàn)輸入改變了右犹,但是Task卻跳過(guò)了)提澎,但是同時(shí)Gradle也不建議把不會(huì)影響輸出的屬性標(biāo)記為輸入,這樣會(huì)在整體上降低Gradle的構(gòu)建速度念链,下面我就一張列表來(lái)看下盼忌,我們可能會(huì)使用到的注解
注解名稱(chēng) | 文件類(lèi)型 | 詳情 |
---|---|---|
@Input | serializable的類(lèi)型 | 也就是前面所說(shuō)的簡(jiǎn)單類(lèi)型 |
@InputFile | File* | 單個(gè)的文件類(lèi)型的輸入(非文件夾) |
@InputDirectory | File* | 單個(gè)的文件類(lèi)型的輸入(非文件) |
@InputFiles | Iterable<File>* | 可Iterable的文件或者文件夾的集合類(lèi)型的輸入 |
@Nested | 自定義類(lèi) | 沒(méi)有實(shí)現(xiàn)serializable,但是至少有一個(gè)屬性使用表里的注解掂墓,包括@Nested注解 |
@OutputFile | File* | 單個(gè)的文件類(lèi)型的輸出(非文件夾) |
@OutputDirectory | File* | 單個(gè)的文件類(lèi)型的輸入(非文件) |
@OutputFiles | Map<String, File>** 或者Iterable<File>* | 可Iterable的文件的集合類(lèi)型的輸出(非文件夾) |
@OutputDirectories | Map<String, File>** 或者Iterable<File>* | 可Iterable的文件夾的集合類(lèi)型的輸出(非文件) |
@TaskAction | - | 用于標(biāo)注Task真正執(zhí)行的action |
從表中我們可以看出谦纱,Gradle對(duì)于Task的輸出偏向于文件方面,同時(shí)Task的定義也是大致如此君编,接下來(lái)我們就可以在我們的插件中使用這個(gè)Task
public class HelloWorldPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
WriteHelloManTask task = project.getTasks().create("writeHello", WriteHelloManTask.class);
task.setFileName("HelloWorld.txt");
task.setHelloMan(new HelloManData("Jim",19));
task.setTargetDirectory(new File("D:/workspace"));
task.setGroup("hello");
}
}
修改一下version跨嘉,重復(fù)一次之前的uploadArchives操作,我們可以在:app的Task下看到一個(gè)新的類(lèi)別hello(我不會(huì)告訴你吃嘿,我僅僅只是因?yàn)楹谜异裟耍驗(yàn)閛ther下的task實(shí)在是太多了),運(yùn)行writeHello的task兑燥,就可以看到我們的相應(yīng)目錄下多了一個(gè)HelloWorld.txt的文件亮瓷,然后再次運(yùn)行,我們就可以看到
:app:writeHello UP-TO-DATE
Task直接跳過(guò)了降瞳,因?yàn)镚radle看來(lái)輸入輸出和上一次沒(méi)有變化嘱支。到此為止,我們一個(gè)相對(duì)簡(jiǎn)單插件就能夠完成了,但是看起來(lái)和我們平時(shí)使用的插件還是有一定的差距除师,所以讓我們繼續(xù)下一節(jié)
關(guān)于DSL
前面我們已經(jīng)提到過(guò)DSL(領(lǐng)域?qū)S谜Z(yǔ)言/domain specific language)赢织,雖然還沒(méi)有學(xué)過(guò)實(shí)現(xiàn)DSL,但我們已經(jīng)使用了很多了馍盟,是否需要定義DSL,是根據(jù)需求來(lái)的茧吊,很多插件并不需要定義DSL贞岭,需要定義DSL的大多都是需要和使用者交互,使用DSL搓侄,使用者不用關(guān)心我們的實(shí)現(xiàn)方式瞄桨,只需要通過(guò)DSL就可以完成他們需要的配置。
幾個(gè)重要的點(diǎn)(或者說(shuō)是類(lèi)讶踪,概念)
- Extension
通過(guò)Extension芯侥,我們可以向目標(biāo)對(duì)象添加DSL擴(kuò)展,這一過(guò)程通過(guò)project中的ExtensionContainer來(lái)實(shí)現(xiàn)乳讥,我們可以通過(guò)ExtensionContainer的create來(lái)創(chuàng)建新的DSL域柱查,并與一個(gè)對(duì)應(yīng)的委托類(lèi)關(guān)聯(lián)起來(lái)(即新建一個(gè)DSL域,并委托給一個(gè)具體類(lèi)) - Convention
與Extension類(lèi)似云石,但是又有所不同唉工,通過(guò)Convention的getPlugin方法,我們會(huì)把一個(gè)類(lèi)融合到Convention所在的域汹忠,而不是新建一個(gè)域(具體區(qū)別可以參考java插件和android插件) - NamedDomainObjectContainer
命名對(duì)象容器淋硝,可以用于在buildscript中創(chuàng)建對(duì)象,創(chuàng)建的對(duì)象必須要有name屬性作為容器內(nèi)元素的標(biāo)識(shí) - Instantiator
Instantiator 用于實(shí)例化對(duì)象, 使用Instantiator而不直接使用new宽菜,是因?yàn)槭褂肐nstantiator實(shí)例化對(duì)象時(shí)谣膳,會(huì)添加DSL特性 - ext 是project的一個(gè)屬性,維持一個(gè)命名空間铅乡,用于為project添加鍵值對(duì)屬性继谚,其實(shí)與DSL無(wú)關(guān),只是形式和類(lèi)型上和extension類(lèi)似
新建DSL
簡(jiǎn)單插件的創(chuàng)建過(guò)程隆判,就和上面一樣創(chuàng)建一個(gè)名為createdsl的model犬庇,我就不一一贅述,創(chuàng)建DSL我們需要使用前面提到的Extension侨嘀,在代碼中我們可以使用project.getExtension()
獲取Extension(ExtensionContainer的實(shí)現(xiàn)類(lèi))臭挽。通過(guò)ExtensionContainer的create
方法創(chuàng)建一個(gè)新的DSL
MyExtension mMyExtension = project.getExtensions().create("myExtension", MyExtension.class);
MyExtension
對(duì)象(一個(gè)簡(jiǎn)單的實(shí)體類(lèi))
public class MyExtension {
private String mExtensionName;
private InnerExtension mInnerExtension;
public MyExtension() {
}
public String getExtensionName() {
return mExtensionName;
}
public void setExtensionName(String extensionName) {
this.mExtensionName = extensionName;
}
public InnerExtension getInnerExtension() {
return mInnerExtension;
}
public void setInnerExtension(InnerExtension innerExtension) {
mInnerExtension = innerExtension;
}
}
這樣我們就成功創(chuàng)建了一個(gè)DSL,在apply過(guò)我們的plugin之后咬腕,我們就可以在buildscript中定義這個(gè)DSL了欢峰,我們可以直接配置簡(jiǎn)單屬性如String、Boolean之類(lèi),但是直接定義對(duì)象類(lèi)就會(huì)出錯(cuò)
myExtension {
extensionName "ddd" //這行代碼沒(méi)有 問(wèn)題,可以正常通過(guò)
innerExtension{ //這個(gè)會(huì)報(bào)錯(cuò)
}
}
關(guān)于上面
extensionName
這個(gè)屬性名纽帖,大家可能會(huì)有所疑惑宠漩,因?yàn)槲覀兩厦娑x的成員名是mExtensionName
,這里這個(gè)屬性名其實(shí)是根據(jù)set方法生成的懊直,這個(gè)屬性賦值扒吁,實(shí)質(zhì)上也是調(diào)用的set方法。
對(duì)于普通對(duì)象型的extension屬性室囊,我們?nèi)绻胍赽uildscript中直接定義它的話(huà)雕崩,就需要使用上面說(shuō)過(guò)的Instantiator。
這里希望大家有一個(gè)概念融撞,我們寫(xiě)在buildscript中的myExtension并不直接就是我們代碼中的mMyExtension對(duì)象盼铁, 這中間存在對(duì)應(yīng)的關(guān)系,對(duì)于MyExtension我們通過(guò)extension.create
來(lái)實(shí)現(xiàn)這一對(duì)應(yīng)(其實(shí)在create方法中也是通過(guò)Instantiator來(lái)實(shí)現(xiàn)的)尝偎,但是對(duì)于MyExtension中的一個(gè)普通成員對(duì)象innerExtension饶火,我們需要使用Instantiator實(shí)現(xiàn)這一關(guān)系。
修改我們的代碼,主要是兩個(gè)地方致扯,一個(gè)是apply方法
Instantiator instantiator = ((DefaultGradle) project.getGradle()).getServices().get(
Instantiator.class);
MyExtension mMyExtension = project.getExtensions().create("myExtension", MyExtension.class,
new Object[]{instantiator});
//create方法的第三個(gè)參數(shù)是MyExtension的構(gòu)造參數(shù)
另一個(gè)是MyExtension構(gòu)造方法
public MyExtension(Instantiator instantiator) {
mInnerExtension = instantiator.newInstance(InnerExtension.class);
}
但是這樣還不夠肤寝,我們?cè)谏厦嬲f(shuō)到,gradle是根據(jù)set方法來(lái)在buildscript中定義屬性的急前,我們上面在buildscript中傳遞給set方法的是scriptblock塊醒陆,所以我們的set方法要接受一個(gè)scriptblock塊。裆针。刨摩。
不過(guò)還好,gradle中定義了一個(gè)Action的interface世吨,就是用于處理這種場(chǎng)景澡刹,讓我們修改InnerExtension的set方法
public void innerExtension(Action<InnerExtension> action) {
//這里方法名寫(xiě)成setInnerExtension,gradle也是可以會(huì)調(diào)用的,
//但是為了防止意外耘婚,還是寫(xiě)出我們想要的名字
action.execute(mInnerExtension);
}
修改后的set方法接受一個(gè)action類(lèi)型的參數(shù)罢浇,perfect!
除了上面所說(shuō)的簡(jiǎn)單類(lèi)型和對(duì)象類(lèi)型沐祷,大家可能還見(jiàn)過(guò)另一種嚷闭,比如android中的buildTypes、productFlavors,這種類(lèi)型可以用于在buildscript中創(chuàng)建新的指定類(lèi)型的對(duì)象赖临,也就是上面提到的NamedDomainObjectContainer,但是,How to use?這種時(shí)候就需要一份[官方文檔](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:container(java.lang.Class, org.gradle.api.NamedDomainObjectFactory))了,我們看到project中有這樣一個(gè)方法:
NamedDomainObjectContainer<T> container(Class<T> type, NamedDomainObjectFactory<T> factory)
//創(chuàng)建一個(gè)容器來(lái)管理指定對(duì)象T的命名對(duì)象胞锰,參數(shù)factory是用于創(chuàng)建指定對(duì)象的,
//所有需要被創(chuàng)建的對(duì)象必須擁有切暴露一個(gè)"name"的屬性,這個(gè)屬性在對(duì)像的生命周期內(nèi)必須是不變的
感覺(jué)是可以開(kāi)始了,根據(jù)文檔兢榨,我們首先創(chuàng)建一個(gè)factory類(lèi)
public class SmallExtensionFactory implements NamedDomainObjectFactory<SmallExtension> {
@Override
public SmallExtension create(String name) {
return new SmallExtension(name);
}
}
好像沒(méi)有問(wèn)題嗅榕,但是真的可以了嗎顺饮?少俠,你還缺少一個(gè)Instantiator
凌那,想要寫(xiě)在buildscript中定義那就徹底把new
放棄掉吧兼雄! 正確的姿勢(shì)應(yīng)該是
public class SmallExtensionFactory implements NamedDomainObjectFactory<SmallExtension> {
private Instantiator mInstantiator;
public SmallExtensionFactory(Instantiator instantiator) {
this.mInstantiator = instantiator;
}
@Override
public SmallExtension create(String name) {
return mInstantiator.newInstance(SmallExtension.class, name);
}
}
現(xiàn)在就是構(gòu)造一個(gè)NamedDomainObjectContainer,把它放到MyExtension中帽蝶。
Instantiator instantiator = ((DefaultGradle) project.getGradle()).getServices().get(
Instantiator.class);
NamedDomainObjectContainer<SmallExtension> smallExtensionsContainer = project.container(
SmallExtension.class, new SmallExtensionFactory(instantiator));
MyExtension mMyExtension = project.getExtensions().create("myExtension", MyExtension.class,
new Object[]{instantiator,smallExtensionsContainer});
MyExtension中同時(shí)添加一個(gè)NamedDomainObjectContainer<SmallExtension>的成員赦肋,set方法如下
public void smallExtensions(
Action<? super NamedDomainObjectContainer<SmallExtension>> action) {
//這里還是是用action的方式來(lái)接受buildscript的參數(shù)
//這里方法名寫(xiě)成setSmallExtensions,gradle就無(wú)法找到對(duì)應(yīng)的方法了
action.execute(mSmallExtensions);
}
還有一點(diǎn)需要注意的是,SmallExtension需要有這么要給屬性
final String mName;
到此為止励稳,我們一個(gè)簡(jiǎn)單的DSL就完成了,我們可以在buildscript中如下定義
myExtension {
extensionName "ddd" //簡(jiǎn)單屬性
innerExtension{//對(duì)象類(lèi)型
extensionName "innerExtension"
}
smallExtensions{//命名對(duì)象容器
extension1{
extensionName "11"
}
extension2{
extensionName "22"
}
}
}
上面三種類(lèi)型是可以組合是用的金砍,組成更多樣的DSL。
小結(jié)
這次內(nèi)容就到此為止麦锯,當(dāng)然Gradle的插件開(kāi)發(fā)遠(yuǎn)不是這么簡(jiǎn)單,還有一些高級(jí)特性這里還沒(méi)有寫(xiě)到琅绅,更多的就要靠大家自己去探索了扶欣。(差點(diǎn)忘了github地址)