Android-EventBus修改纪实(四)-注解处理器
持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 6 天,点击查看活动详情
前言
本文是 EventBus 修改纪实的第四篇,笔者在写第一篇文章时只是想记录下修改 EventBus 的过程,分享解决问题和查看源码的思路,没想到不知不觉会写这么多,“天下无不散之筵席”,本文将分析 EventBus 注解处理器的流程,也是 EventBus 修改纪实的最后一篇文章。
纪实
EventBus 的注解处理器程序只有一个类 EventBusAnnotationProcessor
:
1 2 3 4 5 6 @SupportedAnnotationTypes("com.yarward.org.greenrobot.eventbus.Subscribe") @SupportedOptions(value = {"eventBusIndex", "verbose"}) @IncrementalAnnotationProcessor(AGGREGATING) public class EventBusAnnotationProcessor extends AbstractProcessor { }
EventBusAnnotationProcessor
上有三个注解,下面一一介绍:
@SupportedAnnotationTypes
注解标识该注解处理器只处理 @Subscribe
注解,
@SupportedOptions
注解标识改注解处理器可以有两个参数,eventBusIndex
标识要生成 Index 类的全量限定名称,verbose
主要用于日志输出
@IncrementalAnnotationProcessor
注解方便构建增量注解处理器
process
接下来分析处理流程,处理流程主要在 process
方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public static final String OPTION_EVENT_BUS_INDEX = "eventBusIndex" ;public static final String OPTION_VERBOSE = "verbose" ;@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." ); } 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) { e.printStackTrace(); messager.printMessage(Diagnostic.Kind.ERROR, "Unexpected error in EventBusAnnotationProcessor: " + e); } return true ; }
collectSubscribers
我们首先分析是如何收集订阅者和订阅方法的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private final ListMap<TypeElement, ExecutableElement> methodsByClass = new ListMap <>();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); } } } }
首先遍历 @Subscribe
注解集合,通过 getElementsAnnotatedWith
获取有 @Subscribe
注解的元素集合,然后遍历元素集合,在遍历元素集合时判断元素是否是 ExecutableElement
,即当前元素是不是方法,如何是方法再通过 checkHasNoErrors
检查此方法是否符合订阅方法的规范:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private boolean checkHasNoErrors (ExecutableElement element, Messager messager) { if (element.getModifiers().contains(Modifier.STATIC)) { messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must not be static" , element); return false ; } if (!element.getModifiers().contains(Modifier.PUBLIC)) { messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must be public" , element); return false ; } List<? extends VariableElement > parameters = ((ExecutableElement) element).getParameters(); if (parameters.size() != 1 ) { messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must have exactly 1 parameter" , element); return false ; } return true ; }
检查方法比较简单,需要满足以下条件:
必须不能是静态方法
必须是公开方法
方法有且仅有一个入参
注意这里没有判断是否是桥接方法和合成方法,因为注解处理器处于编译期,桥接方法和合成方法应该经过 javac 编译后才会有。
最后把符合订阅方法规范的方法收集起来,存储在 methodsByClass
中,methodsByClass
是 greenrobot 自定义的,其实际类型是 Map<TypeElement, List<ExecutableElement>>
,以订阅者为 Key,订阅者中的订阅方法集合为 Value。
checkForSubscribersToSkip
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 private void checkForSubscribersToSkip (Messager messager, String myPackage) { for (TypeElement skipCandidate : methodsByClass.keySet()) { TypeElement subscriberClass = skipCandidate; while (subscriberClass != null ) { 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
方法判断订阅者是否可见:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private boolean isVisible (String myPackage, TypeElement typeElement) { Set<Modifier> modifiers = typeElement.getModifiers(); boolean visible; if (modifiers.contains(Modifier.PUBLIC)) { visible = true ; } else if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.PROTECTED)) { visible = false ; } else { String subscriberPackage = getPackageElement(typeElement).getQualifiedName().toString(); if (myPackage == null ) { visible = subscriberPackage.length() == 0 ; } else { visible = myPackage.equals(subscriberPackage); } } return visible; }
如果订阅者符合以下条件即认为可见:
订阅者含有 public 修饰符
Index 类包名为空时,订阅者包名长度为 0,
Index 类包名不为空,订阅者和 Index 类同属一个包下
如果订阅者不符合可见性条件,则添加进 classesToSkip
集合,后续生成 Index 类时会忽略此这些订阅者。
如果订阅者符合可见性条件,接下来判断订阅方法是否符合规范:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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 ; } }
遍历订阅方法,判断订阅方法的入参如果不是声明类型,即不是基本类型;或者入参是声明类型但不是类或接口,则将订阅者添加进跳过订阅者集合并退出 for 循环,否则再判断入参是否可见,若是不可见,则将订阅者添加进跳过订阅者集合并退出 for 循环。
最后获取当前订阅者的父类,循环判断父类是否也符合以上规范,若是父类不符合以上规范,则将当前订阅者添加进跳过订阅者集合并退出 for 循环。
createInfoIndexFile
收集和过滤完订阅者后,接下来就可以生成 Index 类了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 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 com.yarward.org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n" ); writer.write("import com.yarward.org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n" ); writer.write("import com.yarward.org.greenrobot.eventbus.meta.SubscriberInfo;\n" ); writer.write("import com.yarward.org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n" ); writer.write("import com.yarward.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) { } } } }
createInfoIndexFile
方法比较简单,主要是模板代码,生成 Index 的逻辑主要在 writeIndexLines
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private void writeIndexLines (BufferedWriter writer, String myPackage) throws IOException { for (TypeElement subscriberTypeElement : methodsByClass.keySet()) { if (classesToSkip.contains(subscriberTypeElement)) { continue ; } String subscriberClass = getClassString(subscriberTypeElement, myPackage); if (isVisible(myPackage, subscriberTypeElement)) { writeLine(writer, 2 , "putIndex(new SimpleSubscriberInfo(" + subscriberClass + ".class," , "true," , "new SubscriberMethodInfo[] {" ); List<ExecutableElement> methods = methodsByClass.get(subscriberTypeElement); writeCreateSubscriberMethods(writer, methods, "new SubscriberMethodInfo" , myPackage); writer.write(" }));\n\n" ); } else { writer.write(" // Subscriber not visible to index: " + subscriberClass + "\n" ); } } }
writeIndexLines
方法也比较简单,遍历之前收集的订阅者集合,首先判断是否忽略当前订阅者,随后获取订阅者类名,如果是内部类则包含外部类的类名,然后再次判断当前订阅者是否可见,如果可见则生成 SimpleSubscriberInfo
对象添加进 Index。
SimpleSubscriberInfo
是 Index 元素,其构造方法有三个入参,第一个入参是:订阅者 Class 对象,第二个入参是:是否检查父类,默认为 true,第三个参数是:订阅方法信息数组。
接下来调用 writeCreateSubscriberMethods
方法生成订阅方法信息 SubscriberMethodInfo
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 private void writeCreateSubscriberMethods (BufferedWriter writer, List<ExecutableElement> methods, String callPrefix, String myPackage) throws IOException { for (ExecutableElement method : methods) { List<? extends VariableElement > parameters = method.getParameters(); TypeMirror paramType = getParamTypeMirror(parameters.get(0 ), null ); TypeElement paramElement = (TypeElement) processingEnv.getTypeUtils().asElement(paramType); String methodName = method.getSimpleName().toString(); String eventClass = getClassString(paramElement, myPackage) + ".class" ; Subscribe subscribe = method.getAnnotation(Subscribe.class); List<String> parts = new ArrayList <>(); parts.add(callPrefix + "(\"" + methodName + "\"," ); String lineEnd = ")," ; if (subscribe.priority() == 0 && !subscribe.sticky()) { if (subscribe.threadMode() == ThreadMode.POSTING) { parts.add(eventClass + lineEnd); } else { parts.add(eventClass + "," ); parts.add("ThreadMode." + subscribe.threadMode().name() + lineEnd); } } else { parts.add(eventClass + "," ); parts.add("ThreadMode." + subscribe.threadMode().name() + "," ); parts.add(subscribe.priority() + "," ); parts.add(subscribe.sticky() + "," ); parts.add(subscribe.rendezvous() + lineEnd); } writeLine(writer, 3 , parts.toArray(new String [parts.size()])); if (verbose) { processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Indexed @Subscribe at " + method.getEnclosingElement().getSimpleName() + "." + methodName + "(" + paramElement.getSimpleName() + ")" ); } } }
遍历当前订阅者中所有的订阅方法,首先订阅方法的名称,入参 Event 事件的 Class 对象,然后获取 @Subscribe
注解的参数,并根据注解的参数生成 SubscriberMethodInfo
的入参,这里有笔者对必达事件的处理(注意:这里处理有Bug,不知读者有没有看出来呢?) ,最后调用 writeLine
完整的生成 SubscriberMethodInfo
方法。
至此,EventBus 注解处理器的流程分析完毕,下面是生成的 Index 类示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class EventBusTestsIndex implements SubscriberInfoIndex { private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX; static { SUBSCRIBER_INDEX = new HashMap <Class<?>, SubscriberInfo>(); putIndex(new SimpleSubscriberInfo (EventBusMainThreadTest.class, true , new SubscriberMethodInfo [] { new SubscriberMethodInfo ("onEventMainThread" , String.class, ThreadMode.MAIN), })); } private static void putIndex (SubscriberInfo info) { SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info); } @Override public SubscriberInfo getSubscriberInfo (Class<?> subscriberClass) { SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass); if (info != null ) { return info; } else { return null ; } } }
总结
本文分析订阅者是否符合规范时,有一处根据订阅方法检查过滤订阅者,如果订阅者中有一个方法不符合规范,整个订阅者都要跳过,这里你认为是否合理呢?
大家求同存异,欢迎交流。
happy~,希望可以帮你更好的使用 EventBus