該文章中涉及的代碼隆夯,我已經(jīng)提交到GitHub上了,大家按需下載---->源碼
前言
在上篇文章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技術(shù)使用規(guī)則
APT技術(shù)的使用焚志,需要我們遵守一定的規(guī)則。大家先看一下整個(gè)APT項(xiàng)目項(xiàng)目構(gòu)建的一個(gè)規(guī)則圖巡揍,具體如下所示:
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版本中提供了SupportedAnnotationTypes
與SupportedSourceVersion
兩個(gè)注解來替代getSupportedSourceVersion
與getSupportedAnnotationTypes
兩個(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.Processor
到META-INF/services路徑
下欢唾。就像下面這樣:
META-INF/services 相當(dāng)于一個(gè)信息包,目錄中的文件和目錄獲得Java平臺(tái)的認(rèn)可與解釋用來配置應(yīng)用程序粉捻、擴(kuò)展程序礁遣、類加載器和服務(wù)文件,在jar打包時(shí)自動(dòng)生成
其中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)系如下圖所示:
- 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");
使用如上代碼碉克,查看日志界面如下所示:
在每次編譯代碼的時(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目錄下得到下列文件:
關(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)妈嘹。
自定義注解之編譯時(shí)注解(RetentionPolicy.CLASS)
你必須知道的APT、annotationProcessor绍妨、android-apt润脸、Provided、自定義注解