Android 注解系列之APT工具(三)

該文章中涉及的代碼隆夯,我已經(jīng)提交到GitHub上了,大家按需下載---->源碼

騎車車.jpg

前言

在上篇文章Android 注解系列之Annotation(二)中,簡(jiǎn)要的介紹了注解的基本使用與定義类茂。同時(shí)也提出了以下幾個(gè)問題大咱,當(dāng)我們聲明了一個(gè)注解后碴巾,是不是需要手動(dòng)找到所有的Class對(duì)象或Field、Method煮仇?怎么通過注解生成新的類的定義呢夹姥?當(dāng)面對(duì)這些問題的時(shí)候,我相信大家的第一反應(yīng)肯定會(huì)想旦部,"有不有相應(yīng)的三方庫呢士八?Java是否提供了相應(yīng)庫或者方法來解決呢?"秘血,當(dāng)然Java肯定給我們提供了啦,就是我們既陌生又熟悉的APT工具啦忍坷。

為什么這里我會(huì)說既陌生又熟悉呢佩研?我相信對(duì)于大多數(shù)安卓程序适秩,我們都或多或少使用了一些主流庫秽荞,如Dagger2阶捆、ButterKnife、EventBus等儡司,這些庫都使用了APT技術(shù)酵镜。既然大佬們都在使用垢粮,那我們?cè)趺床蝗チ私饽兀亢昧丝糠啵瑫鴼w正傳蜡吧,下面我們就來看看怎么通過APT來處理之前我們提到的問題毫蚓。

APT技術(shù)簡(jiǎn)介

在具體了解APT技術(shù)之前,先簡(jiǎn)單的對(duì)其進(jìn)行介紹昔善。APT(Annotation Processing Tool)是javac中提供的一種編譯時(shí)掃描和處理注解的工具元潘,它會(huì)對(duì)源代碼文件進(jìn)行檢查,并找出其中的注解君仆,然后根據(jù)用戶自定義的注解處理方法進(jìn)行額外的處理咖摹。APT工具不僅能解析注解硫眯,還能根據(jù)注解生成其他的源文件,最終將生成的新的源文件與原來的源文件共同編譯(注意:APT并不能對(duì)源文件進(jìn)行修改操作朋鞍,只能生成新的文件缆蝉,例如在已有的類中添加方法)污尉。具體流程圖如下圖所示:

apt使用流程圖.png

APT技術(shù)使用規(guī)則

APT技術(shù)的使用焚志,需要我們遵守一定的規(guī)則。大家先看一下整個(gè)APT項(xiàng)目項(xiàng)目構(gòu)建的一個(gè)規(guī)則圖巡揍,具體如下所示:


apt_rule.png

APT使用依賴

從圖中我們可以整個(gè)APT項(xiàng)目的構(gòu)建需要三個(gè)部分:

  • 注解處理器庫(包含我們的注解處理器)
  • 注解聲明庫(用于存儲(chǔ)聲明的注解)
  • 實(shí)際使用APT的Android/Java項(xiàng)目

且三個(gè)部分的依賴關(guān)系為注解處理工具依賴注解聲明庫表箭,Android/Java項(xiàng)目同時(shí)依賴注解處理工具庫與注解聲明庫盯桦。

為什么把注解處理器獨(dú)立抽成一個(gè)庫呢玄柠?

對(duì)于Android項(xiàng)目默認(rèn)是不包含 APT相關(guān)類的当宴。所以要使用APT技術(shù)瓢娜,那么就必須創(chuàng)建一個(gè)Java Library励堡。對(duì)于Java項(xiàng)目止吐,獨(dú)立抽成一個(gè)庫百新,更容易維護(hù)與擴(kuò)展旨椒。

為什么把注解聲明也單獨(dú)抽成一個(gè)庫变过,而不放到注解處理工具庫中呢崭孤?

