简述
很多时候有的业务隔离的很严重,但是有需要在一定的时机上互相通知,为了解决这个场景有一种比较合适的方案,就是事件总线机制。EventBus就是该机制的一种实现,基于观察者模式,简单说就是有一个总线控制,然后观察者挂载到总线上,然后有消息发送出来之后,总线根据挂载情况进行分发。
以下代码基于EventBus3.0
参数和构造
先看一下EventBus基础的参数和构造:
//DCL单例模式
static volatile EventBus defaultInstance;
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
//原始事件和该事件可以进行回调的其他事件列表的集合
//一般来说就是结合eventInheritance
//然后会记录自己的所有父类和所实现的所有接口(包括每一个父类所实现的接口)等
private static final Map, List>> eventTypesCache = new HashMap<>();
//回调的方法对应的参数(事件)和对应的订阅者单元(订阅的类和具体的某一个方法)所构成的集合
private final Map, CopyOnWriteArrayList> subscriptionsByEventType;
//订阅者(类)和其所对应的所有事件的列表
private final Map
EventBus为单例模式,也就是一个总线控制台。可以看到实际上观察者都是在内存中的。
观察者的注册
/**
* 注册一个观察者用于接收指定的一些事件,实际上就是查找回调方法,然后记录一堆数据
* 当观察者不再关心这些事件的时候,要通过unregister注销,从而使得观察者不再接收那些事件的回调
* 观察者必须要有回调方法,且该回调方法必须通过@Subscribe进行注解
* 该注解还可以指定线程和优先级
*/
public void register(Object subscriber) {
//获得观察者的类,比方说在AFragment中注册,那么这里就会获得AFragment这个类
Class> subscriberClass = subscriber.getClass();
//通过观察者类查找里面定义的所有回调方法
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {//这个只是对象锁,这就意味着不同的EventBus实例,事件的回调不会互相上锁
//为了防止处理过程中列表变化,这里必须上锁
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
/**
* 将当前观察者和对应的方法记录在缓存当中,相当于标记注册
* 不允许重复添加!所以说重复添加之前必须进行反注册或者清除缓存
* 如果当前观察者进行回调的方法处理黏性事件,那么需要尝试进行黏性事件回调
* @param subscriber 当前的观察者
* @param subscriberMethod 当前的观察者所定义的某一个具体的事件回调方法
*/
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
//获得当前方法对应的事件,实际上就是观察者所观察的某一个事件
Class> eventType = subscriberMethod.eventType;
//通过观察者和该方法定义一个个新的观察单元
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//从缓存中通过当前事件获得观察单元
//同一个事件当然可以有不同的观察者
CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
//当前事件没有观察单元处理,新建
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//其实就是不允许同一个类重复注册,要重新注册,那么之前必须反注册
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
//将当前观察元素添加到缓存中
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
//可以看到,在注册的时候优先级大的会在同一个事件的观察元素队列中靠前
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
//从缓存中通过观察者获得其所观察的所有事件类型
List> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {//之前没有缓存,新建
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);//将当前事件放入缓存,建立了观察者和其所观察的某一个事件的缓存
if (subscriberMethod.sticky) {//当前观察者的回调方法是处理黏性事件的
if (eventInheritance) {
Set, Object>> entries = stickyEvents.entrySet();
for (Map.Entry, Object> entry : entries) {//遍历黏性事件缓存
Class> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
//如果当前观察者的回调方法处理的事件类型为缓存中某一个黏性事件的父类
//举例说明当前方法处理的黏性事件为A
//之前已经有黏性事件B发送过了,而且满足B extends A
//那么当前B事件也会进行回调处理
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
//从缓存中通过当前事件来获取黏性事件,如果在这之前已经有黏性事件发送,那么缓存中必定有值
Object stickyEvent = stickyEvents.get(eventType);
//如果缓存中有值,即null != stickyEvent,说明之前该黏性事件已经发送过了,尝试进行回调
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
/**
* 检查当前黏性事件是否为空
* 非空则进行事件回调处理
* @param newSubscription 订阅元素(当前的观察者和其处理当前黏性事件的方法)
* @param stickyEvent 当前可能需要进行回调的黏性事件
*/
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if (stickyEvent != null) {
postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
}
}
实际上就是记录了当前观察者和观察者想要观察的事件、回调方法。
这个从上面的参数中也可以看到:
1.观察者和该观察者所关心的事件列表所构成的映射,这个一般用于判断某一个观察者是否注册。
2.事件和可以处理该事件的观察者、回调方法所构成的映射,这个用于在发送事件的时候快速查找对应的回调。其中该队列有按照优先级排序,后续在回调的时候可以看到是正序遍历的,所以说优先级高的会先回调。
其中稍微注意一下黏性事件,简单的理解就是黏性事件一旦发送,哪怕观察者在后面注册,也会收到该事件。
事件发送
/**
* 发送一个黏性事件
* 这个事件会保留在内存当中,然后如果发送之后还有黏性事件的观察者注册了
* 此时该事件也可能会进行回调处理
*/
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);//黏性事件会保留在内存当中
}
//当前先发送事件给之前注册了当前事件的观察者
post(event);
}
/**
* 发送一个事件到事件总线上,并且尝试进行对应的回调
* @param event 当前需要发送的事件
* */
public void post(Object event) {
//通过当前调用的线程获得对应的PostingThreadState
PostingThreadState postingState = currentPostingThreadState.get();
//获取当前线程的事件队列
List
这里主要有三个关注点:
1.在进行事件回调的时候,通过正序遍历,从而结合之前的优先级顺序进行回调。
2.事件回调的时候可以通过eventInheritance使得那些观察当前事件父类或者实现的接口的观察者同样可以收到当前事件。
3.事件的发送在不同的线程有不同的发送者,这个发送者有着自己的发送事件队列和参数
4.当前观察者等数据都是记录在内存当中,回调的时候仅仅使用的是内存中的数据,所以说EventBus不支持跨进程的通知。
//每一个线程都有自己对应的发送状态等参数
private final ThreadLocal currentPostingThreadState = new ThreadLocal() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};
final static class PostingThreadState {
//当前线程对应的发送事件列表
final List
观察者进行事件处理
当一个事件发送到总线上,然后总线通过发送器将事件发送到一些对应的观察者,然后观察者开始进行事件的回调
/**
* 根据观察者的回调方法中定义的线程模式来进行不同的事件回调
* @param subscription 当前需要进行回调的观察元素
* @param event 当前需要回调的方法的参数,也是当前需要处理的事件
* @param isMainThread 当前是否处于主线程
*/
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING://直接在当前post的线程中执行
invokeSubscriber(subscription, event);
break;
case MAIN://在ui线程中执行
if (isMainThread) {//当前post在主线程中,那么直接执行
invokeSubscriber(subscription, event);
} else {//否则将当前事件交给主线程Poster等待执行
mainThreadPoster.enqueue(subscription, event);
}
break;
case BACKGROUND://在子线程中执行
if (isMainThread) {//当前post在主线程中,将事件交给指定Poster等待在线程池中的某一个线程执行
backgroundPoster.enqueue(subscription, event);
} else {//当前post在子线程中,直接在当前线程执行
invokeSubscriber(subscription, event);
}
break;
case ASYNC://异步执行
//一定在子线程中执行,但是不管当前在什么线程,都会进入线程池中等待调度执行
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
/**
* 这个用于在Poster里面进行事件回调处理
* 但是因为存在一些异步的Poster,所以说在进行回调之前还是要检查当前是否unregister
*/
void invokeSubscriber(PendingPost pendingPost) {
//记录当前需要进行回调处理的元素
Object event = pendingPost.event;
Subscription subscription = pendingPost.subscription;
//将当前节点进行回收
PendingPost.releasePendingPost(pendingPost);
//有的时候异步进行回调的时候
//可能当前的观察者已经被注销了
//所以这里要进行检查
if (subscription.active) {
invokeSubscriber(subscription, event);
}
}
/**
* 进行方法的调用
* @param subscription 当前需要回调的观察元素
* @param event 当前需要回调的事件,也是回调函数的参数
*/
void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
/**
* 用于在观察者的事件回调方法中调用,那么当前事件所对应的后续的观察元素的回调都不会进行
* 常用的场景就是高优先级阻断低优先级的执行
* 调用这个方法必须要求当前的回调方法是POSTING线程,因为这样才能保证同步性
* 这个和postSingleEventForEventType处理的逻辑有关
*/
public void cancelEventDelivery(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
//实际上这个方法是用于在事件回调的时候调用的,所以说需要检查当前是否处于事件发送中
if (!postingState.isPosting) {
throw new EventBusException(
"This method may only be called from inside event handling methods on the posting thread");
} else if (event == null) {
throw new EventBusException("Event may not be null");
} else if (postingState.event != event) {
throw new EventBusException("Only the currently handled event may be aborted");
} else if (postingState.subscription.subscriberMethod.threadMode != ThreadMode.POSTING) {
throw new EventBusException(" event handlers may only abort the incoming event");
}
postingState.canceled = true;
}
1.不同的ThreadMode会决定不同的回调处理线程
public enum ThreadMode {
/**
* 观察者的回调方法会在post的线程中进行回调。这个是默认值,可以避免线程的频繁切换
* 相对适合一些简单的任务(可以知道在很短的时间内处理的)
* 对于那些耗时的任务来说,如果当前post的线程为UI线程
* 会导致posting这个过程的阻塞(UI线程不应该被阻塞),这样就不合适了
*/
POSTING,
/**
* 观察者的回调方法在UI线程中进行回调。如果post在UI线程则直接调用,都这进入主线程Handler中处理。
* 同样的,在这种模式下不应该进行耗时操作,从而避免阻塞UI线程
*/
MAIN,
/**
* 观察者的回调方法在子线程中进行。如果当前post在子线程则直接调用。
* 否则进入线程池中执行,在设计的时候是尽可能使用单一的线程
* 也就是说在post连续多个事件的时候,要稍微留意一下阻塞的情况
* 某一个事件回调导致子线程阻塞,这同样会阻塞后面的事件进行处理的时机
*/
BACKGROUND,
/**
* 观察者的回调方法在子线程中进行。
* 不同于BACKGROUND的是这里直接将回调在线程池中执行
* 也就是调度的工作交给了线程池,默认使用的是cacheExecutor
* 所以说响应速度还是很快的,但是也要注意不要导致CPU多余忙碌
*/
ASYNC
}
2.可以通过cancelEventDelivery来取消事件的发送,不过这个只是用于在高优先级回调后阻断低优先级处理,所以要求发送模式为POSTING,只有在同步进行事件回调处理的时候,才能保证低优先级的一定在高优先级的回调之后执行,这样才能正确中断事件。
3.当事件没有观察者处理或者出现异常的时候,如果允许发送一些默认的事件,那么可以通过观察这些默认的事件来做一些统计、日志之类的操作。
观察者注销
/**
* 反注册制定的观察者,会将当前观察者和其观察的事件缓存移除
* 并且将对应的观察元素移除
* @param subscriber 当前不再需要观察事件的观察者
*/
public synchronized void unregister(Object subscriber) {
//实际上就是将当前观察者已经注册的所有缓存移除
//包括自身和事件的缓存、自身单独拥有的事件移除等等
List> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class> eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
typesBySubscriber.remove(subscriber);
} else {
Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
/**
* 通过制定的观察者和事件,进行反注册,就是移除缓存
* @param subscriber 当前需要反注册的观察者
* @param eventType 当前需要反注册的观察者曾经观察的事件
* */
private void unsubscribeByEventType(Object subscriber, Class> eventType) {
List subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
if (subscription.subscriber == subscriber) {
subscription.active = false;
subscriptions.remove(i);
i--;
size--;
}
}
}
}
一般来说在Android中观察者是Activity,因为EventBus中通过强引用持有观察者,那么在Activity销毁的时候如果不进行注销,就会导致内存泄漏,这个要注意一下。
简单来说就是在观察者不需要继续观察事件的时候记得注销观察者。
事件回调及函数注册相关
观察者需要定义方法来处理指定的事件,EventBus通过注解Subscribe来识别,这样的最大好处就是可以自定义函数名,而不需要像旧版本一样必须是onEvent开头。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
/**
* 当前回调方法是否处理黏性事件
*/
boolean sticky() default false;
/**
* 回调优先级,这个只有对于同样的线程才有效,优先级高的会先响应事件
* */
int priority() default 0;
}
使用的时候就是类似
@Subscribe(threadMode = ThreadMode.POSTING, priority = 10)
public void handlerLogin(Activity activity){
//做些什么。。。
}
通过这个注解可以定义为EventBus中的回调函数,处理的是Activity这个事件。
接着看EventBus是如何查找观察者中定义的回调函数,这个在之前register中有提到,实际上是通过SubscriberMethodFinder处理
/**
* 通过观察者查找其所定义的事件回调方法
* @param subscriberClass 观察者
* @return 该观察者所定义的事件回调方法列表
*/
List findSubscriberMethods(Class> subscriberClass) {
//如果该观察者之前注册过,但是后来注销了,那么下一次重新注册的时候,直接从缓存中获取即可
//不需要每一次都重新查找一遍,效率会好一些
List subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
if (ignoreGeneratedIndex) {
//忽略索引的话默认使用反射,相对索引来说还是慢不少的
subscriberMethods = findUsingReflection(subscriberClass);
} else {
//通过索引查找,正常来说索引会在编译器生成,然后会写一个新的文件,里面会通过一个map存储所有结果
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;
}
}
/**
* 实际上索引在编译期中就已经生成
* 存在一个static的map存储
* 这里其实就是通过当前的观察者拿出这些函数而已
*/
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);
}
/**
* 通过索引从某一个观察者中获取所有可以处理事件的函数
* @param findState 用于存储查找结果
*/
private SubscriberInfo getSubscriberInfo(FindState findState) {
//之前在匹配索引的时候,可能当前class是之前事件的父类,那么这个时候可能有缓存
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;
}
/**
* 通过反射来找观察者中的用于回调事件的方法
* 注意这个是会去找观察者的父类
* @param subscriberClass 观察者
*/
private List findUsingReflection(Class> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findUsingReflectionInSingleClass(findState);
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
/**
* 通过反射指定的注解来从某一个观察者中获取所有可以处理事件的函数
* @param findState 用于存储查找结果
*/
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// 相对于getMethods来说会快不少,特别当观察者是一些特别巨大的类
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
//出现异常的时候通过getMethods再次尝试,并且跳过父类
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
for (Method method : methods) {
int modifiers = method.getModifiers();//获得函数的修饰
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class>[] parameterTypes = method.getParameterTypes();//获得函数的参数
if (parameterTypes.length == 1) {//EventBus的观察者回调处理参数默认就是1个
//查找对应的注解Subscribe
//EventBus3.0通过反射只认注解
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class> eventType = parameterTypes[0];//获得当前事件
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
//将当前满足条件的函数存入findState中
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException("@Subscribe method " + methodName +
"must have exactly 1 parameter but has " + parameterTypes.length);
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}
可以看到EventBus支持两种查找方式,一种是反射注解,一种是通过索引。
反射运行时注解的效率是偏低的,所以说一般来说推荐使用索引,索引后面会提到是通过编译期间生成的,所以说在使用的时候单纯是通过从map中取出,这个的效率是非常可观的,当然对应的内存相对会占用多一点点。
索引
EventBus通过编译期间处理注解,从而预先将结果保存,后期直接获取即可,这个具体实现在EventBusAnnotationProcessor
@SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe")//用于处理Subscribe这个注解
@SupportedOptions("eventBusIndex")
public class EventBusAnnotationProcessor extends AbstractProcessor {
//...
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment env) {
//...省略一些
//这里是收集整个项目中的非static,而且为public的有Subscribe注解的函数
//总之就是满足EventBus回调处理事件的函数
collectSubscribers(annotations, env, messager);
checkForSubscribersToSkip(messager, indexPackage);
if (!methodsByClass.isEmpty()) {//当前项目中有满足EventBus回调条件的用Subscribe注解的函数
createInfoIndexFile(index);//创建文件,主要是构建一个map,用于存储当前获得的所有观察者和函数
} else {
messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found");
}
//...省略一些
}
这里忽略其它实现细节,简单说就是在编译器间,会自动查找所有有Subscribe注解的类和方法,然后记录有效的观察者和函数等参数(称为索引)起来,并且写一个新的java文件,其中包括一个map,然后将之前记录的索引放入map中。
buildscript {
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
apt {
arguments {
eventBusIndex "com.example.myapp.MyEventBusIndex"
}
}
通过设置指定的eventBusIndex可以生成对应的索引类,上面曾经提到写的java文件其实就是这个,那么在编译完成之后,只需要通过EventBusBuilder将当前类添加即可。
public EventBusBuilder addIndex(SubscriberInfoIndex index) {
if(subscriberInfoIndexes == null) {
subscriberInfoIndexes = new ArrayList<>();
}
subscriberInfoIndexes.add(index);
return this;
}
如此一来索引就成功添加到Builder当中,这里支持添加多个索引,最后只需要通过该Builder创建EventBus即可。
总结
EventBus在不跨进程的场景下还是相当方便的,特别是作为通知。
如果使用EventBus的话,个人推荐使用索引的方式,毕竟说可以自动生成,当然需要手动添加这个有点麻烦,不过这样可以很有效的减去反射的开销,效率上面还是很棒的。