EventBus源码分析(二)

EventBus源码分析(二)

在之前的一篇文章EventBus源码分析(一)分析了EventBus关于注册注销以及事件分发的机制,而关于注册就是当一个类注册监听EventBus发出的事件时,EventBus负责找出该类所有的监听事件的方法并保存在EventBus内部的相应数据结构中,具体地讲就是subscriptionsByEventType, typesBySubscriber, stickyEvents,三个Map的数据结构中。从EventBus 3.0开始,订阅方法使用注解@Subscribe标注,我们都知道对于注解的处理需要使用注解处理器,最简答的方法就是反射的方式,在运行期获取订阅方法信息,但是这样会影响性能。另一方面,Java提供了apt工具可以对Java源码在编译期做处理,从而避免了对于运行期性能的影响,这不过这种方式稍微有些复杂,而且会生成代码,尤其是对于Android系统,应用方法数是受限的,生成的代码会增长应用的方法数,所以两种方式各有利弊。EventBus也提供了两种方式用于查找订阅方法信息,下面分为两部分介绍。

1. 总述

在上一篇文章中介绍EventBus源码中提到订阅方法的查找是使用SubscriberMethodFinder的List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass)方法,从方法签名中可以看出,其功能就是注册类,即Subscriber在注册时找出该类中所有的订阅方法,然后返回包含订阅方法信息的列表。下面直接看该方法的源码:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    //缓存处理
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }

    //根据开关分情况处理
    if (ignoreGeneratedIndex) {
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    if (subscriberMethods.isEmpty()) {
    } else {
        //处理缓存
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

其中ignoreGeneratedIndex是一个boolean值的开关,由SubscriberMethodFinder构造器传递进来的参数决定,根据其值决定是使用反射还是适应index文件做订阅方法的处理。另外METHOD_CACHE是一个缓存,Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();是一个线程安全的Map, 用来存储已经查找到的某些类的订阅方法信息,在重复订阅时可以提高性能。

在介绍两种方式查找订阅方法之前,首先需要了解一个内部类,类似于事件分发中PostingThreadState用于记录线程分发事件的状态,在查找订阅方法过程中也有一个FindState用于记录查找的状态,我们首先看其内部的属性变量:

static class FindState {
    final List<SubscriberMethod> subscriberMethods = new ArrayList<>();   //记录查找得到的订阅方法信息
    final Map<Class, Object> anyMethodByEventType = new HashMap<>();
    final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
    final StringBuilder methodKeyBuilder = new StringBuilder(128);

    Class<?> subscriberClass;  //订阅者类的class对象
    Class<?> clazz;  //保存class对象信息的临时成员变量,用于存放订阅者类的父类的class对象
    boolean skipSuperClasses;  //是否查找父类中的订阅方法
    SubscriberInfo subscriberInfo;  //订阅者的信息,在非反射的方式中会用到,后面再做解释

属性变量中包含一些存储信息的数据结构,已经用注释说明,其中anyMethodByEventType,subscriberClassByMethodKey,methodKeyBuilder,是用在当一个订阅者类中同时有多个方法订阅同一个事件类型时用到,稍后做出解释。下面我们看FindState中的一些重要方法:

void initForSubscriber(Class<?> subscriberClass) {
    this.subscriberClass = clazz = subscriberClass;
    skipSuperClasses = false;
    subscriberInfo = null;
}

void recycle() {
    subscriberMethods.clear();
    anyMethodByEventType.clear();
    subscriberClassByMethodKey.clear();
    methodKeyBuilder.setLength(0);
    subscriberClass = null;
    clazz = null;
    skipSuperClasses = false;
    subscriberInfo = null;
}

这两个方法不需要解释,简单的初始化以及清理回收。下面为两个重要方法,检查一个订阅方法是否可以加入到订阅方法信息中,即检查@Subscribe标注的方法是否正确,其代码为:

boolean checkAdd(Method method, Class<?> eventType) {
    // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
    // Usually a subscriber doesn't have methods listening to the same event type.
    Object existing = anyMethodByEventType.put(eventType, method);
    if (existing == null) {
        return true;
    } else {
        if (existing instanceof Method) {
            if (!checkAddWithMethodSignature((Method) existing, eventType)) {
                // Paranoia check
                throw new IllegalStateException();
            }
            // Put any non-Method object to "consume" the existing Method
            anyMethodByEventType.put(eventType, this);
        }
        return checkAddWithMethodSignature(method, eventType);
    }
}

private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
    methodKeyBuilder.setLength(0);
    methodKeyBuilder.append(method.getName());
    methodKeyBuilder.append('>').append(eventType.getName());

    String methodKey = methodKeyBuilder.toString();
    Class<?> methodClass = method.getDeclaringClass();
    Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
    if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
        // Only add if not already found in a sub class
        return true;
    } else {
        // Revert the put, old class is further down the class hierarchy
        subscriberClassByMethodKey.put(methodKey, methodClassOld);
        return false;
    }
}

正如刚开始的注释所言,通常一个类中只有一个方法监听一个事件,这个很普遍验证方式也很简单,只会走第一个if语句,然后返回的true,此时anyMethodByEventType保存了事件类型和其对应的订阅方法。复杂出现在同一个类中有多个方法监听同一个事件类型,当监听一个事件的第二个方法进来检查的时候,existing就是第一个存进去的方法,这时候就要启动二级检查,即checkAddWithMethodSignature,如其名使用方法签名检查。在第二个方法检查之前需要首先检查第一个存进去的方法,因为需要将所有的方法签名存起来用于比较。执行完这一步之后,anyMethodByEventType中该事件类型对应的值改成了该FindState对象,不再是method,所以当第三个第四个方法进来检查时无需检查之前的方法,新进来的方法直接进入方法签名的检查。
下面看checkAddWithMethodSignature,首先是methodKeyBuilder构造一个方法签名,构造方式很简单,我们只需要知道字符串可以唯一标识一个方法即可,然后是获取该方法所属的类,注意getDeclaringClass()是获取该方法所属的类,与继承关系无关,比如A类声明方法a(), B 继承A 并覆写a()方法,如果是在B注册时查找方法信息,此时的method对象所属的类是B,而不是A。最后就是以方法签名为键值,类的class对象为值存到subscriberClassByMethodKey数据结构中,并检查该方法是否可以存储到订阅方法信息中,检查的逻辑,同样是方法签名不同时也很简单,methodClassOld为空,直接允许并保存信息,如果methodClassOld不为空,那么methodClassOld与methodClass肯定是不同的类,否则编译会有错误。那么在一个类注册的时候EventBus只会遍历它和它的父类,即methodClass是methodClassOld的父类,此时返回false,并且subscriberClassByMethodKey中该方法签名对应的值还是保存methodClassOld,即保持其处于继承关系的最下层,这个也符合情理,在子类注册监听事件时,父类中相同的方法不应该被调用,而是应该调用子类的覆写方法,之所以遍历父类是考虑父类中注册的其他事件,因为父类监听的事件,子类应当是继承的。但是这里有一点疑问,如果methodClass是methodClassOld的子类,我们很快会看到FindState中只有moveToSuperclass方法,后进来的class对象只可能是父类,不可能出现子类,这里添加methodClassOld.isAssignableFrom(methodClass)这个条件是为了逻辑的完备性还是为了其他我还没有相当的情况,对此还不清楚,欢迎熟悉这块的读者在评论中告知。

最后FindState中还有刚刚提到的moveToSuperclass方法,代码如下:

void moveToSuperclass() {
    if (skipSuperClasses) {
        clazz = null;
    } else {
        clazz = clazz.getSuperclass();
        String clazzName = clazz.getName();
        /** Skip system classes, this just degrades performance. */
        if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.")) {
            clazz = null;
        }
    }
}