舉個(gè)例子,如果注解聲明與注解處理器為同一個(gè)庫拯田,如果有開發(fā)者希望把我們的注解處理器用于他的項(xiàng)目中滩字,那么就必須包含注解聲明與整個(gè)注解處理器的代碼,我們能非常確定是御吞,他并不希望已經(jīng)編譯好的項(xiàng)目中包含處理器相關(guān)的代碼麦箍。他僅僅希望使用我們的注解。所以將注解處理器與注解分開單獨(dú)抽成一個(gè)庫時(shí)非常有意義的陶珠。接下來的文章中會(huì)具體會(huì)描述有哪些方法可以將我們的注解處理器不打包在我們的實(shí)際項(xiàng)目中挟裂。

注解處理器的聲明

在了解了ATP的使用規(guī)則后,現(xiàn)在我們?cè)賮砜纯丛趺绰暶饕粋€(gè)注解處理器揍诽,每一個(gè)注解處理器都需要承AbstractProcessor類诀蓉,具體代碼如下所示:

class MineProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {}
    
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() { }

    @Override
    public Set<String> getSupportedAnnotationTypes() { }
}
  • init(ProcessingEnvironment processingEnv):每個(gè)注解處理器被初始化的時(shí)候都會(huì)被調(diào)用,該方法會(huì)被傳入ProcessingEnvironment 參數(shù)暑脆。ProcessingEnvironment 能提供很多有用的工具類渠啤,Elements、Types和Filer饵筑。后面我們將會(huì)看到詳細(xì)的內(nèi)容埃篓。
  • process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv):注解處理器實(shí)際處理方法,一般要求子類實(shí)現(xiàn)該抽象方法根资,你可以在在這里寫你的掃描與處理注解的代碼,以及生成Java文件同窘。其中參數(shù)RoundEnvironment 玄帕,可以讓你查詢出包含特定注解的被注解元素,后面我們會(huì)看到詳細(xì)的內(nèi)容想邦。
  • getSupportedAnnotationTypes(): 返回當(dāng)前注解處理器處理注解的類型裤纹,返回值為一個(gè)字符串的集合。其中字符串為處理器需要處理的注解的合法全稱丧没。
  • getSupportedSourceVersion():用來指定你使用的Java版本鹰椒,通常這里返回SourceVersion.latestSupported()。如果你有足夠的理由指定某個(gè)Java版本的話呕童,你可以返回SourceVersion.RELAEASE_XX漆际。但是還是推薦使用前者。

在Java1.6版本中提供了SupportedAnnotationTypesSupportedSourceVersion兩個(gè)注解來替代getSupportedSourceVersiongetSupportedAnnotationTypes兩個(gè)方法夺饲,也就是這樣:

@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes({"合法注解的名稱"})
class MineProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }
    
}

這里需要注意的是以上提到的兩個(gè)注解是JAVA 1.6新增的奸汇,所以出于兼容性的考慮施符,建議還是直接重寫getSupportedSourceVersion()getSupportedAnnotationTypes()方法。

注冊(cè)注解處理器

到了現(xiàn)在我們基本了解了處理器聲明擂找,現(xiàn)在我們可能會(huì)有個(gè)疑問戳吝,怎么樣將注解處理器注冊(cè)到Java編譯器中去呢?你必須提供一個(gè).jar文件贯涎,就像其他.jar文件一樣听哭,你需要打包你的注解處理器到此文件中,并且在你的jar中塘雳,你需要打包一個(gè)特定的文件javax.annotation.processing.ProcessorMETA-INF/services路徑下欢唾。就像下面這樣:

META-INF/services 相當(dāng)于一個(gè)信息包,目錄中的文件和目錄獲得Java平臺(tái)的認(rèn)可與解釋用來配置應(yīng)用程序粉捻、擴(kuò)展程序礁遣、類加載器和服務(wù)文件,在jar打包時(shí)自動(dòng)生成

放入特定文件夾.png

其中javax.annotation.processing.Processor文件中的內(nèi)容為每個(gè)注解處理器的合法的全名列表肩刃,每一個(gè)元素?fù)Q行分割祟霍,也就是類似下面這樣:

com.jennifer.andy.processor.MineProcessor1
com.jennifer.andy.processor.MineProcessor2
com.jennifer.andy.processor.MineProcessor3

