EventBus|EventBus3.0——索引的使用

EventBus3.0——索引的使用

相信对于索引这个词大家不会陌生,很多地方都有用到,那么为什么EventBus会引进索引这个东西,大家都知道EventBus的功能是通过反射机制获取观察者(订阅者)方法来实现的,而反射本身对性能的损耗比起一般的方法来说要大很多,具体慢多少,你们可以写一个程序测试下,大概50倍左右(当然对于CPU的运行速度,没有你想象中夸张)。这里有3.0作者提供的一张对比图,可以清晰的看到加了索引后对性能的优化有多大:
EventBus|EventBus3.0——索引的使用
文章图片

不知道真实数据有没这么夸张,将近三倍的提升,屌炸天了!!!
这一章的代码部分源于前两章,最后会给大家提供demo,先给大家看一下生成的索引到底长啥样:
/** This class is generated by EventBus, do not edit. */ public class PdmEventBusIndex implements SubscriberInfoIndex { private static final Map, SubscriberInfo> SUBSCRIBER_INDEX; static { SUBSCRIBER_INDEX = new HashMap, SubscriberInfo>(); putIndex(new SimpleSubscriberInfo(com.pdm.eventbus_master.fragment.Fragment_Item.class, true, new SubscriberMethodInfo[] { new SubscriberMethodInfo("pdmEvent", com.pdm.eventbus_master.bean.Student.class, ThreadMode.POSTING, 0, true), new SubscriberMethodInfo("pdmEventMainThread", com.pdm.eventbus_master.bean.Student.class, ThreadMode.MAIN, 2, true), new SubscriberMethodInfo("pdmEventBackground", com.pdm.eventbus_master.bean.Student.class, ThreadMode.BACKGROUND, 3, true), new SubscriberMethodInfo("pdmEventAsync", com.pdm.eventbus_master.bean.Student.class, ThreadMode.ASYNC, 4, true), })); putIndex(new SimpleSubscriberInfo(com.pdm.eventbus_master.fragment.Fragment_List.class, true, new SubscriberMethodInfo[] { new SubscriberMethodInfo("onEventMainThread", java.util.List.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; } } }

是不是有点看蒙了,不用担心,因为这个是自动生成的,所以也不要去改动哦。看到代码中将EventBus反射所需要的方法名、全类名、注解信息都放到了SubscriberMethodInfo对象中,put到SUBSCRIBER_INDEX静态集合,为了方便大家理解,我们来看一下这个索引在源码中的使用:
List findSubscriberMethods(Class subscriberClass) { List subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } if (ignoreGeneratedIndex) {//是否忽略生成的索引,通过反射获取注解方法,这个参数默认为false subscriberMethods = findUsingReflection(subscriberClass); } else {//不忽略索引,则通过索引得到注解方法的信息 subscriberMethods = findUsingInfo(subscriberClass); } if (subscriberMethods.isEmpty()) { throw new EventBusException("Subscriber " + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation"); } else { METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; } }

接下来我们看下findUsingInfo(subscriberClass)方法:
private List findUsingInfo(Class subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { findState.subscriberInfo = getSubscriberInfo(findState); //看这个方法,就是获取索引信息的方法集合 if (findState.subscriberInfo != null) { SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods(); for (SubscriberMethod subscriberMethod : array) { if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); } } } else {//如果没有索引信息,则依然通过反射获取注解方法集合 findUsingReflectionInSingleClass(findState); } findState.moveToSuperclass(); } return getMethodsAndRelease(findState); }

上文有自动生成的索引类,其中SUBSCRIBER_INDEX静态集合里面存的就是SubscriberInfo的信息,其实索引就是将所有用“@Subscribe”标记的方法放到SUBSCRIBER_INDEX集合中,我们通过addIndex方法传给EventBus,这样就不需要通过反射遍历所有方法并找出有注解(@Subscribe)标记的方法,自然速度蹭蹭蹭的上去了。至于getSubscriberInfo(findState)方法我这里就不贴了,你们可以自己去看一下源码。
讲了索引的好处,接下来我们就讲解下怎样生成和使用EventBus3.0的索引。
索引的使用详解: 1.索引的生成:
第一步、在工程目录下的build.gradle添加如下代码:
dependencies { classpath 'com.android.tools.build:gradle:2.2.0' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' }

第二步、在app目录下的build.gradle添加如下代码:
apply plugin: 'com.neenbedankt.android-apt' dependencies { apt 'org.greenrobot:eventbus-annotation-processor:3.0.1' } apt { arguments { eventBusIndex "com.pdm.eventbus_master.PdmEventBusIndex"//这里是声明生成索引的全类名 } }

gradle配置好,再make project,我们就可以查看生成的索引了:
EventBus|EventBus3.0——索引的使用
文章图片

2.索引的使用:
有两种使用方式,个人推荐第二种!
第一种:
EventBus eventBus = EventBus.builder().addIndex(new PdmEventBusIndex()).build(); eventBus.register(this); eventBus.post(new Stuedent("1","pdm1"));

获取添加了索引的EventBus对象,然后通过eventBus发布事件或消息。这种方式跳过了原来的单例模式,会存在线程安全隐患。
第二种:
//传入索引对象new PdmEventBusIndex(),并传给默认EventBus对象 EventBus.builder().addIndex(new PdmEventBusIndex()).installDefaultEventBus(); EventBus.getDefault().register(this); EventBus.builder.post(new Stuedent("1","pdm1"));

这种方式是将索引添加到默认的单例模式实例中,依然使用的是单例模式,我们看下installDefaultEventBus()这个方法的源码:
public EventBus installDefaultEventBus() { synchronized (EventBus.class) { if (EventBus.defaultInstance != null) {//如果默认实例已经存在,则会抛异常。 EventBus.getDefault(); 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; //返回的是默认实例 } }

注意有坑: 这里还要跟大家说一下EventBus.defaultInstance这个默认实例的修饰符,我们看下源码是怎么定义的:
static volatile EventBus defaultInstance;

字面意思是静态的不稳定的,这样使得defaultInstance不会被编译器优化,每一次使用的时候都会重新从内存读取,例如:
volatile int i= 0; int j = i; int pdm = i;

一般编译器会这么干:由于编译器两次读取i的数据,并且在这之间i并没有进行其他操作,那么编译器第二次赋值给pdm的时候,就会自动把上一次读取的数据放到pdm中,而不会再一次从i的内存块中读取,这样是优化的做法。
加了volatile则每一次都会从i的内存中读取数据,防止其他线程改变了i,而不知道的情况。
这样定义有它的好处,但是也出现了使用上的一个坑:运行app,然后大退,再进去,会因为installDefaultEventBus()方法中的判断而抛出异常,导致程序崩溃。
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|EventBus3.0——索引的使用】当你调用这个方法会先判断defaultInstance是否为空,大家可能会觉得,肯定为空啊,只要前面没调用过EventBus.getDefault(),程序大退,defaultInstance内存会被回收,下一次调用默认初始值为null,问题就出在这里了,因为加了volatile 修饰符,你下一次调用以为它被清掉了为空那就大错特错了,内存回收,但是在这块内存区没有被再一次使用前它里面的内容是不会被清掉的,因为加了volatile修饰符也使得系统会默认去访问它原来的内存块,导致EventBus.defaultInstance != null成立,异常抛出!当然有些安卓系统针对这种异常不会有问题,使用上也不会有影响,但是不能避免在部分手机上会出现FC,导致程序崩溃,效果图:
EventBus|EventBus3.0——索引的使用
文章图片

这种效果是不是很坑,因为不是所有手机都会出现这样的问题,如果大家想还原真相,大退app,再用debug模式运行,断点 “if (EventBus.defaultInstance != null)”,就能看到抛出异常闪退的情况了。那么这个坑该怎么填,自然是捕获异常,代码稍作修改:
//传入索引对象new PdmEventBusIndex(),并传给默认EventBus对象 try{ EventBus.builder().addIndex(new PdmEventBusIndex()).installDefaultEventBus(); }catch (EventBusException e){ e.printStackTrace(); } EventBus.getDefault().register(this); EventBus.builder.post(new Stuedent("1","pdm1"));

我们再看效果:
EventBus|EventBus3.0——索引的使用
文章图片

原理讲完了,用法讲完了,坑也填好了,最后祝大家使用愉快,还是给到没用过EventBus的朋友一个小demo(demo没有填坑,记得自己填上):
http://download.csdn.net/detail/aiyh0202/9662495

    推荐阅读