在一次注册过程中,查找订阅信息始终保持一个FindState对象,clazz作为临时变量始终是当前所查询的subscriberClass的本身或某一级父类。这里跳过了Java系统的类以及Android系统的类,因为这些类我们是不可更改的,肯定不会包含订阅方法的。

FindState是一个很重要的数据结构,存储在查找过程的一些状态以及查询结果信息,这里介绍这个内部类,刚开始可能会不太明白,不过可以先看下面的反射方式的查找过程,再回头看此内部类,或许会容易理解一些。FindState到此已经介绍完了,下面分为反射和非反射两种方式介绍订阅方法查找的过程。

2. 反射方式

在运行期使用反射方式特点虽然会影响运行时的性能。但是其过程比较简单,所以首先介绍反射方式的查找过程,其代码如下:

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);  //初始化FindState对象
    while (findState.clazz != null) {   //遍历subscriberClass以及其父类的class对象
        findUsingReflectionInSingleClass(findState);
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);  //根据findState对象返回查找结果
}


private FindState prepareFindState() {  //FIND_STATE_POOL这里使用缓存,避免了对象的创建与销毁
    synchronized (FIND_STATE_POOL) {
        for (int i = 0; i < POOL_SIZE; i++) {
            FindState state = FIND_STATE_POOL[i];
            if (state != null) {
                FIND_STATE_POOL[i] = null;
                return state;
            }
        }
    }
    return new FindState();
}