最后我們只要將你生成的.jar放到你的buildPath中,那么Java編譯器會(huì)自動(dòng)的檢查和讀取javax.annotation.processing.Processor中的內(nèi)容盈包,并注冊(cè)該注解處理器沸呐。

當(dāng)然對(duì)于現(xiàn)在我們的編譯器,如IDEA呢燥、AndroidStudio等中崭添,我們只創(chuàng)建相應(yīng)文件與文件夾就行了,并不同用放在buildPath中去叛氨。當(dāng)然原因是這些編譯器都幫我們處理了啦呼渣。如果你還是嫌麻煩,那我們可以使用Google為我們提供的AutoService
注解處理器寞埠,用于生成META-INF/services/javax.annotation.processing.Processor文件的屁置。也就是我們可以像下面這樣使用:

@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes({"合法注解的名稱"})
@AutoService(Processor.class)
class MineProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }
}

我們只需要在類上聲明@AutoService(Processor.class),那么就不用考慮其他的東西啦仁连。是不是很方便呢蓝角?(當(dāng)然使用AutoService在Gralde中你需要添加依賴compile 'com.google.auto.service:auto-service:1.0-rc2')。

注解處理器的掃描

在注解處理過程中饭冬,我們需要掃描所有的Java源文件使鹅,源代碼的每一個(gè)部分都是一個(gè)特定類型的Element,也就是說Element代表源文件中的元素昌抠,例如包患朱、類、字段扰魂、方法等麦乞。整體的關(guān)系如下圖所示:

element繼承關(guān)系.png
  • Parameterizable:表示混合類型的元素(不僅只有一種類型的Element)
  • TypeParameterElement:帶有泛型參數(shù)的類蕴茴、接口、方法或者構(gòu)造器姐直。
  • VariableElement:表示字段倦淀、常量、方法或構(gòu)造函數(shù)声畏。參數(shù)撞叽、局部變量、資源變量或異常參數(shù)插龄。
  • QualifiedNameable:具有限定名稱的元素
  • ExecutableElement:表示類或接口的方法愿棋、構(gòu)造函數(shù)或初始化器(靜態(tài)或?qū)嵗ㄗ⑨岊愋驮亍?/li>
  • TypeElement :表示類和接口
  • PackageElement:表示包

那接下來我們通過下面的例子來具體的分析:

package com.jennifer.andy.aptdemo.domain;//PackageElement
class Person {//TypeElement 
    private String where;//VariableElement
    
    public void doSomething() { }//ExecutableElement
    
    public void run() {//ExecutableElement
        int runTime;//VariableElement
    }
}

通過上述例子我們可以看出均牢,APT對(duì)整個(gè)源文件的掃描糠雨。有點(diǎn)類似于我們解析XML文件(這種結(jié)構(gòu)化文本一樣)。

既然在掃描的時(shí)候徘跪,源文件是一種結(jié)構(gòu)化的數(shù)據(jù)甘邀,那么我們能不能獲取一個(gè)元素的父元素和子元素呢?垮庐。當(dāng)然是可以的啦松邪,舉例來說,假如我們有個(gè)public class Person的TypeElement元素哨查,那么我們可以遍歷它的所有的孩子元素逗抑。

TypeElement person= ... ;  
for (Element e : person.getEnclosedElements()){ // 遍歷它的孩子 
    Element parent = e.getEnclosingElement();  // 拿到孩子元素的最近的父元素
}

其中getEnclosedElements()getEnclosingElement()Element中接口的聲明,想了解更多的內(nèi)容寒亥,大家可以查看一下源碼邮府。

元素種類判斷

現(xiàn)在我們已經(jīng)了解了Element元素的分類,但是我們發(fā)現(xiàn)Element有時(shí)會(huì)代表多種元素护盈。例如TypeElement代表類或接口挟纱,那有什么方法具體區(qū)別呢?我們繼續(xù)看下面的例子:

