eventbus旨在为Android中的各组件提供解耦和的数据与事件传递。首先在需要接收事件和数据的组件中(官方推荐在onStart方法中)注册:
EventBus.getDefault().register(this);
然后在组件中定义接收事件和数据的方法。在最新的eventbus3.0.0中,方法使用注释(annotation)来注册,即在方法的前添加注释
@Subscribe(threadMode = ThreadMode.XXXX)
XXXX处可选填MAIN,ASYNC,BACKGROUND,POSTING。
MAIN:方法将运行在主线程中。
ASYNC:方法将运行在新开辟的子线程中。
BACKGROUND:方法将运行在子线程中,这里分两种情况:1.如果传递事件的线程为主线程,则新开辟一个子线程来运行该方法;2.如果传递事件的线程为子线程,则直接在该子线程中运行。
POSTING:方法将运行在原线程中,无论原线程是主线程或者是子线程,方法将继续在原线程中运行。这也是方法默认的运行模式。
如果要传递事件,直接调用语句:
EventBus.getDefault().post(new Object());
在传递事件的时候,要尤其注意参数的类型。EventBus在判断事件该传递到哪个方法时的判断依据就是方法中的参数类型。而且,每个方法只能包含一个参数,也就是一个参数类型。如果有多个方法包含了同样的参数类型,那么每个方法都会接收到该事件。
在组件运行结束后,我们还要注销掉之前注册的EventBus,官方推荐在onStop中进行,代码也很简单:
EventBus.getDefault().unregister(this);
以上就是一个较为完整的EventBus的使用。由此可见,EventBus运用起来十分简单方便,使用后,代码也会变得更加清晰。但要在使用中注意传递的事件,而且每个方法只能包含一个参数类型。
StickyEvent
有时候,我们想要传递事件到一个组件,但是该组件还未创建和初始化,如果只是像上面那样简单地调用post方法,事件的传递最终将无效化。这时候,我们就可以使用stickyevent :
EventBus.getDefault().postSticky(new Object());
这样就传递了一个StickyEvent。但仅仅是这样还不行,我们还需要在接收的方法处添加 :
@Subscribe(threadMode = XXXX, sticky = true)
由于在EventBus中,方法的sticky属性默认为false,所以我们需要手动设置方法的sticky属性为true,这样方法就能接收StickyEvent了。
事件处理的优先级及事件的拦截
由于每个含有相同参数类型的方法都会接收到该参数类型的事件,EventBus为了区别各方法,设置了priority优先级属性 :
@Subscribe(threadMode = XXXX, sticky = XXXX, priority = XXXX)
priority默认为0,数字越大,代表优先级越高,也就会越早接收到事件。
在方法中,调用如下的语句,优先级低于该方法的其他方法便接收不到该事件了:
EventBus.getDefault().cancelEventDelivery(event);
但是,目前取消事件仅限于用在POSTING模式的方法中。
下面,和大家一起来看一看源码,看一看EventBus是如何实现的:
首先是register方法:
public void register(Object subscriber) {
Class> subscriberClass = subscriber.getClass();
List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
下面我们来追踪一下findSubscribeMethods方法:
List findSubscriberMethods(Class> subscriberClass) {
List subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
if (ignoreGeneratedIndex) {
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;
}
}
程序中分情况分别调用了findUsingReflection方法,和findUsingInfo方法,下面是findUsingReflection方法:
private List findUsingReflection(Class> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findUsingReflectionInSingleClass(findState);
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
还没完,直接看findUsingReflectionInSingleClass方法:
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
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) {
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()));
}
}
} 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");
}
}
}
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) {
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List).
Set, Object>> entries = stickyEvents.entrySet();
for (Map.Entry, Object> entry : entries) {
Class> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
在subscribe方法中,EventBus对注册的方法进行分类注册,其中的subscriptionsByEventType的定义为:
private final Map, CopyOnWriteArrayList> subscriptionsByEventType;
方法根据事件类型的不同,最终保存到Map中。有上面第7行的else语句也可以看出,一个类中注册的事件类型必须是唯一的,即一个类中不能注册多个事件类型相同的方法。
subscribe方法还对priority属性和sticky属性进行了分类:
第14-20行,将priority值高的方法排在List的前面。
第29行开始,对sticky属性为true的方法进行分类判别。
好了,register方法看完了,总结一下,就是通过Annotation找到注册的类中的方法,然后将它们分类,最后保存到一个Map中。
现在,我们就已经领会到EventBus的精髓了,post方法就是根据事件类型,从Map中找到对应的方法,然后依据threadMode,priority,sticky三个属性来执行。
由于篇幅的限制,这里就不再对post方法进行追踪了,大家可以自己来看一看。
最后,再post一个EventBus的新应用:
在EventBus3中,如果在@Subscrible标注的方法中,如果程序出错,不会立即使程序crash,而是由EventBus拦截异常,并打印错误日志。
用户可以通过EventBusBuilder来配置获取EventBus实例后的对象,来决定在处理event时是否需要抛出异常信息:
eventBus = EventBus.builder().sendNoSubscriberEvent(false)
.sendSubscriberExceptionEvent(false)
.throwSubscriberException(BuildConfig.DEBUG) //只有在debug模式下,会抛出错误异常
.build();
以上代码使用Builder设计模式,来构建返回一个eventBus实例。在调试阶段,可以在程序出现异常时直接Crash发现错误。
EventBus源码:https://github.com/greenrobot/EventBus