findUsingReflection方法的逻辑很简单,已经在注释中说明,不过值得注意的是prepareFindState方法中,使用缓存池保存FindState对象,避免了对象的创建和销毁,从而避免引发内存抖动。下面重点分析findUsingReflectionInSingleClass方法,其代码为:

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    //1. 获取SubScriber中的所有方法
    try {
        // This is faster than getMethods, especially when subscribers are fat classes like Activities
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
        methods = findState.clazz.getMethods();
        findState.skipSuperClasses = true;  //由于对反射不是很熟悉,这里为什么还不明白,之后会看issues/149
    }
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {//这里可以暂且理解为所有公有非抽象方法
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 1) {    //订阅方法必须是只有一个参数
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    Class<?> eventType = parameterTypes[0];
                    if (findState.checkAdd(method, eventType)) {  //检查该订阅方法是否正确,检查规则第一部分已经说明
                        ThreadMode threadMode = subscribeAnnotation.threadMode();  
                        //创建SubscriberMethod(该类已经在第一篇文章中介绍)对象,并添加到FindState的subscriberMethods中
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {

                //strictMethodVerification是构造器传递进来的开关参数,只有为true时,才会向用户通知方法的异常
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException("@Subscribe method " + methodName +
                        "must have exactly 1 parameter but has " + parameterTypes.length);
            }
        } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(methodName +
                    " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
        }
    }
}

该方法的逻辑也不复杂,关键步骤在注释中已经说明,无非是一些检查条件,公有非抽象非静态方法而且必须是只带有有个参数的方法,此时可以再回去看看checkAdd以及checkAddWithSignature,再思考一下一个类的订阅方法覆写父类的方法时会出现一些较为复杂的情况。此外,MODIFIERS_IGNORE

/*
 * In newer class files, compilers may add methods. Those are called bridge or synthetic methods.
 * EventBus must ignore both. There modifiers are not public but defined in the Java class file format:
 * http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.6-200-A.1
 */
 private static final int BRIDGE = 0x40;
 private static final int SYNTHETIC = 0x1000;

 private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;

表示非抽象非静态,至于BRIDGE和SYNTHETIC是EventBus中自己定义的类型,表示编译器添加的方法,对Java虚拟机不是很熟悉,对于编译器添加的方法也不太明白,这里只需要理解为我们手动定义的,公有非静态非抽象方法即可。

最后看一下在查找过程结束以后,从FindState对象中返回查找信息的方法:

private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
    List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
    findState.recycle();
    synchronized (FIND_STATE_POOL) {
        for (int i = 0; i < POOL_SIZE; i++) {
            if (FIND_STATE_POOL[i] == null) {
                FIND_STATE_POOL[i] = findState;
                break;
            }
        }
    }
    return subscriberMethods;
}

