网上关于EventBus的分析已经很多,尤其是EventBus的订阅以及事件发送、接收相关的内容。这里不分析该部分的内容,仅分析一下索引文件是如何生成的。
ps:本文源码分析基于EventBus 3.3.1
涉及到的类
使用方法
// build.gradle
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
// 配置索引文件名
arguments = [eventBusIndex: 'com.xxx.yyy.HelloEventBusIndex']
}
}
}
}
// xxx.java
EventBus.builder().addIndex(new com.xxx.yyy.HelloEventBusIndex()).build();
或
EventBus.builder().addIndex(new com.xxx.yyy.HelloEventBusIndex()).installDefaultEventBus();
索引文件的生成
// 此注解处理器支持的注解类型是"org.greenrobot.eventbus.Subscribe"
@SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe”)
// 此注解处理器支持的参数
@SupportedOptions(value = {"eventBusIndex", "verbose”})
// 用于构建增量注解处理器的注解,帮助我们生成了 /META-INF/gradle/注解 目录和文件的功能,相关可参考https://github.com/tbroyer/gradle-incap-helper
@IncrementalAnnotationProcessor(AGGREGATING)
public class EventBusAnnotationProcessor extends AbstractProcessor {
// build.gradle中eventBus配置的arguments的key
// eventBus索引文件的key,通知该key可获取对应的索引文件名(全限定名,即包含包路径),该索引文件是SubscriberInfoIndex的子类
public static final String OPTION_EVENT_BUS_INDEX = "eventBusIndex”;
// 是否通过Messager打印相关日志,后面日志相关的代码省略
public static final String OPTION_VERBOSE = "verbose”;
// 获取订阅类的所有订阅方法(不包含其父类),key是订阅类对应的TypeElement,value是订阅类的所有订阅方法对应的ExecutableElement的集合
private final ListMap<TypeElement, ExecutableElement> methodsByClass = new ListMap<>();
// 无效订阅类对应的TypeElement集合
private final Set<TypeElement> classesToSkip = new HashSet<>();
@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) {
// build.gradle中没有设置eventBusIndex
return false;
}
verbose = Boolean.parseBoolean(processingEnv.getOptions().get(OPTION_VERBOSE));
int lastPeriod = index.lastIndexOf('.’);
// 索引文件的包名
String indexPackage = lastPeriod != -1 ? index.substring(0, lastPeriod) : null;
round++;
// 省略一些异常情况…
// 收集订阅类和方法
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) {
// ....
}
return true;
}
// 收集订阅类和方法
private void collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager) {
for (TypeElement annotation : annotations) {
// 1.获取被@Subscribe修饰的元素
Set<? extends Element> elements = env.getElementsAnnotatedWith(annotation);
for (Element element : elements) {
// 2.如果该元素是方法(ExecutableElement表示类或者接口中的方法,构造函数或者初始化器)
if (element instanceof ExecutableElement) {
ExecutableElement method = (ExecutableElement) element;
// 3.如果方法是public 且 不是static 且 方法参数只有一个,则是有效方法
if (checkHasNoErrors(method, messager)) {
// Element.getEnclosingElement()获取Element的父元素
// 4.获取method元素的父元素,即类(TypeElement)
TypeElement classElement = (TypeElement) method.getEnclosingElement();
// 5. 将订阅方法对应的元素放入对应的集合中
methodsByClass.putElement(classElement, method);
}
}
}
}
}
// 检测方法是否合法
private boolean checkHasNoErrors(ExecutableElement element, Messager messager) {
// 1.订阅方法是否static
if (element.getModifiers().contains(Modifier.STATIC)) {
return false;
}
// 2.订阅方法是否public
if (!element.getModifiers().contains(Modifier.PUBLIC)) {
return false;
}
// 3.订阅方法参数是否只有一个
List<? extends VariableElement> parameters = ((ExecutableElement) element).getParameters();
if (parameters.size() != 1) {
return false;
}
return true;
}
// 检查无效的订阅类和方法
private void checkForSubscribersToSkip(Messager messager, String myPackage) {
// 1.遍历订阅类
for (TypeElement skipCandidate : methodsByClass.keySet()) {
TypeElement subscriberClass = skipCandidate;
while (subscriberClass != null) {
// 2.如果订阅类对于索引类不可见,则添加到classesToSkip集合中
// 无效的条件:如果订阅类及其父类不是puhlic的 或 订阅类及其父类的包名和eventBusIndex的包名不一样,则该订阅类无效,需要忽略
if (!isVisible(myPackage, subscriberClass)) {
boolean added = classesToSkip.add(skipCandidate);
// ...
break;
}
List<ExecutableElement> methods = methodsByClass.get(subscriberClass);
if (methods != null) {
// 3.遍历订阅方法,如果方法参数不合法则添加到classesToSkip集合中
for (ExecutableElement method : methods) {
String skipReason = null;
VariableElement param = method.getParameters().get(0);
// 获取参数类型
TypeMirror typeMirror = getParamTypeMirror(param, messager);
// 3.1 如果方法参数类型不是一个类或接口类型,则无法解析处理,则该订阅类无效,需要忽略
if (!(typeMirror instanceof DeclaredType) ||
!(((DeclaredType) typeMirror).asElement() instanceof TypeElement)) {
skipReason = "event type cannot be processed";
}
if (skipReason == null) {
TypeElement eventTypeElement = (TypeElement) ((DeclaredType) typeMirror).asElement();
// 3.2 如果方法参数对应的类及其父类不是public的 或 其包名和eventBusIndex的包名不一样,则该订阅类无效,需要忽略
if (!isVisible(myPackage, eventTypeElement)) {
skipReason = "event type is not public";
}
}
if (skipReason != null) {
boolean added = classesToSkip.add(skipCandidate);
// ...
break;
}
}
}
// 4.获取父类,接着遍历父类的订阅方法
subscriberClass = getSuperclass(subscriberClass);
}
}
}
// 判断typeElement对应的类对于索引类是否可见
private boolean isVisible(String myPackage, TypeElement typeElement) {
// 获取类的修饰符
Set<Modifier> modifiers = typeElement.getModifiers();
boolean visible;
if (modifiers.contains(Modifier.PUBLIC)) {
// 如果类是public的,则对于索引类是可见的
visible = true;
} else if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.PROTECTED)) {
// 如果类是private或protected的,则对于索引类是不可见的
visible = false;
} else {
// 如果类的包名和索引类的包名一样,即在同一个包目录下,则对于索引类是可见的
String subscriberPackage = getPackageElement(typeElement).getQualifiedName().toString();
if (myPackage == null) {
visible = subscriberPackage.length() == 0;
} else {
visible = myPackage.equals(subscriberPackage);
}
}
return visible;
}
// 获取父类元素
private TypeElement getSuperclass(TypeElement type) {
// TypeElement.getSuperclass() 返回此类型元素的直接超类。如果此类型元素表示一个接口或者类java.lang.Object,则返回一个种类为 NONE 的 NoType
// TypeKind.DECLARED表示一个类或接口类型
// 如果type的直接超类是一个类或接口,且不是java.或javax.或android.包下的系统类,则返回它的直接超类
if (type.getSuperclass().getKind() == TypeKind.DECLARED) {
TypeElement superclass = (TypeElement) processingEnv.getTypeUtils().asElement(type.getSuperclass());
String name = superclass.getQualifiedName().toString();
if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
// Skip system classes, this just degrades performance
return null;
} else {
return superclass;
}
} else {
return null;
}
}
// 创建索引文件并输出内容
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
}
}
}
}
// 将订阅方法输出到索引文件
private void writeIndexLines(BufferedWriter writer, String myPackage) throws IOException {
// 1.遍历订阅类
for (TypeElement subscriberTypeElement : methodsByClass.keySet()) {
// 2.如果是无效订阅类(例如订阅类或订阅方法参数对应的类对于索引文件不可见),则跳过该类的订阅方法
if (classesToSkip.contains(subscriberTypeElement)) {
continue;
}
// 2.获取订阅类的全限定名(即 包名.类名 )
String subscriberClass = getClassString(subscriberTypeElement, myPackage);
// 3.如果订阅类对应索引类可见,则写入内容到索引文件
if (isVisible(myPackage, subscriberTypeElement)) {
writeLine(writer, 2,
"putIndex(new SimpleSubscriberInfo(" + subscriberClass + ".class,",
"true,", "new SubscriberMethodInfo[] {");
List<ExecutableElement> methods = methodsByClass.get(subscriberTypeElement);
// 4.写入SubscriberMethodInfo到索引类中,省略该部分内容,主要就是写入订阅方法名,参数类型,ThreadMode,priority、sticky。
writeCreateSubscriberMethods(writer, methods, "new SubscriberMethodInfo", myPackage);
writer.write(" }));\n\n");
} else {
writer.write(" // Subscriber not visible to index: " + subscriberClass + "\n");
}
}
}
}
总结:
- eventBus注解处理器只收集被@Subscribe修饰的、public的、非static的、只有一个参数的方法
- 如果订阅类及其父类对于索引类不可见,则忽略该订阅类
- 如果订阅方法的参数类型不是一个类或接口类型,则无法解析处理,忽略该订阅类
- 如果订阅方法的参数对应的类及其父类对于索引类不可见,则忽略该订阅类
ps:
关于编译时注解的一些参考资料: