This is the 13th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

Introduction to the

EventBus is the publish/subscribe EventBus for Android and Java. Uncoupling avoids callback hell and is simple and easy to use

Method of use

Define events
public static class MessageEvent { /* Additional fields if needed */ }
Copy the code
Define subscription methods to handle received events
@Subscribe(threadMode = ThreadMode.MAIN)  
public void onMessageEvent(MessageEvent event) {/* Do something */};
Copy the code

Register and deregister activities and Fragments according to their life cycles.

@Override
 public void onStart() {
     super.onStart();
     EventBus.getDefault().register(this);
 }

 @Override
 public void onStop() {
     super.onStop();
     EventBus.getDefault().unregister(this);
 }
Copy the code
Send the event
EventBus.getDefault().post(new MessageEvent());
Copy the code

This section describes how EventBus works

The Subscribe annotations
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event){
    Log.d("arrom",event.toString());
}
Copy the code

A concrete implementation of the Subscribe annotation

@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Subscribe { // POSTING ThreadMode ThreadMode () Default threadmode.posting; POSTING; POSTING; // Support sticky events, Boolean sticky() default false; Int priority() default 0; // Set the priority of the event subscription method. }Copy the code

The threadMode attribute has the following optional values:

  • Threadmode. POSTING is the default thread schema for POSTING events in one thread and processing events in the other thread.
  • Threadmode. MAIN, such as sending events on the MAIN thread (UI thread), handles events directly on the MAIN thread; If an event is sent in a child thread, it is first queued and then switched to the main thread by Handler, which processes the event in turn.
  • Threadmode. MAIN_ORDERED, regardless of which thread sends the event, queues the event first and then switches to the main thread via Handler, processing the event in turn.
  • Threadmode. BACKGROUND, if an event is sent on the main thread, the event is queued and then processed through the thread pool. If an event is sent in a child thread, the event is processed directly in the thread that sent the event.
  • Threadmode. ASYNC, regardless of which thread sends the event, enqueues it and processes it through the thread pool.
Register event
EventBus.getDefault().register(this);
Copy the code

Go to the getDefault method

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;
}
Copy the code

GetDefault () is a singleton method that guarantees that there is currently only one EventBus instance

public EventBus() { this(DEFAULT_BUILDER); } 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; }Copy the code

Initialization of related properties

private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
Copy the code

DEFAULT_BUILDER is the default EventBusBuilder.

We can also change the properties of EventBus by configuring EventBusBuilder.

EventBus.builder()
        .eventInheritance(false)
        .logSubscriberExceptions(false)
        .build()
        .register(this);
Copy the code

Enter the register method

