EventBus3.0 性能提升之添加索引
EventBus3.0 源码解析 一文中,在分析Subscriber的register()过程中,说到过一个方法方法findUsingReflectionInSingleClass,在该方法的作用是在RunTime期间通过反射获取Subscriber中的SubscriberMethod。
这样就会产生一个问题,在RunTime期间使用反射对程序运行的性能有较大影响。这里我们可以看看EventBus作者提供的一张图:
从上图中我们可以看出,RunTime时反射(EventBus3 No Index)的实现方式是性能最差的。为了避免这样的情况,EventBus3.0中增加了一个新特性:通过在编译期创建索引(SubscriberInfoIndex)以提高程序运行性能
所以,这里主要对EventBus中创建索引相关的功能进行分析。
配置使用索引
关于索引的配置,可以参考下EventBus官方文档:Subscriber Index。
主要就两个步骤:
- 使用annotationProcessor并设置arguments
arguments就是指定的生成的索引的全限定类名 - 创建EventBus实例并传入索引的实例
注解处理器EventBusAnnotationProcessor生成索引
在配置annotationProcessor之后,程序在编译期间就可以通过注解处理器生成一个SubscriberInfoIndex类(SubscriberInfoIndex类的名称就是由arguments指定),在该类中就存储了Subscriber以及相关SubscriberMethod。
现在,我们来分析下索引类是如何生成的,也就是EventBus中注解处理器EventBusAnnotationProcessor是如何工作的。
如何对于注解处理器的工作原理不是很清楚的话,可以先看看Java AbstractProcessor实现自定义ButterKnife
process()
@Override
public boolean process(Set<? extends TypeElement> 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<? 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)) {
// 获取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<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
static {
SUBSCRIBER_INDEX = new HashMap<Class<?>, 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期间反射的使用以提高程序整体的运行性能。