这里需要注意的是使用ArrayList包装findState.subscriberMethods,是为了深拷贝,如果是直接赋值,在recycle之后,返回信息也会被清空,这一点大家都知道,却很容易忽略。另外,这里值得学习的是与刚开始对应的,在recycle之后将对象保存到缓存池中,重复利用。

至此,反射方式的查找过程就介绍完了,这种方式比较简单,重点是findUsingReflectionInSingleClass方法,这种方式简单却影响运行时的性能,与Java中编译期处理源码的方式一样,EventBus也提供了index方法,在编译期处理源码,生成有效的查找方法代码,避免了运行期的反射。下面介绍index方式的查询过程。

3. index 方式

既然不是在运行期使用反射技术查询方法信息,那么肯定需要在编译期对源码做处理,EventBus使用了自定义的Subscribe注解,那么肯定需要自己实现注解处理器,所以首先来看注解处理器的实现。
注解处理器,以及其process方法的主要代码为:

@SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe")
@SupportedOptions(value = {"eventBusIndex", "verbose"})
public class EventBusAnnotationProcessor extends AbstractProcessor {
    public static final String OPTION_EVENT_BUS_INDEX = "eventBusIndex";
    public static final String OPTION_VERBOSE = "verbose";

    /** Found subscriber methods for a class (without superclasses). */
    private final ListMap<TypeElement, ExecutableElement> methodsByClass = new ListMap<>();  //记录订阅方法信息
    private final Set<TypeElement> classesToSkip = new HashSet<>();   //记录需要跳过的subscriber

    private boolean writerRoundDone;
    private int round;
    private boolean 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);
            ...
            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);//收集订阅方法信息保存在methodsByClass
            checkForSubscribersToSkip(messager, indexPackage);//检查需要跳过的类,即subscriber, 保存在classesToSkip

            if (!methodsByClass.isEmpty()) {
                createInfoIndexFile(index);  //创建生成的Java代码文件
            } else {
                messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
            }
           ...
        } catch (RuntimeException e) {
            ...
        }
        return true;
    }

    ....
}

这里读代码做了简化,出去了一些错误的处理以及错误信息的输出,主要是由于对注解处理器不太熟悉,一些逻辑不明白其中的道理,为了避免分析出错,贻笑大方,所以这里只简单介绍其中的流程,有兴趣的可以自行查看源码。
这里首先获取参数中的index,它是要生成类的全称,从中获取包名indexPackage, round++则是递归地处理源码中的注解,即生成的源码中如果还有注解则递归处理,接下来就是重要的三个方法,如代码中添加的注释,分别负责收集订阅方法信息,检查需要跳过的类,最后生成Java代码文件。

下面首先看collectSubscribers,其代码为:

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注解只是标注方法,所以只需要处理ExecutableElement,然后检查方法的正确性,即checkHasNoErrors,这里不再贴出源码,其逻辑就是检查是否为公有非静态非抽象的方法,这个与之前所说的一致。方法正确,则将该方法所属的类和该方法保存到methodByClass中,这里是ListMap, 所以键值是表示该类的TypeElement, 值则为ExecutableElement的方法的List。
下面是