public class SpiltElementProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //這里通過獲取所有包含Who注解的元素set集合
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Who.class);
        for (Element element : elements) {
            if (element.getKind() == ElementKind.CLASS) {//如果元素是類

            } else if (element.getKind() == ElementKind.INTERFACE) {//如果當(dāng)前元素是接口

            }
        }
        return false;
    }
    ...省略部分代碼
}

在上述例子中腐宋,我們通過roundEnvironment.getElementsAnnotatedWith(Who.class)獲取源文件中所有包含@Who注解的元素,通過調(diào)用element.getKind()具體判斷當(dāng)前元素種類檀轨,其中具體元素類型為ElementKind枚舉類型胸竞。ElementKind枚舉聲明如下表所示:

枚舉類型 種類
PACKAGE
ENUM 枚舉
CLASS
ANNOTATION_TYPE 注解
INTERFACE 接口
ENUM_CONSTANT 枚舉常量
FIELD 字段
PARAMETER 參數(shù)
LOCAL_VARIABLE 本地變量
EXCEPTION_PARAMETER 異常參數(shù)
METHOD 方法
CONSTRUCTOR 構(gòu)造函數(shù)
OTHER 其他
省略... 省略...

元素類型判斷

那接下來大家又會(huì)有一個(gè)問題了,既然我們?cè)趻呙璧氖谦@取的元素且這些元素代表著源文件中的結(jié)構(gòu)化數(shù)據(jù)参萄。那么假如我們想獲得元素更多的信息怎么辦呢卫枝?例如對(duì)于某個(gè)類,現(xiàn)在我們已經(jīng)知道了其為ElementKind.CLASS種類讹挎,但是我想獲取其父類的信息校赤,需要通過什么方式呢吆玖?對(duì)于某個(gè)方法,我們也同樣知道了其為ElementKind.METHOD種類马篮,那么我想獲取該方法的返回值類型沾乘、參數(shù)類型、參數(shù)名稱浑测,需要通過什么方式呢翅阵?

當(dāng)然Java已經(jīng)為我們提供了相應(yīng)的方法啦。使用mirror API就能解決這些問題啦迁央,它能使我們?cè)谖唇?jīng)編譯的源代碼中查看方法掷匠、域以及類型信息。在實(shí)際使用中通過TypeMirror來獲取元素類型岖圈《镉铮看下面的例子:

public class TypeKindSpiltProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Who.class);
        for (Element element : elements) {
            if (element.getKind() == ElementKind.METHOD) {//如果當(dāng)前元素是接口
                ExecutableElement methodElement = (ExecutableElement) element;
                TypeMirror returnType = methodElement.getReturnType();//獲取TypeMirror
                TypeKind kind = returnType.getKind();//獲取元素類型
                System.out.println("print return type----->" + kind.toString());
            }
        }
        return false;
    }

}

觀察上述代碼我們可以發(fā)現(xiàn),當(dāng)我們使用注解處理器時(shí)蜂科,我們會(huì)先找到相應(yīng)的Element顽决,如果你想獲得該Element的更多的信息,那么可以配合TypeMirror使用TypeKind來判斷當(dāng)前元素的類型崇摄。當(dāng)然對(duì)于不同種類的Element擎值,其獲取的TypeMirror方法可能會(huì)不同。TypeKind枚舉聲明如下表所示:

枚舉類型 類型
BOOLEAN boolean 類型
BYTE byte 類型
SHORT short 類型
INT int 類型
LONG long 類型
CHAR char 類型
FLOAT float 類型
DOUBLE double 類型
VOID void類型逐抑,主要用于方法的返回值
NONE 無類型
NULL 空類型
ARRAY 數(shù)組類型
省略... 省略...

元素可見性修飾符

在注解處理器中鸠儿,我們不僅能獲得元素的種類和信息,我們還能獲取該元素的可見性修飾符(例如public厕氨、private等)进每。我們可以直接調(diào)用Element.getModifiers(),具體代碼如下所示:

