The first half is mainly about the analysis of EventBus3.0 architecture. Next, we start the source code analysis of EventBus3.0.
We start with the source code analysis of EventBus3.0 usage, and first analyze the registration events~
1. Analysis of registered source code
To register an event:
EventBus.getDefault().register(this);
getDefault() of EventBus is a single instance, ensure that there is only one instance of EventBus object
public static EventBus getDefault() { EventBus instance = defaultInstance; if (instance == null) { synchronized (EventBus.class) { instance = EventBus.defaultInstance; if (instance == null) { instance = EventBus.defaultInstance = new EventBus(); } } } return instance; }
What does the EventBus constructor do?
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder(); public EventBus() { this(DEFAULT_BUILDER); }
In the EventBus nonparametric construction method, a construction method with one parameter is called. The parameter is passed in the EventBusBuilder object, which configures the default properties related to EventBus.
EventBus(EventBusBuilder builder) { logger = builder.getLogger(); subscriptionsByEventType = new HashMap<>(); typesBySubscriber = new HashMap<>(); stickyEvents = new ConcurrentHashMap<>(); mainThreadSupport = builder.getMainThreadSupport(); mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null; backgroundPoster = new BackgroundPoster(this); asyncPoster = new AsyncPoster(this); indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0; subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes, builder.strictMethodVerification, builder.ignoreGeneratedIndex); logSubscriberExceptions = builder.logSubscriberExceptions; logNoSubscriberMessages = builder.logNoSubscriberMessages; sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent; sendNoSubscriberEvent = builder.sendNoSubscriberEvent; throwSubscriberException = builder.throwSubscriberException; eventInheritance = builder.eventInheritance; executorService = builder.executorService; }
These properties can be changed by configuring EventBusBuilder
EventBus.builder() .logNoSubscriberMessages(false) .sendNoSubscriberEvent(false) .installDefaultEventBus();
Next, we will analyze an important method of EventBus registration, register, and first look at its source code
public void register(Object subscriber) { // Get the Class object of the current registered Class Class<?> subscriberClass = subscriber.getClass(); // Find all methods marked by @ Subscribe annotation in the current registered class List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { // Traverse all subscription methods to complete registration for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } }
From the source code of the register method, we can see that the main task is to find and register the subscription method. The search of the subscription method is done by findSubscriberMethods, and the registration is done by subscribe.
First look at the source code implementation of findSubscriberMethods
// Cache subscription method, key: class value of current registered class: subscription method collection private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>(); List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { // First, get the subscription method of the current registered class from the cache. If found, directly return List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } // Ignore generated index defaults to false // Main function: whether to ignore the subscription index generated by apt (annotation processor) if (ignoreGeneratedIndex) { // If ignored, use reflection technology to find all subscription methods marked by @ Subscribe annotation subscriberMethods = findUsingReflection(subscriberClass); } else { // By default, it is not ignored, but if the subscription index generated by apt (annotation processor) is not used, it is still searched by reflection technology subscriberMethods = findUsingInfo(subscriberClass); } // If there is no subscription method in the current registered class, an exception is thrown if (subscriberMethods.isEmpty()) { throw new EventBusException("Subscriber " + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation"); } else { // There is a subscription method in the current registration class, which is added to the cache for direct use when registering again METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; } }
The main task of findSubscriberMethods is very clear. First, get the subscription method of the current registered class from the cache. If it is found, return it directly. If it is not found, find all the subscription methods of the current registered class through apt (annotation processor) or reflection technology. If it is not found, throw an exception. Otherwise, cache it in memory and return it to the current registered class All subscription methods.
The findUsingInfo method is used to find all the subscription methods in the current registered class
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) { // FindState FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { // If the apt (annotation processor) is not used to generate the subscription method index and null is returned, it will enter the else statement 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 { // Use reflection technology to find all subscription methods in the current registered class findUsingReflectionInSingleClass(findState); } // Continue searching from the parent until the parent is null findState.moveToSuperclass(); } // Return all subscription methods in the registration class, release the state in findState, and put the findState object back into the cache pool return getMethodsAndRelease(findState); }
The findUsingInfo method mainly looks up all subscription methods from the current registered class and parent class. First, it looks up from the index of subscription methods generated through apt (annotation processor). If not, it looks up through reflection technology.
Let's first look at how to search through reflection technology? The source code of the key method findusingreflectioningsingleclass is as follows:
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(); // Method must be public and cannot be ABSTRACT or STATIC if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { Class<?>[] parameterTypes = method.getParameterTypes(); // Method must have one parameter if (parameterTypes.length == 1) { Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); // Method must be marked with @ Subscribe annotation if (subscribeAnnotation != null) { // Method parameter type Class<?> eventType = parameterTypes[0]; // Used to determine whether the method has been added 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"); } } }
The search of the subscription method has been analyzed, and then the source code of the subscribe method has been analyzed
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { // Subscription event type, that is, the parameter type of subscription method Class<?> eventType = subscriberMethod.eventType; // Subscription encapsulates the object and subscription method of the current registered class Subscription newSubscription = new Subscription(subscriber, subscriberMethod); // subscriptionsByEventType is a Map, key is the Subscription event type, and value is the Subscription collection 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); } } // Add new subscription to the subscriptions collection 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; } } // typesBySubscriber is also a Map. key is the object of the registration class, and value is the parameter type collection of all the subscription methods in the current registration class List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); // By default, sticky is false, which means it's not a sticky event. The following code doesn't look at it first, and then analyzes it when it comes to sticky events 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); } } }
Two maps of subscriptionsByEventType and typesBySubscriber in the subscribe method. Used in event Publishing
The subscriptionsByEventType is used to process events. When canceling the registration, the two maps, subscriptionsByEventType and typesBySubscriber, will be used. Later, we will analyze them in detail
2. Unregister source code analysis
Deregistration method:
EventBus.getDefault().unregister(this);
Unregister calls the unregister method of EventBus. Here is its source code
public synchronized void unregister(Object subscriber) { // typesBySubscriber is a Map. When registering, it has put the key as the object of the current registered class and the value as the parameter type of all subscription methods of the current registered class into the current Map List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber); // If subscribedTypes is not null, the current class is registered if (subscribedTypes != null) { for (Class<?> eventType : subscribedTypes) { // Unsubscribe according to eventType unsubscribeByEventType(subscriber, eventType); } // Remove registered objects from Map typesBySubscriber.remove(subscriber); } else { logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass()); } }
From the unregister method source code, first find the event types of all subscription methods from the typesBySubscriber cache according to the objects of the current unregistered class, and then cancel the subscription according to the event types. Next, look at the unsubscribeByEventType source code:
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) { // subscriptionsByEventType is also a Map. When registering, you have put the key as the Subscription event type and the value as the Subscription object collection into the current Map List<Subscription> 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; // Remove Subscription subscriptions.remove(i); i--; size--; } } } }
From the analysis of unregistered source code, we can see that it is mainly to remove the registered class object and remove the subscription method from the two maps of typesBySubscriber and subscriptionsByEventType.
3. Publish common events
You can publish common events in the following ways:
EventBus.getDefault().post("hello, eventbus!");
You can see that the publishing event is completed through the post method
public void post(Object event) { // currentPostingThreadState is a ThreadLocal of type PostingThreadState // PostingThreadState maintainer event queue and thread model PostingThreadState postingState = currentPostingThreadState.get(); List<Object> eventQueue = postingState.eventQueue; // Add the event to be sent to the event queue first eventQueue.add(event); // isPosting defaults to false if (!postingState.isPosting) { // Is it the main thread postingState.isMainThread = isMainThread(); postingState.isPosting = true; if (postingState.canceled) { throw new EventBusException("Internal error. Abort state was not reset"); } try { // Loop through the event queue and publish events from the head of the queue while (!eventQueue.isEmpty()) { postSingleEvent(eventQueue.remove(0), postingState); } } finally { postingState.isPosting = false; postingState.isMainThread = false; } } }
The post method first adds the event to the event queue, and then circulates the event queue to the postSingleEvent method for processing
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<?> eventClass = event.getClass(); boolean subscriptionFound = false; // eventInheritance defaults to true, indicating the inheritance class of the lookup event if (eventInheritance) { // Find this event and the collection of events it inherits 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 not registered, subscriptionFound is false if (!subscriptionFound) { if (logNoSubscriberMessages) { // logNoSubscriberMessages defaults to true logger.log(Level.FINE, "No subscribers registered for event " + eventClass); } if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event)); } } }
The postSingleEvent method looks up the event and its inherited events and hands them over to the postSingleEventForEventType method for processing
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; synchronized (this) { // When subscriptionsByEventType is registered, it will add events and subscription methods subscriptions = subscriptionsByEventType.get(eventClass); } // If not registered, subscriptions will be null if (subscriptions != null && !subscriptions.isEmpty()) { for (Subscription subscription : subscriptions) { postingState.event = event; postingState.subscription = subscription; boolean aborted = false; try { // event processing 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; }
In the postSingleEventForEventType, all subscription methods of the event will be found from the registry subscriptionsByEventType and handed to the postToSubscription method for processing
4. Thread switching
Finally, the event processing is finished. The thread processing will be switched according to the ThreadMode thread mode
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { // According to the thread mode of the subscription method, the event needs to be processed in which thread switch (subscription.subscriberMethod.threadMode) { // In the default thread mode, events are processed in which thread when they are published case POSTING: invokeSubscriber(subscription, event); break; // Require events to be processed on the main thread case MAIN: // If the publishing event is in the main thread, execute directly if (isMainThread) { invokeSubscriber(subscription, event); } else { // If the publishing event is in the sub thread, first put the event into the queue, and then switch to the main thread for execution through the Handler mainThreadPoster.enqueue(subscription, event); } break; // Require events to be processed on the main thread case MAIN_ORDERED: // No matter which thread the publishing event is on, the event will be put into the queue, and then the Handler will switch to the main thread for execution if (mainThreadPoster != null) { mainThreadPoster.enqueue(subscription, event); } else { // temporary: technically not correct as poster not decoupled from subscriber invokeSubscriber(subscription, event); } break; // Require events to be processed in a background thread case BACKGROUND: // If the publishing event is in the main thread, first put the event into the queue, and then drop it to the thread pool for processing, serial processing if (isMainThread) { backgroundPoster.enqueue(subscription, event); } else { // If the publishing event is in a sub thread, execute directly invokeSubscriber(subscription, event); } break; // Require events to be processed on asynchronous threads case ASYNC: // No matter which thread the publishing event is on, it will be put into the queue first, and then it will be put into the thread pool for processing and parallel processing asyncPoster.enqueue(subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); } }
In fact, thread switching is based on the thread model of subscription event method and the thread of publishing event. There are two ways to deal with it: one is to execute subscription event method directly through invokeSubscriber method in the corresponding thread, using reflection technology
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); } }
Another is to put the event into the queue (the bottom layer is the implementation of the two-way linked list structure), and then hand it to the Handler or thread pool for processing. Take the ASYNC thread model as an example. AsyncPoster is an instance of AsyncPoster class
class AsyncPoster implements Runnable, Poster { private final PendingPostQueue queue; private final EventBus eventBus; AsyncPoster(EventBus eventBus) { this.eventBus = eventBus; // PendingPostQueue is a two-way linked list queue = new PendingPostQueue(); } public void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); // Add the end of the list queue.enqueue(pendingPost); // Directly throw to thread pool for processing eventBus.getExecutorService().execute(this); } @Override public void run() { PendingPost pendingPost = queue.poll(); if(pendingPost == null) { throw new IllegalStateException("No pending post available"); } eventBus.invokeSubscriber(pendingPost); } }
First of all, we will analyze viscous events, subscription indexes, AsyncExecutor, etc