前言
自前一篇文章:手写简化EventBus,理解框架核心原理(反射实现方式)写完后,一直在研究注解处理器实现方式,中间又有其他事情耽搁了,所以到今天才补上这篇文章。
此篇文章是在上篇反射方式实现的源码的基础上进行更改实现的,所以如果还没看上篇文章的可以先浏览下,能够更快的了解脉络和源码结构,能够更快的进入主题,更快理解。
注解处理器(APT)
顾名思义,APT就是注解处理器,其是Annotation Processing Tool的简称。它是javac的一个工具,用来在编译期扫描和处理注解,通过注解来生成文件(通常是java文件)。即以注解作为桥梁,通过预先规定好的代码生成规则来自动生成 Java 文件。这些生成的java文件会同其手动编写的java代码一样会被javac编译。
简单的流程框图如下:即原来需要在程序运行时获取的方法集合,现在可以在编译时注解生成的新类A中的某个方法获取了。此种方式可以减轻初始化时反射带来的性能损耗,当然此损耗是在项目较大,注册较多时比较明显。
虽然流程图上画的是调用注解处理器,但是Android工程编译的时候JVM怎么找到我们自定义的注解处理器?这个时候就要用到Java SPI机制(这里只引出概念,有需要的可以一起查阅学习,深入研究原理,这里只当做黑箱,使用这个能力)。就是在annotationprocess模块的resources目录下新建META-INF/services,然后新建File,名称javax.annotation.processing.Processor,文件内容就是我们自定义注解处理器的全限定名com.example.AnnotationProcessor,谷歌官方也出品了一个开源库Auto-Service,通过注解@AutoService(Processor.class)可以省略上面配置的步骤。AutoService会自动在META-INF文件夹下生成Processor配置信息文件,该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
其中AbstractProcessor 的主要方法如下:
public abstract class AbstractProcessor implements Processor {
//获取要处理的注解的类型(本例中是@Subscribe)可通过注解方式赋值
public Set<String> getSupportedAnnotationTypes() {
SupportedAnnotationTypes sat = this.getClass().getAnnotation(SupportedAnnotationTypes.class);
……
}
// 获取java需要支持的版本,可注解方式赋值
public SourceVersion getSupportedSourceVersion() {
SupportedSourceVersion ssv = this.getClass().getAnnotation(SupportedSourceVersion.class);
……
}
// 初始化注解处理器
public synchronized void init(ProcessingEnvironment processingEnv) {
……
}
// 要实现的process方法,在实现类中此方法主要是生成新的类文件。
public abstract boolean process(Set<? extends TypeElement> annotations,RoundEnvironment roundEnv);
}
实际上,生成这个类文件A也有两种方式,一个就是用注解处理器自带的processingEnv里的生成文件的方法。另一种即是使用框架javapoet。通过javapoet这个名字翻译,java 诗人,你能想象写代码会有多优雅。
其中processingEnv自带的方式,也是EventBus3.0源码目前使用的方式生成代码示例为:
BufferedWriter writer = null;
try {
JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);
int period = index.lastIndexOf('.');
String myPackage = period > 0 ? index.substring(0, period) : null;
String clazz = index.substring(period + 1);
writer = new BufferedWriter(sourceFile.openWriter());
if (myPackage != null) {
writer.write("package " + myPackage + ";\n\n");
}
writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n");
writer.write("import org.greenrobot.eventbus.ThreadMode;\n\n");
writer.write("import java.util.HashMap;\n");
writer.write("import java.util.Map;\n\n");
writer.write("/** This class is generated by EventBus, do not edit. */\n");
writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n");
writer.write(" private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;\n\n");
writer.write(" static {\n");
writer.write(" SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();\n\n");
writeIndexLines(writer, myPackage)