/**
 * 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) {

    //遍历检查所有添加到methodsByClass中的类
    for (TypeElement skipCandidate : methodsByClass.keySet()) {
        TypeElement subscriberClass = skipCandidate;
        while (subscriberClass != null) {
            // 检查类是否为public,非公有则加入到classesToSkip,不需要再检查方法
            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;
            }
            //如果类是public的,则检查Event类型是否为可以处理,并且是public的
            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()方法是判断这个类对于indexPackage,即index所定义的包是否为可见的,其代码逻辑比较简单不再贴出,通常来说如果不处于一个包内,这个受检查的类必须是public, 否则就是不可见的,需要加入跳过的类的数据结构中,从代码中可以看出如果订阅方法所属的类或者该类中有一个方法的Event不可处理,即Event不是public的,(至于DeclaredType,不太清楚),那么该类中的所有订阅方法都不会在生成的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( ...
        ...

这些代码看着比较抽象,这里举一个简单的例子,在自己的工程中做如下配置:

buildscript {
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

apply plugin: 'com.neenbedankt.android-apt'

dependencies {
    compile 'org.greenrobot:eventbus:3.0.0'
    apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}

apt {
    arguments {
        eventBusIndex "com.recluse.MyEventBusIndex"
    }
}

然后,自己的MainActivity如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

//        EventBus.getDefault().register(this);
    }


    @Subscribe(threadMode = ThreadMode.MAIN)
    public void getEvent(DefaultEvent event){

    }

}

此时编译程序,会在build->generated->apt->debug文件夹下生成一个文件,其代码为:

/** 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.androidlearning.MainActivity.class, true,
                new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("getEvent", com.example.androidlearning.DefaultEvent.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;
        }
    }
}

这段代码就是createInfoIndexFile方法生成,编译过后就可以在运行期调用生成代码的方法用于查询订阅方法的信息。这里需要注意MainActivity的注册方法那行代码是注释掉的,也就是说即使没有注册EventBus,只要有@Subscribe标注的方法,注解处理器就会为我们生成对应的代码。这段代码此处暂时不分析,后面会解释其中的逻辑。

以上部分就是注解处理器部分,注解处理器会收集所有的订阅方法信息,然后生成对应的Java代码源文件,编译一同进入可执行的文件中,这样就可以在运行期直接进行调用查询对应的订阅方法。下面就开始重点,即如何查询订阅方法。
在介绍查询方法之前,我们需要熟悉几个定义的接口,先从生成代码所实现的接口开始:

/**
 * Interface for generated indexes.
 */
public interface SubscriberInfoIndex {
    SubscriberInfo getSubscriberInfo(Class<?> subscriberClass);
}

这个接口定义的功能是可以通过订阅的类查找到该类所有的订阅方法,这个就是我们最终想要的,先定义出来,由生成代码实现,那么很容易就能猜到所有订阅方法的信息肯定是保存在返回的SubscriberInfo中,这个也是一个接口,其代码为:

/** Base class for generated index classes created by annotation processing. */
public interface SubscriberInfo {
    Class<?> getSubscriberClass();

    SubscriberMethod[] getSubscriberMethods();

    SubscriberInfo getSuperSubscriberInfo();

    boolean shouldCheckSuperclass();
}

从这里就可以看出来了,前两个方法就是我们想要的,后面两个方法则是处理继承关系的。下面就是该方法的抽象实现:

/** Base class for generated subscriber meta info classes created by annotation processing. */
public abstract class AbstractSubscriberInfo implements SubscriberInfo {
    private final Class subscriberClass;
    private final Class<? extends SubscriberInfo> superSubscriberInfoClass;
    private final boolean shouldCheckSuperclass;

    protected AbstractSubscriberInfo(Class subscriberClass, Class<? extends SubscriberInfo> superSubscriberInfoClass,
                                     boolean shouldCheckSuperclass) {
        this.subscriberClass = subscriberClass;
        this.superSubscriberInfoClass = superSubscriberInfoClass;
        this.shouldCheckSuperclass = shouldCheckSuperclass;
    }

    @Override
    public Class getSubscriberClass() {
        return subscriberClass;
    }

