美文网首页DevSupport
解析EventBusAnnotationProcessor

解析EventBusAnnotationProcessor

作者: LouisXWB | 来源:发表于2019-03-17 23:49 被阅读0次

    前面我们已经知道为了提升性能,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框架都使用了这种注解思路,掌握后有利于我们对很多框架实现的理解,最重要的是在我们实际项目中增加了一种新的问题解决思路。

    相关文章

      网友评论

        本文标题:解析EventBusAnnotationProcessor

        本文链接:https://www.haomeiwen.com/subject/eriumqtx.html