public class GetModifiersProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Who.class);
        for (Element element : elements) {
            if (element.getKind() == ElementKind.CLASS) {//如果元素是類
                Set<Modifier> modifiers = element.getModifiers();//獲取可見性修飾符
                if (!modifiers.contains(Modifier.PUBLIC)) {//如果當(dāng)前類不是public
                    throw new ProcessingException(classElement, "The class %s is not public.",
                            classElement.getQualifiedName().toString());
                }
            }
        return false;
    }
}

在上述代碼中Modifer為枚舉類型命斧,具體枚舉如下所示:

public enum Modifier {

    /** The modifier {@code public} */          PUBLIC,
    /** The modifier {@code protected} */       PROTECTED,
    /** The modifier {@code private} */         PRIVATE,
    /** The modifier {@code abstract} */        ABSTRACT,
    /**
     * The modifier {@code default}
     * @since 1.8
     */
     DEFAULT,
    /** The modifier {@code static} */          STATIC,
    /** The modifier {@code final} */           FINAL,
    /** The modifier {@code transient} */       TRANSIENT,
    /** The modifier {@code volatile} */        VOLATILE,
    /** The modifier {@code synchronized} */    SYNCHRONIZED,
    /** The modifier {@code native} */          NATIVE,
    /** The modifier {@code strictfp} */        STRICTFP;
}

錯(cuò)誤處理

在注解處理器的自定義中田晚,我們不僅能調(diào)用相關(guān)方法獲取源文件中的元素信息,還能通過處理器提供的Messager來報(bào)告錯(cuò)誤国葬、警告以及提示信息贤徒。可以直接使用processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg);需要注意的是它并不是處理器開發(fā)中的日志工具汇四,而是用來寫一些信息給使用此注解庫的第三方開發(fā)者的接奈。也就是說如果我們像傳統(tǒng)的Java應(yīng)用程序拋出一個(gè)異常的話,那么運(yùn)行注解處理器的JVM就會(huì)崩潰通孽,并且關(guān)于JVM中的錯(cuò)誤信息對(duì)于第三方開發(fā)者并不是很友好序宦,所以推薦并且強(qiáng)烈建議使用Messager。就像下面這樣背苦,當(dāng)我們判斷某個(gè)類不是public修飾的時(shí)候互捌,我們通過Messager來報(bào)告錯(cuò)誤潘明。

注解處理器是運(yùn)行它自己的虛擬機(jī)JVM中。是的秕噪,你沒有看錯(cuò)钳降,javac啟動(dòng)一個(gè)完整Java虛擬機(jī)來運(yùn)行注解處理器。

public class GetModifiersProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Who.class);
        for (Element element : elements) {
            if (element.getKind() == ElementKind.CLASS) {//如果元素是類
                Set<Modifier> modifiers = element.getModifiers();//獲取可見性修飾符
                if (!modifiers.contains(Modifier.PUBLIC)) {//如果當(dāng)前類不是public
                    roundEnvironment.getMessager().printMessage(Diagnostic.Kind.ERROR, "the class is not public");
                }
            }
        return false;
    }
}

同時(shí)巢价,在官方文檔中牲阁,描述了消息的不同級(jí)別,關(guān)于更多的消息級(jí)別壤躲,大家可以通過從Diagnostic.Kind枚舉中查看城菊。

錯(cuò)誤信息顯示界面

如果你需要使用處理器提供的 Messager 來打印日志,那么你需要在如下界面中查看輸出的信息:

 roundEnvironment.getMessager().printMessage(Diagnostic.Kind.ERROR, "the class is not public");

使用如上代碼碉克,查看日志界面如下所示:

messager錯(cuò)誤展示界面.png

在每次編譯代碼的時(shí)候凌唬,如果你使用了 Messager 來打印日志,那么就會(huì)顯示漏麦。

文件生成

到了現(xiàn)在我們已經(jīng)基本了解整個(gè)APT的基礎(chǔ)知識(shí)】退埃現(xiàn)在來講講APT技術(shù)如何生成新的類的定義(也就是創(chuàng)建新的源文件)。對(duì)于創(chuàng)建新的文件撕贞,我們并不用像基本文件操作一樣更耻,通過調(diào)用IO流來進(jìn)行讀寫操作。而是通過JavaPoet來構(gòu)造源文件捏膨。(當(dāng)然當(dāng)你使用JavaPoet時(shí)秧均,在gradle中你需要添加依賴compile 'com.google.auto.service:auto-service:1.0-rc2'),JavaPoet的使用也非常簡(jiǎn)單号涯,就像下面這樣:

