EventBus3实例源码浅析(下)-索引类生成

概述

EventBus一般使用在调用register注册时,会通过反射去解析记录订阅方法,运行期反射比较耗费性能。3.0提供了高级用法即索引,通过注解处理器在编译期就提前解析记录订阅方法。EventBus在索引生成过程中有使用到TypeElementJavaFileObject等接口,需要先对这些接口有一定了解。

实例解析

使用索引详细的配置方法可以按照官方文档http://greenrobot.org/eventbus/documentation/subscriber-index/。

这里继续用上篇的例子:

public class MyEventSubscriber {

    private WeakReference<Activity> mActivity;

    public EventSubscriber(Activity activity) {
        this.mActivity = new WeakReference<>(activity);
    }

    public void register() {
        EventBus.getDefault().register(this);
    }

    public void unregister() {
        EventBus.getDefault().unregister(this);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMyEvent(MyEvent event) {
        //todo 处理事件
        ···
    }
}

在build.gradle中添加配置:

javaCompileOptions {
    annotationProcessorOptions {
        arguments = [ eventBusIndex : 'com.example.myapp.MyEventBusIndex' ]
    }
}

当编译完成后,会在build->generated->source->apt->debug/release下生成对应的索引类。本例中生成com.example.myapp包,包下有一个MyEventBusIndex类。

源码解析

EventBus注解处理依赖库就一个类EventBusAnnotationProcessor,打开源码看看它是如何生成索引类。

一.继承自AbstractProcessor

1.先来看该类注册的两个注解:

@SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe")
@SupportedOptions(value = {"eventBusIndex", "verbose"})
public class EventBusAnnotationProcessor extends AbstractProcessor {
	···
}
  • SupportedAnnotationTypes用于注册该注解处理器支持的注解,这里仅处理EventBus定义的Subscribe注解;
  • SupportedOptions用于注册支持的编译选项,即在本例中在build.gradle中配置的arguments = [ eventBusIndex : ‘com.example.myapp.MyEventBusIndex’ ],eventBusIndex的值对应’com.example.myapp.MyEventBusIndex’ ,verbose默认false。

2.重写部分方法:

@Override
public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latest();
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
	···
}
  • getSupportedSourceVersion返回支持的Java版本,这里返回最新;
  • process核心方法,编译过程中会回调该方法,在这里实现相应的方法。如果我们成功处理,返回true。否则返回false,会有后续的Processor去处理。

ps:根据观察日志发现,注解处理器会执行多轮,它还会处理生成的源文件中的注解或上一个Processor未处理的注解。

二.重要成员变量

先介绍下该类中的一些重要的成员变量的作用:

public class EventBusAnnotationProcessor extends AbstractProcessor {
	/** 记录订阅方法(即注册了@Subscribe的method),订阅者类(即method所在的类)为key。 */
	private final ListMap<TypeElement, ExecutableElement> methodsByClass = new ListMap<>();
    /** 记录不处理的订阅者类。 */
    private final Set<TypeElement> classesToSkip = new HashSet<>();
	/** 标记索引类是否生成完成。 */
    private boolean writerRoundDone;
    /** 标记第几轮执行process */
    private int round;
}

ps:ListMap是greenrobot对Map扩展的集合,对HashMap<K, List<V>>的封装。

三.核心方法process

直接看代码:

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
	// annotations为注册的注解的类型元素的集合,在本例中该集合仅有Subscribe。env用于获取注解处理环境信息
	// Messager用于打印日志
    Messager messager = processingEnv.getMessager();
    try {
    	// 获取在build.gradle中配置的eventBusIndex参数的值,本例即为com.example.myapp.MyEventBusIndex
        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参数值
        verbose = Boolean.parseBoolean(processingEnv.getOptions().get(OPTION_VERBOSE));
        int lastPeriod = index.lastIndexOf('.');
        // 截取生成索引类的目标包名,即com.example.myapp
        String indexPackage = lastPeriod != -1 ? index.substring(0, lastPeriod) : null;

		// 标记轮次增加
        round++;
        // verbose仅用于判断是否打印这段日志
        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.");
        }
        // 以上是检查完毕,下面开始进行解析记录
        // 记录合法的订阅方法,保存进methodsByClass集合---①
        collectSubscribers(annotations, env, messager);
        // 记录不处理的订阅方法---②
        checkForSubscribersToSkip(messager, indexPackage);

        if (!methodsByClass.isEmpty()) {
        	// 若存在合法的订阅方法,则开始生成索引类---③
            createInfoIndexFile(index);
        } else {
            messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
        }
        // 完成后标记writerRoundDone为true
        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);
    }
    // 最终返回true,表示已经正确处理
    return true;
}

