最近项目中比较频繁的运用到了greenbot公司的eventbus框架,怕使用的时候遇见问题无法解决,所以小子最近也是看了点Eventbus的源码解析,了解了一点运行流程,所以特来和大家分享。
首先在了解之前,我们应该先有针对性的提出一些问题,然后针对问题在源码中去寻找答案,否则Eventbus写的内容很多,漫目查找很容易迷失在源码中。我们可以首先来看以下几个问题。
- Q1.Eventbus有什么好处?我们为什么要去使用它。
- Q2.Eventbus在设计的时候采用了什么设计模式?或者说什么地方让我们觉得它写得好。
- Q3.Eventbus事件注册反注册、发送流程、事件触发流程都是怎样实现的?
- Q4.Eventbus是如何实现事件在多线程中切换触发的?
对于源码的切入,我们一般都从构造方法开始。
文章出自:Eventbus运行流程浅析
构造方法
/**
* Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a
* central bus, consider {@link #getDefault()}.
*/
public EventBus() {
this(DEFAULT_BUILDER); // 构造函数中使用默认的EventbusBuilder
}
EventBus(EventBusBuilder builder) {
subscriptionsByEventType = new HashMap<>(); Value:Subscription(订阅者和订阅方法的类)
typesBySubscriber = new HashMap<>();
stickyEvents = new ConcurrentHashMap<>();
mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
backgroundPoster = new BackgroundPoster(this);
asyncPoster = new AsyncPoster(this);
indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
......
}
可以看到Eventbus采用了Builder模式来进行参数配置的,构造函数中采用了默认的Builder来构造,同时从源码注释中可知,如果自己用一个新的Eventbus实例那么每个实例都会有一个自己的事件分发域,所以如果你想要统一中央分发事件那么可以使用getDefault().我们再来看下getDefault()
是怎么样的。
public static EventBus getDefault() {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) { // DLC
defaultInstance = new EventBus();
}
}
}
return defaultInstance;
}
getDefault采用了传统的DLC方法来创建单例的。并且是使用默认的Builder来构建Eventbus单例的。
了解完Eventbus的构造之后,我们再来回到我们的问题中来,我们可以再从Eventbus事件注册流程入手来解决问题。
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
// 通过订阅者class类找订阅者方法
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
// 方法订阅
subscribe(subscriber, subscriberMethod);
}
}
}
Eventbus注册
注册函数,传入的唯一一个参数是Object类型,也就是订阅者。首先通过订阅者的类来找到订阅方法。接着我们来看下findSubscriberMethods
是怎么实现的。
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
if (ignoreGeneratedIndex) { //默认为false
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;
}
}
这个方法是SubscriberMethodFinder
类实现的,首先是从Map缓存中取,如果存储了订阅者类中的订阅方法就直接返回,如果没有存那就会通过findUsingInfo
来找,因为ignoreGeneratedIndex
属性表示是否忽略注解器生成的MyEventBusIndex,默认为false。找到方法之后便会把方法存储Map缓存中并将订阅者方法返回,我们再来看下findUsingInfo
是如何寻找订阅者方法的。
private List<SubscriberMethod> 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();
}
// 通过线程池回收findState并返回订阅者方法
return getMethodsAndRelease(findState);
}
通过getSubscriberInfo来获取订阅者方法信息。如果我们通过EventbusBuilder配置了MyEventBusIndex,便会获取到subscriberInfo,调用subscriberInfo的getSubscriberMethods方法便可以获取到订阅者方法信息,否则便会通过findUsingReflectionInSingleClass来获取订阅者信息。而EventbusBuilder默认是没有的,所以我们看下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();
// 订阅者方法不是public就抛出异常。
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");
}
}
}
通过反射来获取订阅者方法,我们可以看到如果我们的订阅者方法不是public的,那么就会抛出异常must be public, non-staic, and non-abstract
。然后通过方法注解来获取事件类型、线程类型、优先级、是否粘性等属性。并且封装成SubscriberMethod对象存在一个list存进findState中。
通过反射找到订阅者方法后,再回到findUsingInfo方法中,而后返回getMethodsAndRelease
,该方法中将findState对象回收进对象池中并且返回订阅者方法列表。
到这一步,我们已经知道了注册时找到订阅者中订阅者方法的所有流程,然后接下来在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);
}
}
}
我们看一下subscribe中又做了什么。
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); //1
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); //2
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); //3
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
注释1中通过订阅者类拿到之前该订阅者类中的订阅方法。注释2中,根据方法的优先级将依次插入订阅者方法list中,优先级越高在list中越前面。注释3中如果方法为粘性的,那么调用checkPostStickyEventToSubscription
来触发,方法内是根据postToSubscription
来触发的,我们看下是如何触发的。
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 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);
}
}
可以看到通过4中线程模式来分别触发,但都通过invokeSubscriber
来触发。我们可以看下。
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);
}
}
很明显,是通过java反射来触发粘性事件的。而四种线程模式触发情景也一目了然。
- POSTING:直接触发,触发事件和注册发生在同一线程中。
- MAIN:如果当前线程(注册的线程)在主线程中就直接触发,如果不是那么久将时间插入到队列中通过Handler来将时间在主线程中触发。
- BACKGROUND:如果当前线程是主线程,那么通过将事件插入队列中,然后通过线程池来将事件在新的子线程中触发,如果非主线程则直接触发事件。
- ASYNC:无论当前线程是主线程与否,将事件插入队列中通过线程池在新的子线程中触发。
到这里,我们可以先总结下Eventbus的注册做了哪些事情。
- 1.通过订阅者的class类来找到该订阅者中的方法,并且在找的过程中将订阅者的class类别,订阅者中的订阅者方法,订阅者方法中包含了哪些事件类型都进行了存储缓存。
- 2.拿到订阅者方法后,去进行粘性事件的触发操作。并且,如果粘性事件没有进行remove的话,那么每次订阅者重新订阅的时候都会去进行粘性事件的触发。
Eventbus的注册流程已经大致清除了,而反注册流程其实只是将订阅者注册时存储的list,map表等缓存信息中将该订阅者移除。我们也不再细究。
Eventbus的事件发送
回到开头的问题中,了解了Eventbus的注册和反注册之后,我们再来看下Eventbus的发送流程是怎么样的,也就是post
函数如何。
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;
}
}
}
先从ThreadLocal中拿取当前线程的post状态,并将事件插入到事件队列中。然后通过postSingleEvent
来发送事件,并且将post状态改为true。
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
// eventInheritance表示是否向上查找父类事件,默认为true。
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) {
Log.d(TAG, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
找到所有事件类型的list,例如你post的是子类,那么得到的list中包含该子类以及其所有父类。然后事件会通过postSIngleEventForEventType
发送到每一个类中。
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 = false;
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;
}
这里通过我们之前在粘性事件中提到的postToSubscription
来触发事件。所以说如果发送了某个事件,那么会通知到订阅者中该事件类型的订阅方法,同时也会通知到订阅者中该事件类型所有父类的订阅方法。
到这里我们就已经都清除了Eventbus的构造、注册反注册、事件发送以及事件触发是如何进行的了。那么这也解答了问题3中的问题。同时在分析的过程中我们也能解答出问题1、2。
总结
对于问题1,Eventbus有什么好处,我们为什么要用它?
- 1.Eventbus将订阅者和发送者进行了解耦合操作,订阅者只需要实现注册及订阅方法的实现,而不用去关注何处发送事件。
- 2.Eventbus可以用于组件间通信,并且可以携带大容量数据。在传统的Activity通信的Intent中仅仅允许进行1MB大小的传输,而采用Eventbus却可以携带更大的数据。
- 3.采用了粘性事件和优先级的处理,使事件接收时不需要已注册(粘性事件可以注册时再接收)。
对于问题2,Eventbus采用了什么设计模式以及哪里写的好?
- 1.从之前的分析中我们看到了Eventbus的构造时采用的Builder模式进行构建与显示进行了分析操作
- 2.使用的时候采用了单例模式。
- 3.发送及接收的整体流程其实是一种观察者模式。
- 4.构建findState对象时采用了对象池来创建和回收对象
- 5.线程切换触发事件时采用了
newCachedThreadPool
构建线程池来进行处理。
问题3,事件注册反注册、发送流程、事件触发流程从上面的分析也清楚了。
问题4,是如何实现事件在多线程中切换触发的? Eventbus通过4中线程模式来触发事件,通过Handler及线程池来进行事件在主线程和子线程中的切换,以及线程池将事件弄到子线程中进行触发。