因爲運行時註解須要在Activity初始化中進行綁定操做,調用了大量反射相關代碼,在界面複雜的狀況下,使用這種方法就會嚴重影響Activity初始化效率。而ButterKnife使用了更高效的方式——Annotation Processor來完成這一工做,那麼什麼是 Annotation Processor呢?java
Annotation Processor即爲註解的處理器。與運行時註解RetentionPolicy.RUNTIME 不一樣,Annotation Processor處理RetentionPolicy.SOURCE類型的註解。在Java代碼編譯階段對標註RetentionPolicy.SOURCE類型的註解進行處理。這樣在編譯過程當中添加代碼,效率就很是高了。一樣,Annotation Processor也能夠實現IDE編寫代碼時的各類代碼檢驗,例如當你在一個並未覆寫任何父類方法的函數上添加 @Override 註解,IDE會紅線標識出你的函數提示錯誤。app
建立一個名爲MyProcessorTest的項目工程,獨立於主app module,咱們獨立開發了自定義的processor module程。項目結構以下:ide
MyProcessorTest │ ├─MyProcessor │ │ │ └─src │ └─main │ └─java │ └─com │ └─processor │ MyProcessor.java │ TestAnnotation.java │ └─src └─main └─java └─com └─hello HelloWorld.java
用Annotation Processor須要實現AbstraceProcessor
這個抽象類,示例代碼以下:函數
public class MyProcessor extends AbstractProcessor { @Override public synchronized void init(ProcessingEnvironment env){ } @Override public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { } @Override public Set<String> getSupportedAnnotationTypes() { } @Override public SourceVersion getSupportedSourceVersion() { } }
init(ProcessingEnvironment env)
: 每個註解處理器類都必須有一個空的構造函數。然而,這裏有一個特殊的init()
方法,它會被註解處理工具調用,並輸入ProcessingEnviroment
參數。ProcessingEnviroment
提供不少有用的工具類Elements
, Types
和Filer
。後面咱們將看到詳細的內容。process(Set<? extends TypeElement> annotations, RoundEnvironment env)
: 這至關於每一個處理器的主函數main()
。你在這裏寫你的掃描、評估和處理註解的代碼,以及生成Java文件。參數annotations
表示被處理的全部的註解。參數 env ,可讓你查詢出包含特定註解的被註解元素。後面咱們將看到詳細的內容。返回值表示是否截獲該註解(不進行進一步處理)。getSupportedAnnotationTypes()
: 須要聲明此Processor所支持處理的註解類 。注意,它的返回值是一個字符串的集合,包含本處理器想要處理的註解類型的合法全稱。換句話說,你在這裏定義你的註解處理器註冊到哪些註解上。getSupportedSourceVersion()
: 用來指定你使用的Java版本。一般這裏返回SourceVersion.latestSupported()
。或者制定與你JDK版本相同的版本,例如你本地JDK版本爲1.7,那麼只須要返回SourceVersion.RELEASE_7便可。在JDK1.7中,你也可使用註解來代替getSupportedAnnotationTypes()
和getSupportedSourceVersion()
,像這樣:工具
import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; @SupportedAnnotationTypes({"com.processor.TestAnnotation"}) @SupportedSourceVersion(SourceVersion.RELEASE_7) public class MyProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { System.out.println("Test log in MyProcessor.process"); return false; } }
因爲自定義Processor類最終是經過打包成jar,在編譯過程當中調用的。爲了讓java編譯器識別出這個自定義的Processor,須要打包一個特定的文件javax.annotation.processing.Processor
到META-INF/services
路徑,而後將這個自定義的類名註冊進去。
javax.annotation.processing.Processor
文件內容:測試
com.processor.MyProcessor com.foo.OtherProcessor net.blabla.SpecialProcessor
每個註解處理器須要換行,把MyProcessor.jar
放到你的builpath中,javac會自動檢查和讀取javax.annotation.processing.Processor
中的內容,而且註冊MyProcessor
做爲註解處理器。gradle
註冊完成後的MyProcessor
工程結構以下:ui
├─MyProcessor │ │ │ └─src │ └─main │ ├─java │ │ └─com │ │ └─processor │ │ MyProcessor.java │ │ TestAnnotation.java │ │ │ └─resources │ └─META-INF │ └─services │ javax.annotation.processing.Processor
這樣自定義Processor的基本雛形就完成了。spa
接下來編寫HelloWorld類,引入自定義註解:.net
import com.processor.TestAnnotation; public class HelloWorld { @TestAnnotation(value = 5, what = "This is a test") public static String msg = "Hello world!"; public static void main(String[] args) { System.out.println(msg); } }
首先在根目錄的settings.gradle中添加processor工程,以便在根目錄下直接編譯兩個工程,以及後續的依賴配置。
settings.gradle文件內容:
include ':app','MyProcessor'
而後在app module目錄的build.gradle中聲明依賴,以便在HelloWorld中完成對自定義註解的處理:
... dependencies { compile project('MyProcessor') }
接下來就能夠編譯項目了,在根目錄下執行如下命令:
gradlew.bat assemble
輸出如下日誌:
Executing command: ":assemble" :MyProcessor:compileJava UP-TO-DATE :MyProcessor:processResources UP-TO-DATE :MyProcessor:classes UP-TO-DATE :MyProcessor:jar UP-TO-DATE :compileJava Test log in MyProcessor.process Test log in MyProcessor.process :processResources UP-TO-DATE :classes :jar :assemble BUILD SUCCESSFUL Total time: 7.353 secs Completed Successfully
前面只打印了編譯時運行到這一步的日誌,接下來咱們看看註解處理器如何根據參數處理業務邏輯。
示例代碼以下:
@SupportedAnnotationTypes({"com.processor.TestAnnotation"}) @SupportedSourceVersion(SourceVersion.RELEASE_7) public class MyProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { System.out.println("Test log in MyProcessor.process"); System.out.println(roundEnv.toString()); for (TypeElement typeElement : annotations) { // 遍歷annotations獲取annotation類型 for (Element element : roundEnv.getElementsAnnotatedWith(typeElement)) { // 使用roundEnv.getElementsAnnotatedWith獲取全部被某一類型註解標註的元素,依次遍歷 // 在元素上調用接口獲取註解值 int annoValue = element.getAnnotation(TestAnnotation.class).value(); String annoWhat = element.getAnnotation(TestAnnotation.class).what(); System.out.println("value = " + annoValue); System.out.println("what = " + annoWhat); // 向當前環境輸出warning信息 processingEnv.getMessager().printMessage(Kind.WARNING, "value = " + annoValue + ", what = " + annoWhat, element); } } return false; } }
運行命令:
gradlew.bat compileJava
打印出的日誌:
Executing command: ":compileJava" :MyProcessor:compileJava :MyProcessor:processResources UP-TO-DATE :MyProcessor:classes :MyProcessor:jar :compileJava Test log in MyProcessor.process [errorRaised=false, rootElements=[com.hello.HelloWorld], processingOver=false] D:\test\MyProcessorTest\src\main\java\com\hello\HelloWorld.java:8: 警告: value = 5, what = This is a test public static String msg = "Hello world!"; ^ 1 個警告 value = 5 what = This is a test Test log in MyProcessor.process [errorRaised=false, rootElements=[], processingOver=true] BUILD SUCCESSFUL Total time: 9.048 secs Completed Successfully
到此,咱們對註解處理過程有了一個大體的瞭解。我必須再次說明一下:註解處理器是一個很是強大的工具,減小了不少機械式的代碼編寫工做。須要注意的是,註解處理器能夠作到比我上面提到的工廠模式的例子複雜不少的事情。例如,泛型的類型擦除,由於註解處理器是發生在類型擦除(type erasure)以前的(譯者注:類型擦除能夠參考這裏)。