process方法中先进行初步的合法性检查,之后分三步开始处理:1.解析出所有订阅方法;2.记录不处理的订阅方法;3.生成索引类。接下来逐步分析。

1.初步收集订阅方法

关键在collectSubscribers方法中:

private void collectSubscribers(Set<? extends TypeElement> annotations, RoundEnvironment env, Messager messager) {
	// 遍历该处理器注册的注解元素的集合
    for (TypeElement annotation : annotations) {
    	// 获取所有使用了该注解的元素集合,在这里即所有添加了@Subscribe的方法的元素
        Set<? extends Element> elements = env.getElementsAnnotatedWith(annotation);
        for (Element element : elements) {
            if (element instanceof ExecutableElement) {
            	// ExecutableElement表示可执行方法的元素,因为@Subscribe只能用在method上
                ExecutableElement method = (ExecutableElement) element;
                // 合法性校验
                if (checkHasNoErrors(method, messager)) {
                	// 获取ExecutableElement所在类的元素,即订阅方法所在的订阅者类
                    TypeElement classElement = (TypeElement) method.getEnclosingElement();
                    // 根据class作key、method作value缓存到集合中
                    methodsByClass.putElement(classElement, method);
                }
            } else {
                messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element);
            }
        }
    }
}

上面方法很简单,就是取得所有添加@Subscribe的method,将符合要求的method根据它所在的class归类缓存到methodsByClass中。

接着看下checkHasNoErrors方法,这里面如何对method进行校验:

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;
    }

	// 方法必须是public的
    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;
}

可以看出,我们创建的订阅方法必须符合public、非static、仅有一个参数,否则即使添加@Subscribe也是无效的。

2.筛选订阅方法

进入checkForSubscribersToSkip方法:

private void checkForSubscribersToSkip(Messager messager, String myPackage) {
	// 遍历集合key,依次检查订阅者类
    for (TypeElement skipCandidate : methodsByClass.keySet()) {
        TypeElement subscriberClass = skipCandidate;
        while (subscriberClass != null) {
        	// 判断订阅者类是否可访问
            if (!isVisible(myPackage, subscriberClass)) {
            	// 若该订阅者类对于即将生成的目标索引类不可访问,则添加进筛除集合,结束while循环,检查下一个订阅者类
                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);
                    // 参数必须是一个类的声明类型和类元素,即如本例定义的MyEvent实体类
                    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切换到父类,若父类是系统的类,则返回null,结束while循环
            subscriberClass = getSuperclass(subscriberClass);
        }
    }
}

该方法中依次遍历订阅者类及父类,校验订阅者类的可访问性和订阅方法参数的类型及访问性,若有不符合要求的就添加进筛除集合。

接着再详细看下校验访问性的方法isVisible:

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 {
    	// 若是默认则判断订阅者类所在包名和索引类所在包名是否一致
    	// getPackageElement方法中不断获取typeElement的顶层元素直至取到包元素,然后获取包名
        String subscriberPackage = getPackageElement(typeElement).getQualifiedName().toString();
        if (myPackage == null) {
            visible = subscriberPackage.length() == 0;
        } else {
            visible = myPackage.equals(subscriberPackage);
        }
    }
    return visible;
}

接着看获取参数类型的方法getParamTypeMirror:

