解析EventBusAnnotationProcessor
前面我们已经知道为了提升性能,EventBus3.0以前使用的是运行时注解,由于Java的反射机制非常耗费性能,因此,从3.0以后使用编译时注解,编译时利用EventBusAnnotationProcessor注解处理器获取@Subscribe所包含的信息,生成索引类来保存订阅者以及订阅的相关性信息。下面我们会分析一下EventBusAnnotationProcessor的源码,搞清楚如何通过注解器获取订阅的信息。
抽象处理器 AbstractProcessor
-
注解处理器是一个在javac中的,用来编译时扫描和处理注解的工具。你可以为特定的注解,注册你自己的注解处理器。
-
注解处理器可以生成Java代码,这些生成的Java代码会组成 .java 文件,但不能修改已经存在的Java类(即不能向已有的类中添加方法)。而这些生成的Java文件,会同时与其他普通的手写Java源代码一起被javac编译。
详细可见Android编译时注解APT实战(AbstractProcessor),后面我会花时间把利用注解动态生成代码的原理和流程讲明白。
下面我们主要分析一下EventBusAnnotationProcessor三个主要的方法:
1、process(Set<?extendsTypeElement> annotations, RoundEnvironment env)
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
Messager messager = processingEnv.getMessager();
try {
String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);
if (index == null) {
messager.printMessage(Diagnostic.Kind.ERROR, "No option " + OPTION_EVENT_BUS_INDEX +
" passed to annotation processor");
return false;
}
verbose = Boolean.parseBoolean(processingEnv.getOptions().get(OPTION_VERBOSE));
int lastPeriod = index.lastIndexOf('.');
String indexPackage = lastPeriod != -1 ? index.substring(0, lastPeriod) : null;
round++;
if (verbose) {
messager.printMessage(Diagnostic.Kind.NOTE, "Processing round " + round + ", new annotations: " +
!annotations.isEmpty() + ", processingOver: " + env.processingOver());
}
if (env.processingOver()) {
if (!annotations.isEmpty()) {
messager.printMessage(Diagnostic.Kind.ERROR,
"Unexpected processing state: annotations still available after processing over");
return false;
}
}
if (annotations.isEmpty()) {
return false;
}
if (writerRoundDone) {
messager.printMessage(Diagnostic.Kind.ERROR,
"Unexpected processing state: annotations still available after writing.");
}
//主要的逻辑从这里开始
//根据 Subscribe 注解得到对应的所有订阅方法
collectSubscribers(annotations, env, messager);
// 检查这些订阅方法,过滤掉不符合要求的
checkForSubscribersToSkip(messager, indexPackage);
if (!methodsByClass.isEmpty()) {
//生成索引类
createInfoIndexFile(index);
} else {
messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
}
writerRoundDone = true;
} catch (RuntimeException e) {
// IntelliJ does not handle exceptions nicely, so log and print a message
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.ERROR, "Unexpected error in EventBusAnnotationProcessor: " + e);
}
return true;
}
上面这段代码主要逻辑就是我们注释的地方:
- 获取订阅方法
- 检查订阅方法
- 生成索引类
先分析如何生成订阅方法: collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager)
private void collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager) {
//遍历注解
for (TypeElement annotation : annotations) {
Set<? extends Element> elements = env.getElementsAnnotatedWith(annotation);
//遍历声明注解的参数类型
for (Element element : elements) {
if (element instanceof ExecutableElement) {
//获取订阅方法
ExecutableElement method = (ExecutableElement) element;
if (checkHasNoErrors(method, messager)) {
//缓存订阅方法
TypeElement classElement = (TypeElement) method.getEnclosingElement();
methodsByClass.putElement(classElement, method);
}
} else {
messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element);
}
}
}
}
获取后订阅方法后,我们继续解析流程的第二步检查过滤订阅方法:checkForSubscribersToSkip(Messager messager, String myPackage)
/**
* Subscriber classes should be skipped if their class or any involved event class are not visible to the index.
*/
private void checkForSubscribersToSkip(Messager messager, String myPackage) {
//遍历获取到的订阅方法
for (TypeElement skipCandidate : methodsByClass.keySet()) {
TypeElement subscriberClass = skipCandidate;
while (subscriberClass != null) {
//如果不可见即访问不了,则添加到classesToSkip,过滤这个方法,
//下面有分析isVisible(myPackage, subscriberClass)的具体实现
if (!isVisible(myPackage, subscriberClass)) {
boolean added = classesToSkip.add(skipCandidate);
if (added) {
String msg;
if (subscriberClass.equals(skipCandidate)) {
msg = "Falling back to reflection because class is not public";
} else {
msg = "Falling back to reflection because " + skipCandidate +
" has a non-public super class";
}
messager.printMessage(Diagnostic.Kind.NOTE, msg, subscriberClass);
}
break;
}
List<ExecutableElement> methods = methodsByClass.get(subscriberClass);
if (methods != null) {
// 检查过滤,生成符合要求的订阅方法
for (ExecutableElement method : methods) {
String skipReason = null;
VariableElement param = method.getParameters().get(0);
TypeMirror typeMirror = getParamTypeMirror(param, messager);
if (!(typeMirror instanceof DeclaredType) ||
!(((DeclaredType) typeMirror).asElement() instanceof TypeElement)) {
skipReason = "event type cannot be processed";
}
if (skipReason == null) {
TypeElement eventTypeElement = (TypeElement) ((DeclaredType) typeMirror).asElement();
if (!isVisible(myPackage, eventTypeElement)) {
skipReason = "event type is not public";
}
}
if (skipReason != null) {
boolean added = classesToSkip.add(skipCandidate);
if (added) {
String msg = "Falling back to reflection because " + skipReason;
if (!subscriberClass.equals(skipCandidate)) {
msg += " (found in super class for " + skipCandidate + ")";
}
messager.printMessage(Diagnostic.Kind.NOTE, msg, param);
}
break;
}
}
}
subscriberClass = getSuperclass(subscriberClass);
}
}
}
解析上面涉及的isVisible(String myPackage, TypeElement typeElement)
private boolean isVisible(String myPackage, TypeElement typeElement) {
Set<Modifier> modifiers = typeElement.getModifiers();
boolean visible;
//访问修饰符是public,则返回true
if (modifiers.contains(Modifier.PUBLIC)) {
visible = true;
} else if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.PROTECTED)) {
//访问修饰符是private或者protected,则返回false
visible = false;
} else {
//访问修饰符是默认的且订阅方法和索引类在同一个包则返回true,否则返回false
String subscriberPackage = getPackageElement(typeElement).getQualifiedName().toString();
if (myPackage == null) {
visible = subscriberPackage.length() == 0;
} else {
visible = myPackage.equals(subscriberPackage);
}
}
return visible;
}
到了最后一步,生成索引类createInfoIndexFile(String index)
其实,下面的代码不用怎么解析了,一看就知道是通过写死的模板代码生成一个java文件,最后生成我们想要的索引类:
private void createInfoIndexFile(String index) {
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);
writer.write(" }\n\n");
writer.write(" private static void putIndex(SubscriberInfo info) {\n");
writer.write(" SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n");
writer.write(" }\n\n");
writer.write(" @Override\n");
writer.write(" public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n");
writer.write(" SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n");
writer.write(" if (info != null) {\n");
writer.write(" return info;\n");
writer.write(" } else {\n");
writer.write(" return null;\n");
writer.write(" }\n");
writer.write(" }\n");
writer.write("}\n");
} catch (IOException e) {
throw new RuntimeException("Could not write source for " + index, e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
//Silent
}
}
}
}
Eventbus的编译时注解机制,解决了3.0以前使用反射导致的性能问题,这个优化流程我们大致已经基本弄清楚了,后面我会更深入解析通过注解器获取订阅信息的原理,我们都知道的ButterKnife和Dagger框架都使用了这种注解思路,掌握后有利于我们对很多框架实现的理解,最重要的是在我们实际项目中增加了一种新的问题解决思路。