當(dāng)進(jìn)行注釋處理或與元數(shù)據(jù)文件(例如目胡,數(shù)據(jù)庫模式、協(xié)議格式)交互時(shí)链快,JavaPoet對(duì)于源文件的生成可能非常有用誉己。通過生成代碼,消除了編寫樣板的必要性域蜗,同時(shí)也保持了元數(shù)據(jù)的單一來源巨双。

@AutoService(Processor.class)
@SupportedAnnotationTypes("com.jennifer.andy.apt.annotation.Who")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class CreateFileByJavaPoetProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        createFileByJavaPoet(set, roundEnvironment);
        return false;
    }
    
    /**
     * 通過JavaPoet生成新的源文件
     */
    private void createFileByJavaPoet(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //創(chuàng)建main方法
        MethodSpec main = MethodSpec.methodBuilder("main")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)//設(shè)置可見性修飾符public static
                .returns(void.class)//設(shè)置返回值為void
                .addParameter(String[].class, "args")//添加參數(shù)類型為String數(shù)組,且參數(shù)名稱為args
                .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")//添加語句
                .build();
        //創(chuàng)建類
        TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(main)//將main方法添加到HelloWord類中
                .build();

        //創(chuàng)建文件霉祸,第一個(gè)參數(shù)是包名炉峰,第二個(gè)參數(shù)是相關(guān)類
        JavaFile javaFile = JavaFile.builder("com.jennifer.andy.aptdemo.domain", helloWorld)
                .build();

        try {
            //創(chuàng)建文件
            javaFile.writeTo(processingEnv.getFiler());
        } catch (IOException e) {
            log(e.getMessage());
        }

    }

    /**
     * 調(diào)用打印語句而已
     */
    private void log(String msg) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg);
    }

}

當(dāng)我們build上述代碼后,我們可以在我們的build目錄下得到下列文件:


生成文件結(jié)果.png

關(guān)于JavaPoet的更多的詳細(xì)使用脉执,大家可以參考官方文檔-------->JavaPoet

分離處理器和項(xiàng)目

在上文中描述的APT使用規(guī)則中,我們是將注解聲明庫注解處理器庫分成了兩個(gè)庫戒劫,具體原因我也做了詳細(xì)的解釋半夷,現(xiàn)在我們來思考如下問題婆廊。就算我們把兩個(gè)庫都抽成了兩個(gè)獨(dú)立的庫,但是如果有開發(fā)者想把我們自定義的注解處理器用于他的項(xiàng)目中巫橄,那么他整個(gè)項(xiàng)目的編譯就必須也要把注解處理器與注解聲明庫包括進(jìn)來淘邻。對(duì)于開發(fā)者來說,他們并不希望已經(jīng)編譯好的項(xiàng)目中有包含注解處理器的相關(guān)代碼湘换。所以將注解聲明庫與注解處理器庫不打包進(jìn)入項(xiàng)目是非常有必要的1鼍恕!換句話說彩倚,注解處理器只在編譯處理期間需要用到筹我,編譯處理完后就沒有實(shí)際作用了,而主項(xiàng)目添加了這個(gè)庫會(huì)引入很多不必要的文件帆离。

因?yàn)樽髡呶冶旧硎茿ndroid開發(fā)人員蔬蕊,所以以下都是針對(duì)Android項(xiàng)目展開討論。

使用android-apt

anroid-apt是Hugo Visser開發(fā)的一個(gè)Gradle插件哥谷,該插件的主要作用有如下兩點(diǎn):

  • 允許只將編譯時(shí)注釋處理器配置為依賴項(xiàng)岸夯,而不在最終APK或庫中包括工件
  • 設(shè)置源路徑,以便Android Studio能正確地找到注釋處理器生成的代碼

