因为运行时注解须要在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)以前的(译者注:类型擦除能够参考这里)。