private TypeMirror getParamTypeMirror(VariableElement param, Messager messager) {
    TypeMirror typeMirror = param.asType();
    // Check for generic type
    if (typeMirror instanceof TypeVariable) {
    	// 获取该类型变量的上边界,如果该对象有通过extends继承其他对象,则upperBound为DeclaredType类型
        TypeMirror upperBound = ((TypeVariable) typeMirror).getUpperBound();
        if (upperBound instanceof DeclaredType) {
            if (messager != null) {
                messager.printMessage(Diagnostic.Kind.NOTE, "Using upper bound type " + upperBound +
                        " for generic parameter", param);
            }
            // 替换参数类型为上边界的类型
            typeMirror = upperBound;
        }
    }
    return typeMirror;
}

经过筛选将不需要写入索引类的订阅者类存进classesToSkip集合中,后续生成的时候会比较这个集合中的类,判断是跳过还是处理。

3.生成索引类

如果methodsByClass中不为空,则调用createInfoIndexFile方法:

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
            }
        }
    }
}

writeIndexLines方法:

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方法中封装了缩进换行操作
            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方法中遍历methodsByClass,然后依次写入订阅方法相关信息。
先看里面的getClassString方法,该方法获取订阅者类的类名字符串:

private String getClassString(TypeElement typeElement, String myPackage) {
    PackageElement packageElement = getPackageElement(typeElement);
    // 获取订阅者类的包元素全限定名称
    String packageString = packageElement.getQualifiedName().toString();
    // 订阅者类全限定名称
    String className = typeElement.getQualifiedName().toString();
    if (packageString != null && !packageString.isEmpty()) {
    	// 比较包名
        if (packageString.equals(myPackage)) {
        	// 若包名一致,则只取订阅者类的simple名称
        	// cutPackage通过截取字符串方式,而不是调用getSimpleName,避免内部类的时候取到$
            className = cutPackage(myPackage, className);
        } else if (packageString.equals("java.lang")) {
        	// 若是java.lang包,则直接取SimpleName
            className = typeElement.getSimpleName().toString();
        }
    }
    return className;
}

再看写订阅方法相关信息writeCreateSubscriberMethods:

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() + 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() + ")");
        }

    }
}

以上就是生成索引类源文件,整个过程大致是从methodsByClass中获取订阅者类、方法相关信息,跳过不合法的订阅者类,然后获取类名、方法名、参数对象类字符串以及注解中的值,写入生成。

四.分析最终生成的源文件

以下是最终生成的源文件代码:

package com.example.myapp;

import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;
import org.greenrobot.eventbus.meta.SubscriberMethodInfo;
import org.greenrobot.eventbus.meta.SubscriberInfo;
import org.greenrobot.eventbus.meta.SubscriberInfoIndex;

import org.greenrobot.eventbus.ThreadMode;

import java.util.HashMap;
import java.util.Map;

/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(com.example.myapp.subscribe.MyEventSubscriber.class, true,
                new SubscriberMethodInfo[] {
                        new SubscriberMethodInfo("onMyEvent", com.example.myapp.model.event.MyEvent.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;
        }
    }
}

在静态代码块中,构造了SimpleSubscriberInfo对象,然后存进HashMap集合中。

回到EventBus3实例源码浅析(上),结合里面的源码来分析:

1)在初始化时通过EventBusBuilder的addIndex方法添加索引

/** Adds an index generated by EventBus' annotation preprocessor. */
public EventBusBuilder addIndex(SubscriberInfoIndex index) {
    if(subscriberInfoIndexes == null) {
        subscriberInfoIndexes = new ArrayList<>();
    }
    // 可添加多个索引,存入ArrayList集合中
    subscriberInfoIndexes.add(index);
    return this;
}

只需实例化索引类然后传入即可。

2)注册时会优先使用索引
在SubscriberMethodFinder的findUsingInfo方法中,会查找可用索引类,最终调用索引类的getSubscriberInfo方法获取SubscriberInfo对象。SubscriberInfo中包含订阅方法、参数、注解值等相关信息,因此省去了反射查找、校验的操作。

总结

索引将注册时的繁琐操作放在编译期完成,大大节省了事件,不过发送事件时仍需要反射。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章