Gradle插件開(kāi)發(fā)

前言

學(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)如下:

目錄結(jié)構(gòu).png

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è)解釋

image

在正常情況下,一個(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+" !";
    }
}

上面的代碼鳞仙,一眼就能看到諸如InputOutput叨襟、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地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市千扶,隨后出現(xiàn)的幾起案子料祠,更是在濱河造成了極大的恐慌,老刑警劉巖澎羞,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件髓绽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡妆绞,警方通過(guò)查閱死者的電腦和手機(jī)顺呕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)括饶,“玉大人株茶,你說(shuō)我怎么就攤上這事⊥佳妫” “怎么了启盛?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)技羔。 經(jīng)常有香客問(wèn)我僵闯,道長(zhǎng),這世上最難降的妖魔是什么藤滥? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任鳖粟,我火速辦了婚禮,結(jié)果婚禮上超陆,老公的妹妹穿的比我還像新娘牺弹。我一直安慰自己浦马,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布张漂。 她就那樣靜靜地躺著晶默,像睡著了一般。 火紅的嫁衣襯著肌膚如雪航攒。 梳的紋絲不亂的頭發(fā)上磺陡,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音漠畜,去河邊找鬼币他。 笑死,一個(gè)胖子當(dāng)著我的面吹牛憔狞,可吹牛的內(nèi)容都是我干的蝴悉。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼瘾敢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼拍冠!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起簇抵,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤庆杜,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后碟摆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體晃财,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年典蜕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了断盛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡愉舔,死狀恐怖郑临,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情屑宠,我是刑警寧澤厢洞,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站典奉,受9級(jí)特大地震影響躺翻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卫玖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一公你、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧假瞬,春花似錦陕靠、人聲如沸迂尝。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)垄开。三九已至,卻和暖如春税肪,著一層夾襖步出監(jiān)牢的瞬間溉躲,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工益兄, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锻梳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓净捅,卻偏偏與公主長(zhǎng)得像疑枯,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛔六,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容