    @Override
    public SubscriberInfo getSuperSubscriberInfo() {
        if(superSubscriberInfoClass == null) {
            return null;
        }
        try {
            return superSubscriberInfoClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean shouldCheckSuperclass() {
        return shouldCheckSuperclass;
    }

    protected SubscriberMethod createSubscriberMethod(String methodName, Class<?> eventType) {
        return createSubscriberMethod(methodName, eventType, ThreadMode.POSTING, 0, false);
    }

    protected SubscriberMethod createSubscriberMethod(String methodName, Class<?> eventType, ThreadMode threadMode) {
        return createSubscriberMethod(methodName, eventType, threadMode, 0, false);
    }

    protected SubscriberMethod createSubscriberMethod(String methodName, Class<?> eventType, ThreadMode threadMode,
                                                      int priority, boolean sticky) {
        try {
            Method method = subscriberClass.getDeclaredMethod(methodName, eventType);
            return new SubscriberMethod(method, eventType, threadMode, priority, sticky);
        } catch (NoSuchMethodException e) {
            throw new EventBusException("Could not find subscriber method in " + subscriberClass +
                    ". Maybe a missing ProGuard rule?", e);
        }
    }

}

这个抽象类实现了接口,而接口中四个方法中实现了三个,而实现逻辑就是返回构造器传进来的值,所以逻辑很简单,只有一个我们最为需要的方法,就是getSubscriberMethods留个了子类实现,同时它实现了createSubscriberMethod方法,实现了订阅信息(即该方法的几个参数)转换为SubscriberMethod对象,这样子类可以很方便地调用。最后就是EventBus为我们提供的一个实现类,就是在生成代码中用到的SimpleSubscriberInfo,其代码为:

/**
 * Uses {@link SubscriberMethodInfo} objects to create {@link org.greenrobot.eventbus.SubscriberMethod} objects on demand.
 */
public class SimpleSubscriberInfo extends AbstractSubscriberInfo {

    private final SubscriberMethodInfo[] methodInfos;

    //构造器,传入SubscriberInfo接口定义的四个功能中所需要所有数据,只不过SubscriberMethod需要SubscriberMethodInfo转变一下
    public SimpleSubscriberInfo(Class subscriberClass, boolean shouldCheckSuperclass, SubscriberMethodInfo[] methodInfos) {
        super(subscriberClass, null, shouldCheckSuperclass);
        this.methodInfos = methodInfos;
    }

    //我们所需要的方法,也就是简单地将SubscriberMethodInfo转变为SubscriberMethod
    @Override
    public synchronized SubscriberMethod[] getSubscriberMethods() {
        int length = methodInfos.length;
        SubscriberMethod[] methods = new SubscriberMethod[length];
        for (int i = 0; i < length; i++) {
            SubscriberMethodInfo info = methodInfos[i];
            methods[i] = createSubscriberMethod(info.methodName, info.eventType, info.threadMode,
                    info.priority, info.sticky);
        }
        return methods;
    }
}

这个类的逻辑已经在注释中解释,但是有一点需要注意,就是调用父类构造器时,即AbstractSubscriberInfo的构造器,有一个参数,就是superSubscriberInfoClass,即Subscriber的父类的class对象,它传的空值。
到这里我们就清楚了,注解处理器中收集到的所有有关订阅方法的信息都以SubscriberMethodInfo的形式保存下来,然后由SimpleSubscriberInfo将其转变为我们想要的SubscriberMethod。现在再来分析我们前面没有分析的生成代码:

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

    putIndex(new SimpleSubscriberInfo(com.example.androidlearning.MainActivity.class, true,
            new SubscriberMethodInfo[] {
        new SubscriberMethodInfo("getEvent", com.example.androidlearning.DefaultEvent.class, ThreadMode.MAIN),
    }));

}

private static void putIndex(SubscriberInfo info) {
    SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
}

这里使用静态的SUBSCRIBER_INDEX保存每个类的SubscriberInfo,而注解处理器收集到的信息都在SimpleSubscriberInfo构造器中传递进去构造出我们想要的SubscriberInfo,保存在SUBSCRIBER_INDEX中。在接口定义的方法:

@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
    SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
    if (info != null) {
        return info;
    } else {
        return null;
    }
}