但是 Google爸爸看到別人這個(gè)功能功能不錯(cuò)们妥,所以為自己的Android Gradle 插件也添加了名為annotationProcessor 的功能來完全代替 android-apt猜扮,既然官方支持了。那我們就去看看annotationProcessor的使用吧监婶。

annotationProcessor使用

其實(shí)annotationProcessor的使用也非常簡(jiǎn)單旅赢,分為兩種類型,具體使用如下代碼所示:

 annotationProcessor project(':apt_compiler')//如果是本地庫
 annotationProcessor 'com.jakewharton:butterknife-compiler:9.0.0-rc1'//如果是遠(yuǎn)程庫

總結(jié)

整個(gè)APT的流程下來压储,自己也查閱了非常多的資料鲜漩,也解決了許多問題。雖然寫博客也花了非常多的時(shí)間集惋。但是自己也發(fā)現(xiàn)了很多有趣的問題孕似。我發(fā)現(xiàn)查閱的相關(guān)資料都會(huì)有一個(gè)通病。也就是沒有真正搞懂android apt與annotationProcessor的具體作用刮刑。所以這里這里也要告誡大家喉祭,對(duì)于網(wǎng)上的資料,自己一定要帶著懷疑與疑問的態(tài)度去瀏覽雷绢。

同時(shí)個(gè)人覺得Gradle這一塊的知識(shí)點(diǎn)也非常重要泛烙。因?yàn)殛P(guān)于怎么不把庫打包到實(shí)際項(xiàng)目中也是構(gòu)建工具的特性與功能。希望大家有時(shí)間翘紊,一定要學(xué)習(xí)下相關(guān)Gradle知識(shí)蔽氨。作者最近也在學(xué)習(xí)呢。和我一起加油吧~

該文章中涉及的代碼,我已經(jīng)提交到GitHub上了鹉究,大家按需下載---->源碼

最后

該文章參考以下博客與圖書宇立,站在巨人的肩膀上∽耘猓可以看得更遠(yuǎn)妈嘹。

ANNOTATION PROCESSING 101

自定義注解之編譯時(shí)注解(RetentionPolicy.CLASS)

你必須知道的APT、annotationProcessor绍妨、android-apt润脸、Provided、自定義注解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末他去,一起剝皮案震驚了整個(gè)濱河市毙驯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌孤页,老刑警劉巖尔苦,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異行施,居然都是意外死亡允坚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門蛾号,熙熙樓的掌柜王于貴愁眉苦臉地迎上來稠项,“玉大人,你說我怎么就攤上這事鲜结≌乖耍” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵精刷,是天一觀的道長(zhǎng)拗胜。 經(jīng)常有香客問我,道長(zhǎng)怒允,這世上最難降的妖魔是什么埂软? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮纫事,結(jié)果婚禮上勘畔,老公的妹妹穿的比我還像新娘。我一直安慰自己丽惶,他們只是感情好炫七,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钾唬,像睡著了一般万哪。 火紅的嫁衣襯著肌膚如雪侠驯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天壤圃,我揣著相機(jī)與錄音陵霉,去河邊找鬼。 笑死伍绳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的乍桂。 我是一名探鬼主播冲杀,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼睹酌!你這毒婦竟也來了权谁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤憋沿,失蹤者是張志新(化名)和其女友劉穎旺芽,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辐啄,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡采章,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了壶辜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悯舟。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖砸民,靈堂內(nèi)的尸體忽然破棺而出抵怎,到底是詐尸還是另有隱情,我是刑警寧澤岭参,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布反惕,位于F島的核電站,受9級(jí)特大地震影響演侯,放射性物質(zhì)發(fā)生泄漏姿染。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一蚌本、第九天 我趴在偏房一處隱蔽的房頂上張望盔粹。 院中可真熱鬧,春花似錦程癌、人聲如沸舷嗡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽进萄。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間中鼠,已是汗流浹背可婶。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留援雇,地道東北人矛渴。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像惫搏,于是被迫代替她去往敵國和親具温。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348