一、前言
EventBus是一个开源的事件总线框架,Android中被广泛使用,目前最新的版本为3.2.0,github地址:https://github.com/greenrobot/EventBus。
二、简单使用
在build.gradle中引入依赖:
implementation 'org.greenrobot:eventbus:3.2.0'
定义事件实体类:
public class CallEvent { }
订阅消息:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EventBus.getDefault().register(this); } @Override protected void onDestroy() { super.onDestroy(); EventBus.getDefault().unregister(this); } @Subscribe(threadMode = ThreadMode.ASYNC) public void onEvent(CallEvent event) { // TODO }
发送消息:
CallEvent event = new CallEvent(); EventBus.getDefault().post(event);
来到这里就可以把EventBus用起来了,不过这里有个问题,注册时会反射遍历注册类的所有被Subscribe注解的方法,这个过程比较耗时会影响性能,因此EventBus又提供了一个eventbus-annotation-processor库来解决该问题,因此我们把它引入进来:
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.1'
这个eventbus-annotation-processor还需要一个key为eventBusIndex的键值对,用来指明自动生成的类的类名:
android { defaultConfig { javaCompileOptions { annotationProcessorOptions { arguments = [ eventBusIndex : 'org.greenrobot.eventbusperf.MyEventBusIndex' ] } } }
然后编译代码,就可以在build/source/apt/debug/下看到自动生成了org.greenrobot.eventbusperf.MyEventBusIndex类,这时我们需要把这个类添加到EventBus中:
EventBus.builder() .addIndex(new MyEventBusIndex()) .installDefaultEventBus();
配置到此结束,可以愉快地使用了。
三、原理
EventBus的流程并不复杂,其本质就是采用观察者模式,使用者可以进行消息的订阅和发布,所以内部肯定就是维护了一个订阅者的集合,当发布者发布消息时,会找到对应的订阅者,然后调用其事件处理方法。
订阅消息
调用register方法把订阅者对象传进去,EventBus会找到该对象所对应的类的所有被Subscribe注解的方法,也就是事件处理方法,然后把订阅者对象和这些方法的信息都保存起来,具体到代码中是保存到一个map里:
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
这里的key就是事件处理方法的第一个参数的Class类型。
这里的value也就是订阅者对象列表,每一个元素,就是一个Subscription对象,该对象保存了订阅者对象和对应的事件方法信息,注意对于同一个订阅者对象,如果有N个事件处理方法,这里就会对应N个Subscription对象。在把Subscription对象添加到列表中,会根据事件处理方法的优先级字段进行添加,优先级高的放前面,低的放后面。
找到订阅者对象的事件处理方法
在第1步中,订阅消息会找到订阅对象的事件处理方法,这里是怎么找的呢?第一种方法就是直接反射查找,找到所有方法后,再逐个判断是否被Subscribe注解,如果是,就说明这是一个事件处理方法,反射的同时,也收集注解的信息,比如回调线程,优先级等等,关键代码如下:
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); if (subscribeAnnotation != null) { Class<?> eventType = parameterTypes[0]; if (findState.checkAdd(method, eventType)) { ThreadMode threadMode = subscribeAnnotation.threadMode(); findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky())); } }
很明显这种方法比较耗时,特别是订阅一般都是在UI线程中进行的,执行过慢就可能会出现一系列问题,因此EventBus就把这个过程提前到编译时进行,在编译时就把这些信息收集好,运行时直接查找就行,为了实现该目的,需要引入org.greenrobot:eventbus-annotation-processor库,然后编译后就会自动生成MyEventBusIndex类,类名可以在脚本中指定,生成的类代码:
/** 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.mco.testdemo.SecondActivity.class, true, new SubscriberMethodInfo[] { new SubscriberMethodInfo("onEvent", Object.class, ThreadMode.ASYNC), })); } 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; } } }
可以看到,订阅者的Class和事件处理方法被封装成SimpleSubscriberInfo,然后放到map里,至于如何自动解析注解生成这个类,可以看EventBusAnnotationProcessor的源码,流程比较简单这里就不分析了。
继续分析EventBus是怎样使用这个类的,看EventBus的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就是保存我们调用addIndex方法传进来的MyEventBusIndex对象,因此这里会直接调用MyEventBusIndex的getSubscriberInfo,也就获取到了对应的SubscriberInfo对象。
发布消息
发布消息就比较简单了,调用post方法,传入事件对象,看下EventBus的post方法:
public void post(Object event) { PostingThreadState postingState = currentPostingThreadState.get(); List<Object> eventQueue = postingState.eventQueue; eventQueue.add(event); if (!postingState.isPosting) { postingState.isMainThread = isMainThread(); postingState.isPosting = true; if (postingState.canceled) { throw new EventBusException("Internal error. Abort state was not reset"); } try { while (!eventQueue.isEmpty()) { postSingleEvent(eventQueue.remove(0), postingState); } } finally { postingState.isPosting = false; postingState.isMainThread = false; } } }
注意currentPostingThreadState是一个ThreadLocal对象,所以不同线程同时发消息时不会互相干扰的,这里会把消息放到currentPostingThreadState的队列中,然后循环遍历队列发消息,发消息的流程:
(1)根据消息的Class对象(包括父类和接口)拿到对应的订阅者列表。
(2)调用postToSubscription真正地发消息,这里会做线程切换。
(3)反射调用事件处理方法。
回调线程
EventBus有个枚举ThreadMode,定义了各种回调线程的枚举值:
(1)POSTING:表示在发送消息所在线程中回调。
(2)MAIN:表示在主线程中回调。
(3)MAIN_ORDERED:表示post一个任务到主线程消息队尾中回调。
(4)BACKGROUND:表示在子线程中回调,如果post线程本身就是子线程,那就直接回调,默认用了DEFAULT_EXECUTOR_SERVICE线程池,它实际上就是无队列,无限大的缓存线程池,线程缓存时间60s。
(5)ASYNC:表示在子线程中回调,但post本身是子线程也不会直接回调,默认也是用了DEFAULT_EXECUTOR_SERVICE线程池。
粘性事件
可以调用postSticky发送粘性事件,事件对象会被保存到stickyEvents中,后面订阅者在订阅该消息时,如果事件处理方法需要处理粘性事件,就回调。
四、其它问题
通过上面的分析,EventBus并不支持多进程,消息的订阅和发布都只能在同个进程中进行。
混淆问题,在查找订阅者的事件处理方法时,如果用反射查找,就会报错:
java.lang.NoSuchFieldError: No static field POSTING of type Lorg/a/a/r; in class Lorg/a/a/r; or its superclasses
这里keep住ThreadMode就可以了。(这个其实没怎么想明白,反编译看是有POSTING的,继续研究……)
如果用index查找,因为自动生成类中订阅者的方法名是混淆前的,而运行时该方法名已经被混淆成另外一个了,因此反射调用肯定是找不到的,而且没有任何地方调用的话这个方法会被proguard移除掉,所以这里要keep住事件处理方法。
事件死循环
如果两个地方都有发消息和处理消息的逻辑,要避免消息来回发送消息导致死循环的问题。