1、事件总线框架设计
站在设计者的角度思考,如果我们要动手设计一个Android事件总线框架,需要注意哪些问题和实现哪些功能呢?
- 既然是事件总线,那么所有订阅者只需要向总线注册自己需要订阅的事件,然后等待相应的事件到来即可;而发布者只需要向总线发布事件而不需要关注谁处理、怎么处理这个事件;
- 线程调度,无论事件是由哪个线程发布的,订阅者总是可以在合适的线程处理该事件;
- sticky粘性事件,订阅着也许需要知道在向总线注册前被发布到总线上的事件,在订阅者向总线注册之后向其发送事件;
- 订阅者可能会有优先级,让优先级高的订阅者先接收到事件。
2、EventBus使用
EventBus使用步骤如下:
-
添加依赖:
implementation 'org.greenrobot:eventbus:3.2.0'
-
设置EventBus。EventBus不是单例,所以可以直接new,也可以使用builder来创建。但是建议使用第三种。如果不需要特殊的设置,这一步可以跳过,后面直接通过EventBus.getDefault()获取默认的EventBus对象即可。
val bus1 = EventBus() val bus2 = EventBus.builder() .eventInheritance(true) .build() val bus3 = EventBus.builder().installDefaultEventBus()
-
创建事件类型,类名和属性视需求而定,没有特殊要求
class Event { }
-
向总线注册订阅者,不需要的时候解注册
/** * 注册订阅者 */ fun register() { EventBus.getDefault().register(this) } /** * 解注册 */ fun unregister() { EventBus.getDefault().unregister(this) } /** * 总是在后台线程处理事件 * 优先级 1000 * 接收粘性事件 */ @Subscribe( threadMode = ThreadMode.BACKGROUND, priority = 1000, sticky = true ) fun onEvent(event: Event) { Log.d("EventBusDemo", "Process event on ${Thread.currentThread()}") }
register和unregister方法接收的参数类型都是Object类型,但是需要注意的是此参数对应的类中至少要有一个@Subscribe注解标记的public方法用于接收事件。
-
发布事件
/** * 发布普通事件 */ fun postEvent() { EventBus.getDefault().post(Event()) } /** * 发布粘性事件 */ fun postStickyEvent() { EventBus.getDefault().postSticky(Event()) }
注意:EventBus除了通过@Subscribe注解来订阅事件外,其实还有一种方法:
public class EventBusIndexTest {
private String value;
/** Ensures the index is actually used and no reflection fall-back kicks in. */
@Test
public void testManualIndexWithoutAnnotation() {
SubscriberInfoIndex index = new SubscriberInfoIndex() {
@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
Assert.assertEquals(EventBusIndexTest.class, subscriberClass);
SubscriberMethodInfo[] methodInfos = {
new SubscriberMethodInfo("someMethodWithoutAnnotation", String.class)
};
return new SimpleSubscriberInfo(EventBusIndexTest.class, false, methodInfos);
}
};
EventBus eventBus = EventBus.builder().addIndex(index).build();
eventBus.register(this);
eventBus.post("Yepp");
eventBus.unregister(this);
Assert.assertEquals("Yepp", value);
}
public void someMethodWithoutAnnotation(String value) {
this.value = value;
}
}
但是此种方法本文不讨论,下面也不作源码分析,因为两者只是形式上稍有差别,本质上并没有什么不同。
3、源码分析
接下来,我们将会从EventBus的订阅流程(register)和发布流程(post/postSticky)入手分析它的工作机制,看看它是怎么实现我们第一节提出的要点的。
以下源码分析基于EventBus 3.2.0版本
3.1 订阅流程
从EventBus#register方法入手
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对象获取相应的订阅方法,然后遍历并调用subscribe方法。
3.1.1 findSubscriberMethods
先看SubscriberMethodFinder#findSubscriberMethods
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
if (ignoreGeneratedIndex) {
// 反射
subscriberMethods = findUsingReflection(subscriberClass);
} else {
// findUsingInfo方法中会判断是否有通过上文所说addIndex的方法注册
// 如果有,使用SubscriberInfoIndex相关方法获取并返回结果
// 如果没有,最终也是调用findUsingReflection(subscriberClass)获取
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:
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findUsingReflectionInSingleClass(findState);
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
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
try {
methods = findState.clazz.getMethods();
} catch (LinkageError error) { // super class of NoClassDefFoundError to be a bit more broad...
String msg = "Could not inspect methods of " + findState.clazz.getName();
if (ignoreGeneratedIndex) {
msg += ". Please consider using EventBus annotation processor to avoid reflection.";
} else {
msg += ". Please make this class visible to EventBus annotation processor to avoid reflection.";
}
throw new EventBusException(msg, error);
}
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) { // 参数个数为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()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { // 遇到@Subscribe标记但参数个数不是1的方法则抛出异常
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)) { // 遇到@Subscribe标记但不是public的非静态方法则抛出异常
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}
这里就是反射获取被@Subscribe注解标记的方法,然后反射父类、父类的父类…直到顶层Object类或反射有异常抛出。
需要注意的是,@Subscribe标记的方法必须:
- 有且仅有一个参数,类型为订阅事件的类型;
- 必须是public的非静态方法
此外,我们看到,反射之后并不是直接返回了java.lang.reflect.Method类型的List,而是SubscriberMethod,看一下这个类的定义:
public class SubscriberMethod {
final Method method;
final ThreadMode threadMode;
final Class<?> eventType;
final int priority;
final boolean sticky;
/** Used for efficient comparison */
String methodString;
public SubscriberMethod(Method method, Class<?> eventType, ThreadMode threadMode, int priority, boolean sticky) {
this.method = method;
this.threadMode = threadMode;
this.eventType = eventType;
this.priority = priority;
this.sticky = sticky;
}
...
}
这个类封装了执行事件处理的java.lang.reflect.Method对象、线程模式的ThreadMode对象、事件类的Class对象、订阅优先级、是否处理粘性事件。
3.1.1 subscribe
在返回来看EventBus#subscribe方法
// 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);
} 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<Class<?>> 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<Class>).
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);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
这里的流程也不复杂,主要做了三件事情:
- 把subscriber和subscriberMethod封装成Subscription对象,按照优先级从大到小的顺序保存到subscriptionsByEventType对应的List里。subscriptionsByEventType是Map<Class<?>, CopyOnWriteArrayList>对象,键为subscriber的类型
- 把事件类的Class存储到typesBySubscriber对应的List。typesBySubscriber是Map<Object, List<Class<?>>>对象,键为订阅者的Class
- 处理粘性事件,如果该订阅方法@Subscribe注解指定sticky为true,则发送stickyEvents中保存的对应的事件。
注意这里的eventInheritance标记位,它代表Event的继承关系的处理。假设有如下代码:
fun postEvent() {
EventBus.getDefault().post(SubEvent())
}
@Subscribe
fun onEvent(event: Event) {
Log.d("EventBusDemo", "Process event on ${Thread.currentThread()}")
}
open class EventClass {
}
class SubEventClass: EventClass() {
}
也就是说订阅的是EventClass,而发布的是子类SubEventClass对象。在这种情况下,eventInheritance如果是true,那么onEvent方法可以接收事件,否则则不能。
3.2 post
EventBus#post方法:
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};
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;
}
}
}
这个方法非常简单,就是获取当前线程的PostingThreadState对象,把新的事件入队。
这里有个疑问,既然是ThreadLocal,那么这个队列的意义在哪里呢?
接下来判断是否正在执行发布流程,不是则进入发布流程,遍历队列并调用postSingleEvent方法。
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
if (eventInheritance) {
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted;
try {
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
postSingleEvent方法做的事情很简单,判断eventInheritance是否为true,如果是,找出事件类以及其父类的订阅者,执行相关方法,否则只执行事件类的订阅者的相关方法而不包括父类。最终调用了postToSubscription方法,这个方法处理了线程调度。
3.3 postSticky
EventBus#postSticky相当简单:
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);
}
与post的唯一区别就是把event保存到了stickyEvents中,然后在register的时候把保存的event发送给订阅者。需要注意的是stickyEvents只会保存最后一个event而不是所有的历史event。
此外,由于stickyEvents持有的是强引用,使用时需要注意内存泄漏的问题。
4、总结
EventBus的源码实际上非常简单,基本上就是注解加反射。作为一个事件总线框架,功能足够强大了。但是EventBus并没有做跨进程的相关支持,如果跨进程需求需要AIDL或者选用其他事件总线框架。