Public void register(Object subscriber) {// Get the Class Object <? > subscriberClass = subscriber.getClass(); // SubscriberMethod Class SubscriberMethod Class SubscriberMethod Class SubscriberMethod Class // Method object, thread mode, event type, priority, stickiness, etc subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : // Loop through the collection of methods subscribed to the event to subscribe(subscribe, subscriberMethod); }}}Copy the code

The register() method is divided into two parts: lookup and registration.

To find the
List<SubscriberMethod> findSubscriberMethods(Class<? > subscriberClass) {// METHOD_CACHE is a ConcurrentHashMap that directly stores the collection of subscriberClass and corresponding SubscriberMethod to improve registration efficiency and assign values to repeated lookup. List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods ! = null) { return subscriberMethods; } // Since the default EventBusBuilder is used, the ignoreGeneratedIndex property defaults to false, That is whether to ignore the annotation generator if (ignoreGeneratedIndex) {subscriberMethods = findUsingReflection(subscriberClass); } else { subscriberMethods = findUsingInfo(subscriberClass); } // If there is no method in the corresponding class, Will throw an exception if (subscriberMethods isEmpty ()) {throw new EventBusException subscriberClass (" Subscriber "+ +" and its super classes have no public methods with the @Subscribe annotation"); } else {// A method to save found subscription events method_cache. put(subscriberClass, subscriberMethods); return subscriberMethods; }}Copy the code

The findUsingInfo() method is called after the collection is cached. The findUsingInfo() method is called after the collection is cached.

private List<SubscriberMethod> findUsingInfo(Class<? > subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); // Findstate. clazz is the 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 {/ / subscription via the reflection events of method findUsingReflectionInSingleClass (findState); } / / modify findState. Clazz for subscriberClass parent Class, which need to traverse the superclass findState. MoveToSuperclass (); } // The found methods are stored in the subscriberMethods collection of the FindState instance. Return getMethodsAndRelease(findState); return getMethodsAndRelease(findState); return getMethodsAndRelease(findState); }Copy the code

The findUsingInfo() method looks for the method that subscribed to the event in the class currently being registered and its parent, and there is a FindState class that is an internal class to the SubscriberMethodFinder that AIDS in finding the method that subscribed to the event

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) {// Get the type of all the arguments to the current method Class<? >[] parameterTypes = method.getParameterTypes(); // If (parametertypes. length == 1) {Subscribe subscribeAnnotation = method.getannotation (Subscribe. Class);  // Subscribe if (Subscribe! = null) {// Get the type of this parameter Class<? > eventType = parameterTypes[0]; The checkAdd() method is used to check whether the FindState anyMethodByEventType map has been added with the current eventType as the key pair. Return true if (findState.checkAdd(method, eventType)) {Subscribe threadMode attribute, The thread pattern ThreadMode ThreadMode = subscribeAnnotation. ThreadMode (); // Create a SubscriberMethod object, And added to the collection subscriberMethods 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"); }}}Copy the code

It looks for methods to subscribe to events primarily through reflection. The findSubscriberMethods() process in this Register () method completes the analysis, and we have found the collection of methods that subscribe to events in the currently registered class and its parent class

registered
Private void subscribe(Object subscriber, SubscriberMethod SubscriberMethod) {// Get the parameter type of the method currently subscribed to the event Class<? > eventType = subscriberMethod.eventType; // The Subscription class holds the class object to be registered and the current subscriberMethod Subscription newSubscription = newSubscription (subscriber, subscriberMethod); SubscriptionsByEventType is a HashMap, // subscriptionsByEventType = subscriptionsByEventType; // subscriptionsByEventType = subscriptionsByEventType CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); // If it does not exist, create a SUBSCRIPTIONS, And save to subscriptionsByEventType 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 newSubscription object to subscriptions int size = subscription.size (); for (int i = 0; i <= size; i++) { if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) { subscriptions.add(i, newSubscription); break; }} // typesBySubscribere is a HashMap that stores a key pair of the Class to be registered and a set of parameter types of the methods that subscribe to events in the registered Class. >> subscribedEvents = typesBySubscriber.get(subscriber); // Create a subscribedEvents and save it to typesBySubscriber if (subscribedEvents == null) {subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } // Save the parameter type of the method that is currently subscribed to the event subscribedevents.add (eventType); If (subscriberMethod.sticky) {if (eventInheritance) {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); }}}Copy the code

Subscribe () gets two hashmaps: subscriptionsByEventType and typesBySubscriber. Among them, subscriptionsByEventType is used when sending events to complete the processing of events. When canceling EventBus registration, typesBySubscriber and subscriptionsByEventType are used to release related resources.

Cancel the registration
Public synchronized void unregister(Object subscriber) {List<Class<? >> subscribedTypes = typesBySubscriber.get(subscriber); if (subscribedTypes ! = null) {// Iterate over the set of parameter types, freeing the Subscription for (Class<? > eventType : subscribedTypes) { unsubscribeByEventType(subscriber, eventType); } // Delete the pair of typesbysubscriber.remove (subscriber); } else { logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass()); }}Copy the code

The unsubscribeByEventType() method is called

private void unsubscribeByEventType(Object subscriber, Class<? > eventType) {/ / get the current parameter types corresponding collection Subscription List < Subscription > subscriptions = subscriptionsByEventType. Get (eventType); if (subscriptions ! = null) { int size = subscriptions.size(); // Subscription set for (int I = 0; i < size; i++) { Subscription subscription = subscriptions.get(i); // If the current subscription object corresponds to the same registered class object as the unregistered class object, If (subscription == subscriber) {subscription. Active = false; subscriptions.remove(i); i--; size--; }}}}Copy the code

In the unregister() method, the main thing is to release the resources cached in typesBySubscriber and subscriptionsByEventType.

Send the event
Public void post (Object event) {/ / currentPostingThreadState is a ThreadLocal PostingThreadState type / / PostingThreadState class information such as thread and preserved the event queue model PostingThreadState postingState = currentPostingThreadState. The get (); List<Object> eventQueue = postingState.eventQueue; // Add events to the event queue eventqueue.add (event); // isPosting defaults to false if (! Whether postingState. IsPosting) {/ / give priority to the thread postingState. IsMainThread = isMainThread (); postingState.isPosting = true; if (postingState.canceled) { throw new EventBusException("Internal error. Abort state was not reset"); } try {// traverse the event queue while (! Eventqueue.isempty ()) {// Send a single event // eventQueue.remove(0), remove event postSingleEvent(eventQueue.remove(0), postingState);  } } finally { postingState.isPosting = false; postingState.isMainThread = false; }}}Copy the code

The POST () method saves the sent event to the event queue of the List, and then loops it out of the queue to the postSingleEvent() method for processing.

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<? > eventClass = event.getClass(); boolean subscriptionFound = false; If (eventInheritance) {// Search for the Class of the current event type and save it with the Class of the current event type into the collection 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)); }}}Copy the code

PostSingleEvent () method, according to the eventInheritance properties, decide whether to traverse upward parent types of events, and then use postSingleEventForEventType further processing events () method.

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<? > eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; Synchronized (this) {/ / to get event types corresponding to the Subscription set subscriptions = subscriptionsByEventType. Get (eventClass); } // If (subscriptions! = null && ! Subscription.isempty ()) {for (Subscription Subscription: subscriptions) {// Record events postingstate.event = event; / / record the corresponding subscription postingState. Subscription = subscription; boolean aborted = false; Try {/ / final event handling 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; }Copy the code

PostSingleEventForEventType () method of the core is the event type corresponding traversal send the Subscription set, and then call postToSubscription process events () method.

Handle events
private void postToSubscription(Subscription subscription, Object event, Boolean isMainThread) {/ / determine the subscription event method thread mode switch (subscription. SubscriberMethod. ThreadMode) {/ / the default mode of thread, Case POSTING: invokeSubscriber(subscription, event); break; If (isMainThread) {invokeSubscriber(subscription, event); } else {// If the event is sent in the child thread, the event is queued, // mainThreadPoster is not null mainThreadPoster. Enqueue (subscription, event); } break; // Regardless of which thread sends the event, the event is first queued and then switched to the main thread by Handler, which processes the event in turn. // mainThreadPoster not empty case MAIN_ORDERED: if (mainThreadPoster! = null) { mainThreadPoster.enqueue(subscription, event); } else { invokeSubscriber(subscription, event); } break; case BACKGROUND: If (isMainThread) {backgroundPoster. Enqueue (subscription, event); // If (isMainThread) {backgroundPoster. } else {// If an event is sent in a child thread, invokeSubscriber is handled by reflection directly in the sending thread (subscription, event); } break; // Regardless of which thread sends the event, the event is queued and processed by the thread pool. case ASYNC: asyncPoster.enqueue(subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); }}Copy the code

The postToSubscription() method determines how an event is handled based on the pattern of the thread that subscribes to the event method and the thread that sent the event. There are two main ways to handle the event: One way is to use reflection to execute subscribed events directly in the corresponding thread through the invokeSubscriber() method, so that sent events are received by the subscriber and processed accordingly.

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); }}Copy the code

If an event is sent in a child thread, the event is handled directly by reflection in the thread that sent the event

MainThreadPoster. Enqueue (subscription, event) ¶ mainThreadPoster. Enqueue (mainThreadPoster. MainThreadPoster is an instance of the HandlerPoster class.

public class HandlerPoster extends Handler implements Poster { private final PendingPostQueue queue; private boolean handlerActive; . public void enqueue(Subscription subscription, PendingPost PendingPost = PendingPost.obtainPendingPost(subscription, event); Synchronized (this) {queue. Enqueue (pendingPost); if (! handlerActive) { handlerActive = true; The handleMessage() method is executed, completing the switch from child thread to main thread. If (! sendMessage(obtainMessage())) { throw new EventBusException("Could not send handler message"); } } } } @Override public void handleMessage(Message msg) { boolean rescheduled = false; try { long started = SystemClock.uptimeMillis(); PendingPost PendingPost = queue.poll(); PendingPost = queue.poll(); . / / further processing pendingPost eventBus. InvokeSubscriber (pendingPost); . } } finally { handlerActive = rescheduled; }}}Copy the code

The enqueue() method for HandlerPoster encapsulates the subscription and event objects into a PendingPost object, which is stored in a queue and then switched to the main thread by a Handler. The PendingPost object is looped out of the queue in the handleMessage() method and handed over to the invokeSubscriber() method for further processing.

void invokeSubscriber(PendingPost pendingPost) { Object event = pendingPost.event; Subscription subscription = pendingPost.subscription; / / release pendingPost reference resources pendingPost. ReleasePendingPost (pendingPost); If (subscription. Active) {invokeSubscriber(subscription, event); }}Copy the code

Fetching previously saved events from The pendingPost, subscription, and then using reflection to execute the subscription is back to the first approach. So the core of mainThreadposter.enqueue (subscription, event) is to queue the event and then switch it from the child thread to the main thread by a Handler.

Backgroundposter.enqueue () and asyncposter.enqueue are similar in that events are enqueued and then unqueued internally, but further processed through the thread pool.

Viscous event

Typically, we use EventBus to prepare to subscribe to events, then register the events, and finally send the events, that is, have the recipient of the event first. Sticky events, on the other hand, can be sent first, followed by methods for subscribing to events and registering events.

EventBus.getDefault().postSticky(event);
Copy the code

Enter postSticky source code

public void postSticky(Object event) {
    synchronized (stickyEvents) {
        stickyEvents.put(event.getClass(), event);
    }
    post(event);
}
Copy the code

The ostSticky() method does two main things:

  • First, save the event type and corresponding event to stickyEvents for subsequent use;
  • It then executes post(event) to continue sending the event, which is the same post() method that was sent earlier.

Therefore, if there is a subscriber of the corresponding type of event before sending the sticky event, it can still receive the sent sticky event even if it is non-sticky.

Registration of sticky events
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { ...... . . // If (Subscribe method.sticky) {// If (Subscribe method.sticky) {// Default is true, If (eventInheritance) {// stickyEvents save the event type and the corresponding event when sending a sticky event Set< map.Entry <Class<? >, Object>> entries = stickyEvents.entrySet(); for (Map.Entry<Class<? >, Object> entry : entries) { Class<? > candidateEventType = entry.getKey(); / / if candidateEventType is a subclass of eventType or if (eventType. IsAssignableFrom (candidateEventType)) {/ / get the corresponding event Object stickyEvent = entry.getValue(); / / handling viscous checkPostStickyEventToSubscription (newSubscription stickyEvent); } } } else { Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); }}}Copy the code

StickyEvents are handled by iterating through stickyEvents during EventBus registration. If the currently registered event subscription method is stickyEvents and receives an event of the same type as one of the stickyEvents or its parent class, Extract the specific event of the corresponding event type in stickyEvents for further processing.


private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
    if (stickyEvent != null) {
        postToSubscription(newSubscription, stickyEvent, isMainThread());
    }
}
Copy the code

The subscribe () method is the core of checkPostStickyEventToSubscription () method.

Index of the Subscriber

EventBus is mainly used to find subscription event method information through reflection at project runtime. If there are a large number of subscription event methods in a project, the performance of the project runtime will be affected. In addition to finding subscription event method information by reflection at project runtime, EventBus also provides a way to find subscription event method information by annotation handler at project compile time, generating a secondary Index class called Subscriber Index to hold this information. The principle is similar to ButterKnife.

We need to add dependencies to the configuration file

// annotationProcessor 'org. greenRobot :eventbus-annotation-processor:3.2.0'Copy the code

Add the configuration to the Application as well

EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
Copy the code

Enter the MyEventBusIndex class

Public class MyEventBusIndex implements SubscriberInfoIndex {// Saves the class type of the currently registered class and information about the event subscription method in it Map<Class<? >,SubscriberInfo> SUBSCRIBER_INDEX; static { SUBSCRIBER_INDEX = new HashMap<>(); putIndex(new SimpleSubscriberInfo(MainActivity.class,true, new SubscriberMethodInfo[]{ new SubscriberMethodInfo("changeText",String.class), })); } private static void putIndex(SubscriberInfo info){ SUBSCRIBER_INDEX.put(info.getSubscriberClass(),info); } @Override public SubscriberInfo getSubscriberInfo(Class<? > subscriberClass) { SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass); if(null! =info){ return info; } return null; }}Copy the code

Go to the addIndex method

public EventBusBuilder addIndex(SubscriberInfoIndex index) {
    if (subscriberInfoIndexes == null) {
        subscriberInfoIndexes = new ArrayList<>();
    }
    subscriberInfoIndexes.add(index);
    return this;
}
Copy the code

Save instances of the generated index classes in the subscriberInfoIndexes collection.

public EventBus installDefaultEventBus() {
    synchronized (EventBus.class) {
        if (EventBus.defaultInstance != null) {
            throw new EventBusException("Default instance already exists." +
                    " It may be only set once before it's used the first time to ensure consistent behavior.");
        }
        EventBus.defaultInstance = build();
        return EventBus.defaultInstance;
    }
}
Copy the code

Create an EventBus instance with the current EventBusBuilder object, so that the Subscriber Index we configured through EventBusBuilder is passed to the EventBus instance. It then assigns to the defaultInstance member variable of EventBus.

The default singleton for EventBus is generated in the Application, ensuring that eventbus.getDefault () is executed elsewhere in the project to get a unique EventBus instance

Because we now use the Subscriber Index so not through findUsingReflectionInSingleClass () to reflect parsing subscribe to events.

Private SubscriberInfo getSubscriberInfo(FindState FindState) {// This condition is not valid if (FindState. = null && findState.subscriberInfo.getSuperSubscriberInfo() ! = null) { SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo(); if (findState.clazz == superclassInfo.getSubscriberClass()) { return superclassInfo; If (subscriberInfoIndexes! = null) {// SubscriberInfoIndex index: // Find SubscriberInfo info = based on the Class Class of the registered Class index.getSubscriberInfo(findState.clazz); if (info ! = null) { return info; } } } return null; }Copy the code

The subscriberInfoIndexes are created in the addIndex() method that stores the index class instance in the project, that is, MyEventBusIndex. Continue with the getSubscriberInfo() method of the index class. We’re in the MyEventBusIndex class.

@Override public SubscriberInfo getSubscriberInfo(Class<? > subscriberClass) { SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass); if(null! =info){ return info; } return null; }Copy the code

Find the corresponding SubscriberInfo from SUBSCRIBER_INDEX according to the Class type of the registered Class. If we define a method to subscribe to events in the registered Class, the info is not empty. Findstate.subscriberinfo! = null, the main content of the analysis is finished, the other as before the registration process.

The core of Subscriber Index is to use annotation processor to generate Index class to save event subscription method information when the project is compiled, and then set the Index class instance to EventBus when the project is running, so that when registering EventBus, the event subscription method information corresponding to the current registered class can be taken out from the Index class. To complete the final registration, the runtime reflection process is avoided, so there is a qualitative improvement in performance.

EventBus thread switch

The method in question is EventBus’s postToSubscription() method

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);
    }
}
Copy the code
Switch to the main thread to receive events

To receive messages on the MAIN thread, set threadMode to MAIN

case MAIN:
    if (isMainThread) {
        invokeSubscriber(subscription, event);
    } else {
        mainThreadPoster.enqueue(subscription, event);
    }
Copy the code

If it is a non-main thread, it is handled in mainThreadPoster

mainThreadPoster = mainThreadSupport ! = null ? mainThreadSupport.createPoster(this) : null;Copy the code

Go to the createPoster method

public interface MainThreadSupport { boolean isMainThread(); Poster createPoster(EventBus eventBus); class AndroidHandlerMainThreadSupport implements MainThreadSupport { private final Looper looper; public AndroidHandlerMainThreadSupport(Looper looper) { this.looper = looper; } @Override public boolean isMainThread() { return looper == Looper.myLooper(); } @Override public Poster createPoster(EventBus eventBus) { return new HandlerPoster(eventBus, looper, 10); }}}Copy the code

The logic is all in the HandlerPoster class, which implements the Poster interface, which is a normal Handler, except that its Looper uses the Main Looper of the Main thread to distribute messages to the Main thread.

public void handleMessage(Message msg) { boolean rescheduled = false; try { long started = SystemClock.uptimeMillis(); while (true) { PendingPost pendingPost = queue.poll(); if (pendingPost == null) { synchronized (this) { // Check again, this time in synchronized pendingPost = queue.poll(); if (pendingPost == null) { handlerActive = false; return; } } } eventBus.invokeSubscriber(pendingPost); Long timeInMethod = systemclock. uptimeMillis() - started; if (timeInMethod >= maxMillisInsideHandleMessage) { if (! sendMessage(obtainMessage())) { throw new EventBusException("Could not send handler message"); } rescheduled = true; return; } } } finally { handlerActive = rescheduled; }}Copy the code

To avoid frequently sending sendMessage() to the main thread, EventBus’s practice is to process as many message events as possible in a message, so it uses a while loop to continuously fetch messages from the message queue.

At the same time in order to avoid long-term occupied the main thread, interval of 10 ms (maxMillisInsideHandleMessage = 10 ms) will resend sendMessage (), is used to make the main thread of the executive power, avoid to cause the UI caton and ANR.

MAIN ensures that the event is received. In the MAIN thread, it is important to note that if the event is sent in the MAIN thread, it will execute directly using MAIN. To make development and configurability even better, MAIN_ORDERED was added to EventBus V3.1.1, which does not discriminate between current threads, but instead uses mainThreadPoster for all message distribution that must go through the Handler.

Switch to child thread execution

To allow messages to be processed in child threads, you can set threadMode to BACKGROUND or AYSNC, both of which can be implemented, but there are some differences.

BACKGROUND
case BACKGROUND:
    if (isMainThread) {
        backgroundPoster.enqueue(subscription, event);
    } else {
        invokeSubscriber(subscription, event);
    }
    break;
Copy the code

BACKGROUND will distinguish between the thread on which the event is currently occurring, whether it is the main thread, and non-main thread which will distribute the event directly, and backgroundPoster which will distribute the event if it is the main thread

private volatile boolean executorRunning; BackgroundPoster(EventBus eventBus) { this.eventBus = eventBus; queue = new PendingPostQueue(); } public void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); synchronized (this) { queue.enqueue(pendingPost); if (! executorRunning) { executorRunning = true; eventBus.getExecutorService().execute(this); }}}Copy the code

BackgroundPoster also implements the Poster interface, which also maintains a linked list implementation of the message queue PendingPostQueue.

In BackgroundPoster, this is done using EventBus’s executorService thread pool object

Private volatile Boolean executorRunning; private volatile Boolean executorRunning; BackgroundPoster(EventBus eventBus) { this.eventBus = eventBus; queue = new PendingPostQueue(); } public void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); synchronized (this) { queue.enqueue(pendingPost); // Execute if (! executorRunning) { executorRunning = true; eventBus.getExecutorService().execute(this); } } } @Override public void run() { try { try { while (true) { PendingPost pendingPost = queue.poll(1000); if (pendingPost == null) { synchronized (this) { // Check again, this time in synchronized pendingPost = queue.poll(); if (pendingPost == null) { executorRunning = false; return; } } } eventBus.invokeSubscriber(pendingPost); } } catch (InterruptedException e) { eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e); } } finally { executorRunning = false; }}Copy the code

In BackgroundPoster, when processing an event thrown by the main thread, there’s only one thread at a time that’s going to loop in and fetch the event processing event from the queue.

Synchronized synchronization was used to ensure thread-safety of queue data, and volatile identification was used to keep execution status visible from different threads

ASYNC
asyncPoster.enqueue(subscription, event); class AsyncPoster implements Runnable, Poster { ... public void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); queue.enqueue(pendingPost); 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); }}Copy the code

The corresponding Poster is AsyncPoster, which does no special processing, and all events are brainlessly thrown to the executorService thread pool of EventBus, which ensures that the thread that causes the event, and the thread that receives the event, It must be different, and it is guaranteed that events are handled in child threads.

BACKGROUND and ASYNC are both guaranteed to receive processing events in child threads, but their internal implementations are different

BACKGROUND only uses one child thread at a time to loop events from the event queue and process them. That is, the execution efficiency of previous events affects the execution of subsequent events.

ASYNC mindlessly sends tasks to the thread pool executorService, which, if you don’t configure it, by default uses Executors’ newCachedThreadPool(). OOM appears when there are too many tasks (events).