Subscriber Index
回顾之前分析的 EventBus 注册事件流程,主要是在项目运行时通过反射来查找订事件的方法信息,这也是默认的实现,如果项目中有大量的订阅事件的方法,必然会对项目运行时的性能产生影响。其实除了在项目运行时通过反射查找订阅事件的方法信息,EventBus 还提供了在项目编译时通过注解处理器查找订阅事件方法信息的方式,生成一个辅助的索引类来保存这些信息,这个索引类就是Subscriber Index
根据官方说明,注解的引入让EventBus 3.0相比2.x性能变差3-5倍,但是引入索引,3.0相比2.x性能提高至少3倍。
可以看到在性能方面,EventBus 3由于使用了注解,比起使用反射来遍历方法的2.4版本逊色不少。但开启了索引后性能像打了鸡血一样,远远超出之前的版本。
如何使用EventBus的索引呢?在 app 的 build.gradle 中加入如下配置:
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
// 根据项目实际情况,指定辅助索引类的名称和包名
arguments = [ eventBusIndex : 'com.eventbus.project.MyEventBusIndex' ]
}
}
}
}
dependencies {
compile 'org.greenrobot:eventbus:3.1.1'
// 引入注解处理器
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.1.1'
}
注意
只有@Subscribe 可以被索引,必须是 public修饰
不能在内部类中
编译之后,索引文件在如下目录
build/generated/source/apt/debug/com/eventbus/project/MyEventBusIndex.java
当你实例化EventBus时,通过以下方式:
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
或者,你想要在整个app中使用设置了索引类的EventBus实例作为默认单例:
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
在3.0版本中,EventBus提供了一个EventBusAnnotationProcessor注解处理器来在编译期通过读取@Subscribe注解,并解析和处理其中所包含的信息,然后生成java类来保存订阅者中所有的事件响应函数,这样就比在运行时使用反射来获得订阅者中所有事件响应函数的速度要快。
之后的用法就和我们平时使用 EventBus 一样了
// Now the default instance uses the given index. Use it like this:
EventBus eventBus = EventBus.getDefault();
MyEventBusIndex源码如下:
public class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map, SubscriberInfo> SUBSCRIBER_INDEX;
static {
SUBSCRIBER_INDEX = new HashMap, SubscriberInfo>();
putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
new SubscriberMethodInfo("changeText", String.class),
}));
}
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是一个HashMap,保存了当前注册类的 Class 类型和其中事件订阅方法的信息
接下来分析下使用 Subscriber Index 时 EventBus 的注册流程,我们先分析:
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
首先创建一个EventBusBuilder,然后通过addIndex()方法添加索引类的实例
public EventBusBuilder addIndex(SubscriberInfoIndex index) {
if (subscriberInfoIndexes == null) {
subscriberInfoIndexes = new ArrayList<>();
}
subscriberInfoIndexes.add(index);
return this;
}
即把生成的索引类的实例保存在subscriberInfoIndexes集合中。
然后用installDefaultEventBus()创建默认的 EventBus实例
public EventBus installDefaultEventBus() {
synchronized (EventBus.class) {
if (EventBus.defaultInstance != null) {
throw new EventBusException("Default instance already exists." +
" It may be only set once before it's used the first time to ensure consistent behavior.");
}
EventBus.defaultInstance = build();
return EventBus.defaultInstance;
}
}
public EventBus build() {
// this 代表当前EventBusBuilder对象
return new EventBus(this);
}
即用当前EventBusBuilder对象创建一个 EventBus 实例,这样我们通过EventBusBuilder配置的 Subscriber Index 也就传递到了EventBus实例中,然后赋值给EventBus的 defaultInstance成员变量。之前我们在分析 EventBus 的getDefault()方法时已经见到了defaultInstance:
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
所以在 Application 中生成了 EventBus 的默认单例,这样就保证了在项目其它地方执行EventBus.getDefault()就能得到唯一的 EventBus 实例!
之前在注册流程中分析了findUsingInfo方法:
private List findUsingInfo(Class> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
// 查找SubscriberInfo
findState.subscriberInfo = getSubscriberInfo(findState);
// 条件成立
if (findState.subscriberInfo != null) {
// 获得当前注册类中所有订阅了事件的方法
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
// findState.checkAdd()之前已经分析过了,即是否在FindState的anyMethodByEventType已经添加过以当前eventType为key的键值对,没添加过返回true
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
// 将subscriberMethod对象添加到subscriberMethods集合
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
由于我们现在使用了 Subscriber Index 所以不会通过findUsingReflectionInSingleClass()来反射解析订阅事件的方法。我们重点来看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) {
// 根据注册类的 Class 类查找SubscriberInfo
SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
if (info != null) {
return info;
}
}
}
return null;
}
subscriberInfoIndexes就是在前边addIndex()方法中创建的,保存了项目中的索引类实例,即MyEventBusIndex的实例,继续看索引类的getSubscriberInfo()方法,来到了MyEventBusIndex类中:
@Override
public SubscriberInfo getSubscriberInfo(Class> subscriberClass) {
SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
if (info != null) {
return info;
} else {
return null;
}
}
即根据注册类的 Class 类型从 SUBSCRIBER_INDEX 查找对应的SubscriberInfo,如果我们在注册类中定义了订阅事件的方法,则 info不为空,进而上边findUsingInfo()方法中findState.subscriberInfo != null成立,到这里主要的内容就分析完了,其它的和之前的注册流程一样。
所以 Subscriber Index 的核心就是项目编译时使用注解处理器生成保存事件订阅方法信息的索引类,然后项目运行时将索引类实例设置到 EventBus 中,这样当注册 EventBus 时,从索引类取出当前注册类对应的事件订阅方法信息,以完成最终的注册,避免了运行时反射处理的过程,所以在性能上会有质的提高。项目中可以根据实际的需求决定是否使用 Subscriber Index。