EventBus是安卓(Java中也可以用)开发中非常流行的一个第三方库,是一种发布/订阅事件的总线.拥有以下特性:
现在的EventBus早已经从2.0进入3.0版本,而且最新版3.1.1也已经稳定了挺长的时间,从17年底再没有更新过GitHub.因为之前的项目中集成的都是3.0.0
版本的,因此以该版本源码做分析.常见的使用方法就不介绍了,官方文档里都有.所有操作,包括发送事件、在订阅事件的组件中注册
和解绑,都是通过EventBus.getDefault()
创建一个单例对象,完成后续方法的调用.
先看注册事件的部分,因为EventBus通常的使用顺序就是接收方注册->发送方定义发送事件->接收方定义接受入口->发送方发送事件->收到事件。当然从数据流向看先从发送事件开始也是可以理解的:
源码:
public void register(Object subscriber) {
//注册事件所在的类
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
其中的subscriberMethodFinder是SubscriberMethodFinder类型对象,在EventBus的默认构造时初始化。通过findSubscriberMethods方法可以找到该类中添加了Subscribe注解的方法。只有添加了该注解的方法(方法名可以任意)才可以收到事件。如果该类没有添加过注解,抛异常。
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
List<SubscriberMethod> 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;
}
}
METHOD_CACHE是Map
类型,它存储的value是该类中所有添加了Subscribe注解的方法(被包装为SubscriberMethod类)的集合。该类第一次注册事件的时候集合为null,默认ignoreGeneratedIndex为false,会走到方法findUsingInfo.而在该方法内部,默认的情况下经过一系列逻辑判断又会走到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()));
}
}
}
}
}
前面的操作已经将该类封装到了静态内部类FindState中,作为clazz字段。这里重新加工要使用的FindState的步骤是:
Map
。订阅事件的类如果只定义了一个符合要求的接收事件的方法,那么以方法接收的事件类为key存储该方法。private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
methodKeyBuilder.setLength(0);
methodKeyBuilder.append(method.getName());
methodKeyBuilder.append('>').append(eventType.getName());
String methodKey = methodKeyBuilder.toString();
Class<?> methodClass = method.getDeclaringClass();
Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
// Only add if not already found in a sub class
return true;
} else {
// Revert the put, old class is further down the class hierarchy
subscriberClassByMethodKey.put(methodKey, methodClassOld);
return false;
}
}
methodKeyBuilder是一个StringBuilder对象。可以看出,每次调用该方法都将重置它,并且以方法名+'>'+事件类型
作为键值。subscriberClassByMethodKey是一个Map
对象,存储的value为接受事件的方法所在类。只有该Map存在重复键时该方法才会返回false,但是我们定义的键值,因为同一个类中不会出现同名同参数类型同参数个数的方法,所以不会重复,返回true。
额外话:该方法什么情况会返回false?假设有一个类ActivityA,其中定义了订阅和接受事件的方法,另外有一个类ActivityB继承了A作为页面展示,并重写了其中的订阅方法。这时候在findUsingInfo方法中的while循环体内,会继续向B的父类A调用。这时的subscriberClassByMethodKey相同的键对应的value就不为空了,并且由于A、B之间 继承关系,方法会走入else分支返回false。最终抛出IllegalStateException。因此要注意派生关系在订阅事件的应用
继续向下,出现了接受同一事件的多个方法,这时anyMethodByEventType的value用任意的对象代替以“消费”该方法,源码中直接取的FindState的当前对象。接着又一次调用了checkAddWithMethodSignature方法。前面说过了因为键不会重复的原因,因此最终返回true。该接受事件方法校验通过。也就是说,在同一个类中定义多个接受同一事件的方法,那么这些方法都会执行。
上面的方法调用完毕后就获取到了订阅事件方法的集合,并且将其缓存到METHOD_CACHE中,这样同一个类多次注册事件时就不必做重复的大量逻辑判断。这时候就可以遍历该集合,正式开始事件的订阅了。
精简代码:
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
}
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<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
//后面分析stick事件
}
将订阅事件的类和订阅方法包装成一个Subscription对象。CopyOnWriteArrayList相当于一个线程安全的ArrayList,其实就是内部实现的方法上加了锁。subscriptionsByEventType是类型为Map
, value为CopyOnWriteArrayList
的对象,会以事件类型为键存储所有订阅该事件的方法集合,根据@SubScribe的priority,优先级高的会添加到头部,保证了多个订阅方法的执行顺序。typesBySubscriber是类型为Map
的对象,用于存储该订阅类中订阅的所有事件类型
接收方注册了事件,订阅了事件,就来看发送方发布事件的代码.最常用的post方法,其中的参数event就是我们在要发送事件的地方自己定义的一个对象,通常是一个静态内部类:
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
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;
}
}
}
首先在EventBus第一次被初始化的时候创建了一个ThreadLocal类的对象currentPostingThreadState,其泛型形参为EventBus中的静态内部类PostingThreadState.在post的时候会获取该内部类,从而获取到其内部的eventQueue(事件队列,用ArrayList存储),并且将要发送的事件(抽象为发送方自己定义的对象)加入到该队列中.isPosting可以避免并发情况下一个Event的多次被调用.判断事件不处于发送中的状态后,开始依次从队列中的头部取出事件发送,直到队列为空.可以看出,发送事件的同时会将事件移除出队列.发送单个事件的方法为postSingleEvent。
忽略一些逻辑判断,核心代码是:
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
postSingleEventForEventType(event, postingState, eventClass);
}
看到这个方postSingleEventForEventType,该方法内部的逻辑是:如果对应事件类型的订阅方法存在,那么会为每个订阅方法调用postToSubscription。它会比较发送事件和订阅事件各自所在的线程来决定是否立即执行。符合条件的话,有订阅类,有事件类型,最终会反射调用Method的invoke方法,也就是我们接受到了事件开始处理了。这样整个发送->事件流也就结束了。
EventBus定义的一个枚举。我们通过订阅方法上添加的Subscribe注解就可以设置这个值。它代表了订阅方法在哪类线程被EventBus调用。设计之初就是让发送和接收事件的线程独立开来。共定义了四种线程:
默认。订阅事件和发送事件处在同一线程。最常用的是这种,它可以避免切换线程的损耗。但由于发送事件可能在主线程中,因此接收事件的方法不能做耗时操作,否则会阻塞线程。
接收事件总会在主线程(UI线程)中,也要避免耗时操作。如果发送事件在子线程,则接收事件会放到MessageQueue中等待执行
接收事件总会在子线程中。如果发送事件也在子线程中,那么接收事件应立即在发送事件所在的线程中执行。如果发送在主线程中,接收事件运行在EventBus维护的单一子线程中,因为还可能存在其他接收事件的方法,所以也要避免阻塞。
接收事件在一个独立的线程中执行,独立于发送方所在线程和主线程。适合做耗时操作,如网络请求。EventBus内部使用了线程池来复用这些异步的接收事件方法所在线程,但还是要避免同一时间内大量触发此类型方法。
前面提到,如果接收和发送所处的线程不一样,会将事件先放到队列中等待执行。有这么三种情况:
加入到HandlerPoster,这个类继承了Handler,其实就是在enque方法中调用了sendMessage,又在handleMessage中调用了EventBus的invokeSubscriber方法,完成了接受事件方法的调用。
加入到BackgroundPoster,这个类实现类Runnable,在enque方法中找到了EventBus全局定义的一个默认的线程池:Executors.newCachedThreadPool(),获取了一个无数量上限,无核心线程,可以复用空闲线程的线程池。enque方法加锁,而且全局定义了一个volatile类型变量,保证了任一时间只有一个任务被执行,也就是前面说的单一子线程。在run方法中调用了invokeSubscriber。
加入到AsyncPoster,它也实现Runnable。但不同于BackgroundPoster,它没有加锁和保证有序性,因此等待被执行的任务只要放入线程池就行了。
假设有这种场景,我在登录页面输入用户名和密码后跳转到其他的一些界面,相应的页面都要有所变化。那这时候发送事件的时候因为新打开的页面还没有初始化,还没有完成订阅事件,subscriptionsByEventType里不存在value,也就无法接收到事件。这时候就会用到postSticky.
/**
* Posts the given event to the event bus and holds on to the event (because it is sticky). The most recent sticky
* event of an event's type is kept in memory for future access by subscribers using {@link Subscribe#sticky()}.
*/
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
// Should be posted after it is putted, in case the subscriber wants to remove immediately
post(event);
}
和普通的发送事件相比,就是同步将要发送的事件类型和事件对象存储到了stickyEvents中。然后到了订阅事件时,会走到subsribe方法的该分支,默认精简如下:
if (subscriberMethod.sticky) {
// 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<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
取出之前的stickyEvents,遍历其键值对。校验发送的和接受的是同一事件后,判断如果存储的事件对象不为空,则调用postToSubscription方法。这样也就保证了即使发送事件在先,注册事件在后,也可以接收到事件。注意的是接受事件的方法的Subscribe注解中的sticky要置为true.
/** Unregisters the given subscriber from all event classes. */
public synchronized void unregister(Object subscriber) {
List<Class<?>> 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());
}
}
取消订阅的方法中,首先移除了所有订阅了该事件的方法集合中该类中所定义的方法。然后移除了该类中所有的订阅事件类型。
EventBus作为部分替代BroadcastReceiver和接口回调的一款知名框架,本身是基于观察者模式实现的,实现原理总结了有下面几个核心要点: