前言
由于运行时注解需要在Activity初始化中进行绑定操作,调用了大量反射相关代码,在界面复杂的情况下,使用这种方法就会严重影响Activity初始化效率。而ButterKnife使用了更高效的方式——Annotation Processor来完成这一工作,那么什么是 Annotation Processor呢?
Annotation Processor即为注解的处理器。与运行时注解RetentionPolicy.RUNTIME 不同,Annotation Processor处理RetentionPolicy.SOURCE类型的注解。在Java代码编译阶段对标注RetentionPolicy.SOURCE类型的注解进行处理。这样在编译过程中添加代码,效率就非常高了。同样,Annotation Processor也可以实现IDE编写代码时的各种代码检验,例如当你在一个并未覆写任何父类方法的函数上添加 @Override 注解,IDE会红线标识出你的函数提示错误。
使用过程
1.创建测试自定义Annotation processor的工程
创建一个名为MyProcessorTest的项目工程,独立于主app module,我们独立开发了自定义的processor module程。项目结构如下:
MyProcessorTest
│
├─MyProcessor
│ │
│ └─src
│ └─main
│ └─java
│ └─com
│ └─processor
│ MyProcessor.java
│ TestAnnotation.java
│
└─src
└─main
└─java
└─com
└─hello
HelloWorld.java
2.创建自定义Annotation Processor
用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;
}
}
3.注册Processor
由于自定义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
作为注解处理器。
注册完成后的MyProcessor
工程结构如下:
├─MyProcessor
│ │
│ └─src
│ └─main
│ ├─java
│ │ └─com
│ │ └─processor
│ │ MyProcessor.java
│ │ TestAnnotation.java
│ │
│ └─resources
│ └─META-INF
│ └─services
│ javax.annotation.processing.Processor
这样自定义Processor的基本雏形就完成了。
4.引用自定义注解
接下来编写HelloWorld类,引入自定义注解:
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);
}
}
5.配置Gradle编译环境
首先在根目录的settings.gradle中添加processor工程,以便在根目录下直接编译两个工程,以及后续的依赖配置。
settings.gradle文件内容:
include ':app','MyProcessor'
然后在app module目录的build.gradle中声明依赖,以便在HelloWorld中完成对自定义注解的处理:
...
dependencies {
compile project('MyProcessor')
}
6.执行自定义Processor
接下来就可以编译项目了,在根目录下执行以下命令:
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
7. 添加注解处理及信息提示
前面只打印了编译时运行到这一步的日志,接下来我们看看注解处理器如何根据参数处理业务逻辑。
示例代码如下:
@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)之前的(译者注:类型擦除可以参考这里)。