只是简答地从Map中查询相应的SubscribeInfo。至此生成代码也就分析完了,到这里我们也就大概可以猜出SubscriberMethodFinder是如何使用index方式查询订阅信息了,它首先需要获取一个index类的对象,然后使用subscriberClass查找对应的SubscriberInfo, 然后查找对应的List<SubscriberMehtod>。下面看SubscriberMethodFinder中的代码:

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    //初始化findState
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    //根据findState开始查询
    while (findState.clazz != null) {
        //获取SubscriberInfo,这个是重点
        findState.subscriberInfo = getSubscriberInfo(findState);
        if (findState.subscriberInfo != null) {
            //检查所有的方法,并添加到findState.subscriberMethods,其逻辑与使用反射方式相同
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            for (SubscriberMethod subscriberMethod : array) {
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            //如果没有找到该类对应的SubscriberInfo,则还是使用反射方式
            findUsingReflectionInSingleClass(findState);
        }
        //遍历父类
        findState.moveToSuperclass();
    }
    //获取订阅信息并释放findState
    return getMethodsAndRelease(findState);
}

这段代码逻辑很清晰就不在解释了,除了getSubscriberInfo(findState)方法之外,其逻辑与反射方式中几乎完全一致,下面重点看getSubscriberInfo()方法:

private SubscriberInfo getSubscriberInfo(FindState findState) {
    if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
        SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
        if (findState.clazz == superclassInfo.getSubscriberClass()) {
            return superclassInfo;
        }
    }
    if (subscriberInfoIndexes != null) {
        for (SubscriberInfoIndex index : subscriberInfoIndexes) {
            SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
            if (info != null) {
                return info;
            }
        }
    }
    return null;
}

这个方法后面一半很容易理解,subscriberInfoIndexes是SubscriberFinder构造器传进来的值,其代表生成代码的类,也就是用于查询SubscriberInfo的类,在编译期生成,而EventBus类中subscriberInfoIndexes是Builder中可以添加的参数,所以就明白了我们如果想要使用index方式,需要在生成EventBus对象时添加该参数,官方实例如下:

EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();

如果想要使用默认的单例,还可以使用如下方式:

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();

这样讲index的类传进去,就可以使用生成的类,用于查询订阅信息了。

接下来看getSubscriberInfo方法中的前一半逻辑,我们之前说过SimpleSubscriberInfo构造器没有SuperSubscriberClass这个参数,而它在调用父类构造器时也是传的空,在看AbstractSubscriberInfo中,getSuperSubscriberInfo()方法如下:

@Override
public SubscriberInfo getSuperSubscriberInfo() {
    if(superSubscriberInfoClass == null) {
        return null;
    }
    try {
        return superSubscriberInfoClass.newInstance();
    } catch (InstantiationException | IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

如果我们使用EventBus默认的index方式,superSubscriberInfoClass必然为空,那么也就永远不会执行上面那个方法的前一半逻辑,所以需要自己实现SubscriberInfo时才会执行,而findState.clazz == superclassInfo.getSubscriberClass()仿佛又是在判断到达了类结构的顶端或者类似的,一时没有想明白,知道部分用意的读者欢迎在评论中告知,不胜感激!

好了,到此index方式查询方法也介绍完了,总结就是如下步骤,第一步注解处理器收集subscribe注解标注的方法,将信息写入生成的代码中,并以subscriberClass为键,以SubscribeInfo为值保存信息;第二步就是在一个类注册时,使用该类在EventBus的所有index类中查询对应的SubscribeInfo, 进而可以获取到订阅方法信息,使用订阅方法信息就可以执行注册操作(这部分在第一篇文章中讲述)。

而整个查找过程中,无论是使用反射方式还是index方式,都是需要动态更新一个FindState的内部类的对象,该对象记录查询状态并记录查询结果,最后对其回收重复利用。

4. 后记

本篇文章主要介绍了EventBus查询订阅方法信息的过程,重点是讲述了index方式的查找,由于对注解处理器了解较少,对于注解处理器的讲述比较粗糙简略,而且还会有一些错误,欢迎批评指正,有时间还是需要看一些java的基础。EventBus源码分析的第二部分就结束了,接下来就需要学习EventBus最核心的代码了,就是它的三个Poster,也就是这三个Poster是的事件可以在不同线程中特别方便地传递,使得应用极为简单,这部分将在第三部分做分析。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容