?????? 之前沒(méi)有接觸過(guò) Annotation Processor(注解處理器)這個(gè)概念,當(dāng)研究Butterknife框架實(shí)現(xiàn)機(jī)制時(shí),發(fā)現(xiàn)Annotation Processor是Butterknife框架實(shí)現(xiàn)機(jī)制的核心要點(diǎn)。原來(lái)代碼本身能生成代碼丑搔,我之前還停留在程序運(yùn)行階段利用反射去讀取注解再進(jìn)行處理的階段勋磕,有了Annotation Processor之后,我們就能在編譯時(shí)根據(jù)注解動(dòng)態(tài)生成代碼埠忘,執(zhí)行與注解關(guān)聯(lián)的邏輯,可以有效避免反射的性能問(wèn)題馒索,非常高大上莹妒。
Annotation(注解)分類:
????? 按照運(yùn)行機(jī)制,注解分三類:
????????? 源碼注解(RetentionPolicy.SOURCE): 注解只在源碼中存在绰上,編譯成.class文件就不存在了旨怠。
??????????編譯時(shí)注解(RetentionPolicy.CLASS):注解在.class文件里面存在。?
????????? 運(yùn)行時(shí)注解(RetentionPolicy.RUNTIME):在運(yùn)行階段還起作用蜈块,甚至?xí)绊戇\(yùn)行邏輯的注解?鉴腻。
???????? 上面三類中,Annotation Processor處理的對(duì)象主要針對(duì) 編譯時(shí)注解
Annotation Processor是什么百揭?
????????它是一個(gè)工具爽哎,包含在javac中,Java5之后才有(因?yàn)锳nnotation也是Java5才有的)器一,但Java6中才有API供使用课锌。
Annotation Processor的作用?
??????? 編譯時(shí)掃描和處理注解(主要是編譯時(shí)注解)祈秕。一個(gè)注解的注解處理器渺贤,以Java代碼(或者編譯過(guò)的字節(jié)碼)作為輸入,生成文件(一般是.java文件)作為輸出请毛。
??????? 通過(guò)Annotation Processor志鞍,我們獲取到注解,然后再生成相關(guān)Java代碼方仿,注意:注解處理器不能更新手寫的Java代碼固棚,比如在一個(gè)類中增加方法街州。新生成Java代碼會(huì)與手寫的代碼一樣,會(huì)被javac編譯玻孟。
如何使用Annotation Processor?
第一步:實(shí)現(xiàn)處理函數(shù)
?????? JDK提供了javax.annotation.processing.AbstractProcessor來(lái)處理注解唆缴,這是一個(gè)抽象類,需要實(shí)現(xiàn)其中幾個(gè)方法黍翎。
?????? public synchronized void init(ProcessingEnvironment var1);
????????????????? 它會(huì)被注解處理工具調(diào)用面徽,并輸入ProcessingEnvironment 參數(shù)。ProcessingEnvironment 提供很多有用的工具類匣掸,比如Elements趟紊、Types、Filer等
?????? public Set<String> getSupportedAnnotationTypes();??
?????????????????? 必須指定碰酝,返回值是一個(gè)字符串的集合霎匈,包含本處理器想要處理的注解類型的合法全稱。即你在這里定義你的注解處理器處理哪些注解
?????? public SourceVersion getSupportedSourceVersion();
?????????????????? 用來(lái)指定使用的Java版本送爸,一般返回 SourceVersion.latestSupported();
??????? public abstract boolean process(Set<? extends TypeElement> var1, RoundEnvironment var2);?
?????????????????? 這里實(shí)現(xiàn)主要的處理邏輯铛嘱,利用init函數(shù)里得到的工具類Elements、Types袭厂、 Filer 等來(lái)處理注解墨吓,并自動(dòng)生成代碼。
?????????????????? 在這里除了可以調(diào)用JDK里的api外纹磺,還可以使用很多第三方庫(kù)帖烘,因?yàn)閖avac會(huì)啟動(dòng)一個(gè)完整Java虛擬機(jī)來(lái)運(yùn)行注解處理器。
第二步:注冊(cè)Annotation Processor
??????? 實(shí)現(xiàn)好AbstractProcessor之后橄杨,比如名叫MyProcessor秘症,需要將MyProcessor注冊(cè)到j(luò)avac中。首先需要將MyProcessor打包成jar式矫,比如叫MyProcessor.jar乡摹,在這個(gè)jar中,你需要打包一個(gè)特定的文件javax.annotation.processing.Processor
到META-INF/services
路徑下
???????? javax.annotation.processing.Processor這個(gè)文件里的內(nèi)容是我們實(shí)現(xiàn)的Annotation Processor的列表,比如:
???? ?com.example.MyProcessor1
?? ? ?com.example.MyProcessor2
??? 這說(shuō)明我們可以實(shí)現(xiàn)不止一個(gè)Annotation Processor衷佃。
最后把MyProcessor.jar
放到你的builpath中趟卸,javac會(huì)自動(dòng)檢查和讀取javax.annotation.processing.Processor
中的內(nèi)容蹄葱,并且注冊(cè)MyProcessor
作為注解處理器氏义。
如何生成META-INF等目錄以及javax.annotation.processing.Processor文件?
??第一種方式:手工編寫生成图云。
???? 創(chuàng)建resources 源文件夾 惯悠、然后在里面創(chuàng)建META-INF/services/javax.annotation.processing.Processor文件,這個(gè)文件寫處理器的類完整路徑竣况。
??第二種方式:利用google提供的AutoService注解處理器克婶,等于說(shuō)我們的注解處理器需要被AutoService注解處理器先進(jìn)行處理。使用方式(針對(duì)Android Studio里的grandle配置):
? 1.注解處理器模塊的build.gradle文件中引入
???????? compile 'com.google.auto.service:auto-service:1.0-rc2'
? 2.在public class MyProcessor上面使作注解:@AutoService(Processor.class)
如何將MyProcessor.jar放到builpath中?
?? 需要將Annotation Processor所在的工程單獨(dú)成一個(gè)java標(biāo)準(zhǔn)工程
??1.使用android-apt Gradle插件:
apply plugin: 'com.neenbedankt.android-apt'
?? 其有兩個(gè)作用:?
???a.允許配置只在編譯時(shí)作為注解處理器的依賴情萤,而不添加到最后的APK或library(因?yàn)檫\(yùn)行時(shí)用不到鸭蛙,沒(méi)必要增加apk的體積)
???b.設(shè)置源路徑,使注解處理器生成的代碼能被Android Studio正確的引用
2.在dependencies里 用apt替代compile來(lái)引用處理器模塊
apt project(':annotation-comprocesser')
工具類Elements筋岛、Types娶视、Filer等的使用
??? 作用:Elements是處理Element的工具類,Element代表程序的元素,例如包睁宰、類或者方法肪获,可以理解成源代碼;TypeElement代表的是源代碼中的類型元素,例如類柒傻、域孝赫、方法等;從TypeElement中能獲取類的名字,但是你獲取不到類的信息红符,例如它的父類青柄,這個(gè)需要從TypeMirror獲取,而TypeMirror需要調(diào)用Element的asType()函數(shù)
? 使用步驟:
? 在process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)函數(shù)中
? 1.利用roundEnv.getElementsAnnotatedWith(xxx.class)可以得到xxx注解標(biāo)注的Element集合
???? Element表示一個(gè)程序元素预侯,比如類本身刹前、類的變量、方法等雌桑,這個(gè)Element集合中將所有類中的元素都列出來(lái)了喇喉,為了方便生成java代碼,需要要將Element集合按照類進(jìn)行分類校坑,以便生成操作該類的類
? 2.通過(guò)Element的getEnclosingElement()函數(shù)拣技,可以獲取該元素最外層的元素,比如方法耍目,其肯定屬于某個(gè)類膏斤,故其最外層的元素就是類,此元素用TypeElement表示邪驮,再通過(guò)TypeElement的getQualifiedName()就能拿到類全名
??? 如果我們只想獲取特定類型上的注解元素莫辨,比如只想要標(biāo)準(zhǔn)在class上的元素,可以用Element的getKind()函數(shù)來(lái)獲取元素的類型(比如返回ElementKind.CLASS表示該元素是類)來(lái)過(guò)濾毅访,比如這樣:
??? // 檢查被注解為@Factory的元素是否是一個(gè)類 ?????
??? if (annotatedElement.getKind() != ElementKind.CLASS) {
???????...
???? }
??? Elements的getPackageOf(Element type)能得到包信息
? 3.如果我們知道一個(gè)Element是ElementKind.CLASS或ElementKind.Field沮榜,可以強(qiáng)轉(zhuǎn)成TypeElement,然后用TypeElement的getAnnotation(xxx.class)取出注解的實(shí)例信息喻粹,這些信息是使用注解時(shí)蟆融,相當(dāng)于傳入的實(shí)參
??4.注解所在類拿到了,注解的信息也拿到了守呜,被注解的元素也拿到了型酥,最后我們可以通過(guò)這些信息產(chǎn)生新的文件了山憨。
? 5.生成代碼∶趾恚可以使用第三方庫(kù)Javapoet來(lái)加快生成速度郁竟,或者直接使用FileWriter
???? 如果要生成java的源碼,需要用到Filer
需要注意的問(wèn)題
1.注解處理過(guò)程是一個(gè)有序的循環(huán)過(guò)程由境,因?yàn)樯傻男碌奈募幸灿锌赡馨枰惶幚淼淖⒔猓?雖然需要經(jīng)過(guò)多輪處理枪孩,但不能重載或者重新創(chuàng)建已經(jīng)生成的源代碼,所以最后生成文件后藻肄,需要將為了生成文件而保存的緩存清除蔑舞,避免第二輪又調(diào)用process去試圖重復(fù)生成文件而導(dǎo)致異常
2.為什么要單獨(dú)將Annotation Processor打包成一個(gè)jar,因?yàn)锳nnotation Processor模塊只是為了編譯嘹屯,在運(yùn)行時(shí)不需要攻询,為了使最后的程序不過(guò)于臃腫,而且apk中.dex文件也有65k個(gè)方法的限制州弟,同時(shí)Annotation Processor是可以包含其它庫(kù)的钧栖,所以要避免將其打包進(jìn)去。
3.Annotation Processor是發(fā)生在類型擦除(type erasure)之前的婆翔,所以它可以做很多復(fù)雜的事情
???????