EventBus3.0 性能提升之添加索引

EventBus3.0 源码解析 一文中,在分析Subscriber的register()过程中,说到过一个方法方法findUsingReflectionInSingleClass,在该方法的作用是在RunTime期间通过反射获取Subscriber中的SubscriberMethod。

这样就会产生一个问题,在RunTime期间使用反射对程序运行的性能有较大影响。这里我们可以看看EventBus作者提供的一张图:

从上图中我们可以看出,RunTime时反射(EventBus3 No Index)的实现方式是性能最差的。为了避免这样的情况,EventBus3.0中增加了一个新特性:通过在编译期创建索引(SubscriberInfoIndex)以提高程序运行性能

所以,这里主要对EventBus中创建索引相关的功能进行分析。


配置使用索引

关于索引的配置,可以参考下EventBus官方文档:Subscriber Index
主要就两个步骤:

  1. 使用annotationProcessor并设置arguments
    arguments就是指定的生成的索引的全限定类名
  2. 创建EventBus实例并传入索引的实例

注解处理器EventBusAnnotationProcessor生成索引

在配置annotationProcessor之后,程序在编译期间就可以通过注解处理器生成一个SubscriberInfoIndex类(SubscriberInfoIndex类的名称就是由arguments指定),在该类中就存储了Subscriber以及相关SubscriberMethod。

现在,我们来分析下索引类是如何生成的,也就是EventBus中注解处理器EventBusAnnotationProcessor是如何工作的。
如何对于注解处理器的工作原理不是很清楚的话,可以先看看Java AbstractProcessor实现自定义ButterKnife

process()

   @Override
    public boolean process(Set annotations, RoundEnvironment env) {
        Messager messager = processingEnv.getMessager();
        try {
            // 1、获取build.gradle中配置的arguments(这个就是在编译后生成类的全限定类名)
            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;
            }
            
            // 省略部分代码

            // 2、获取每个SubSubscriber中所有的SubscribeMethod
            collectSubscribers(annotations, env, messager);
            // 3、获取所有的不需要被执行的SubSubscriber
            checkForSubscribersToSkip(messager, indexPackage);

            if (!methodsByClass.isEmpty()) {
                //4、创建类文件,初始化所有索引
                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;
    }

在process()方法中,首先获取SubscriberInfoIndex类的类名index,如何这个index为空的话,注解处理器就会执行结束也就不能生成SubscriberInfoIndex类,所以在build.gradle必须配置arguments。

获取类名后,开始获取所有的Subscriber以及相关SubscriberMethod,这主要通过collectSubscribers()方法完成。

collectSubscribers()

    private void collectSubscribers(Set annotations, RoundEnvironment env, Messager messager) {
        for (TypeElement annotation : annotations) {
            Set elements = env.getElementsAnnotatedWith(annotation);
            for (Element element : elements) {
                if (element instanceof ExecutableElement) {
                    ExecutableElement method = (ExecutableElement) element;
                    if (checkHasNoErrors(method, messager)) {
                        // 获取Subscriber类
                        TypeElement classElement = (TypeElement) method.getEnclosingElement();
                        // 存储每个Subscriber中的SubscriberMethod
                        methodsByClass.putElement(classElement, method);
                    }
                } else {
                    messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element);
                }
            }
        }
    }

collectSubscribers()方法,主要就是遍历获取所有的Subscriber及SubscriberMethod,并且通过methodsByClass储存起来。

methodsByClass是一个ListMap,Key代表一个Subscriber类,value是一个存储该Subscriber所有SubscriberMethod的list集合。

这里我们通过putElement()方法可以看出:


public synchronized void putElement(K key, V value) {
    C collection = map.get(key);
    if (collection == null) {
        collection = createNewCollection();
        map.put(key, collection);
    }
    collection.add(value);
}

collectSubscribers()

通过collectSubscribers()方法获取到所有的Subscriber后,需要对其中一些不可用的Subscriber做一个过滤,例如私有类型或者受保护的类型。

这个方法在一般情况下也不会使用,所以这里就不做过多的介绍了。

createInfoIndexFile()

createInfoIndexFile()方法,就是具体的创建一个索引类,即生成一个Java文件。这里对这部分代码就不做过多说明了。

这里贴出一个实例代码:

package com.zhangke.eventbusdemo;

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, SubscriberInfo> SUBSCRIBER_INDEX;

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

        // 这里有两个Subscriber,每个Subscriber中都有两个SubscriberMethod
        putIndex(new SimpleSubscriberInfo(SecondActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onFirstEvent", FirstEvent.class),
            new SubscriberMethodInfo("onSecond", FirstEvent.class, ThreadMode.MAIN, 1, true),
        }));

        putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onFirstEvent", FirstEvent.class),
            new SubscriberMethodInfo("onSecond", FirstEvent.class, ThreadMode.MAIN, 1, false),
        }));

    }

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

在该类中声明了一个SUBSCRIBER_INDEX的静态Map集合,并然后通过静态代码块将所有的Subscriber存储在该静态集合中。并且在该类中提供了有一个Override方法getSubscriberInfo()用于获取指定Subscriber中的SubscriberInfo。

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

使用索引

在官方文档中提到,要想使用索引需要使用下面两种方式创建EventBus实例:

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

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

我们看看addIndex()方法的源码:

    /** Adds an index generated by EventBus' annotation preprocessor. */
    public EventBusBuilder addIndex(SubscriberInfoIndex index) {
        if (subscriberInfoIndexes == null) {
            subscriberInfoIndexes = new ArrayList<>();
        }
        subscriberInfoIndexes.add(index);
        return this;
    }

在该方法中,将SubscriberInfoIndex存储在subscriberInfoIndexes集合中。

那集合subscriberInfoIndexes在什么时候使用呢?
这里我们需要回到register()的过程中的findUsingInfo()方法中,在该方法中有一个方法getSubscriberInfo()在上节中我们不曾提到,这个方法就是用于获取索引中存储的Subscriber信息。

    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;
            }
        }
        
        // 获取索引中的Subscriber内容
        if (subscriberInfoIndexes != null) {
            for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                if (info != null) {
                    return info;
                }
            }
        }
        return null;
    }

这样,就通过索引获取到了SubscriberInfo,这样在findUsingInfo()中,findUsingReflectionInSingleClass()方法就不会被调用了。最终的目的(避免反射的使用)也就达到了。

findUsingReflectionInSingleClass()方法就是在RunTime时使用反射获取SubscriberMethod

总结

EventBus3.0中创建索引,目的就是在编译期间通过注解处理器创建Java类,避免在RunTime期间反射的使用以提高程序整体的运行性能。

你可能感兴趣的:(EventBus3.0 性能提升之添加索引)