In the last article, you probably didn’t use EventBus correctly. It’s time to really understand EventBus (above). We talked about optimizing using EventBus with APT and explained the EventBus# Register and EventBus#unRegister methods. If you are not familiar with this piece of content, you can continue to look at the following content.

This section covers the rest, including EventBus#post, sticky events, and more.

1.EventBus#post(Object event)

As mentioned earlier, when we register, we will find the method of the added annotation and save it. This section explains how to use it.

public void post(Object event) {
   // Get the PostingThreadState object
   PostingThreadState postingState = currentPostingThreadState.get();
   // Get the eventQueue and set the event to it
   List<Object> eventQueue = postingState.eventQueue;
   eventQueue.add(event);

   // Check whether the current event was posted
   if(! postingState.isPosting) {// Set whether the current POST is the main thread
       postingState.isMainThread = isMainThread();
       // Mark the task as posted
       postingState.isPosting = true;
       if (postingState.canceled) {
           throw new EventBusException("Internal error. Abort state was not reset");
       }
       try {
           while(! eventQueue.isEmpty()) {// eventQueue is a List,
               // we know that the List#remove method removes objects from the queue and returns the removed object
               postSingleEvent(eventQueue.remove(0), postingState); }}finally {
           postingState.isPosting = false;
           postingState.isMainThread = false; }}}Copy the code

PostSingleEvent (EventQueue.remove (0), postingState)

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<? > eventClass = event.getClass();boolean subscriptionFound = false;
    //eventInheritance defaults to true to retrieve inheritance relationships
    if (eventInheritance) {
        // Find all eventTypes based on the event class object (which contains all event superclasses)List<Class<? >> eventTypes = lookupAllEventTypes(eventClass);int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) { Class<? > clazz = eventTypes.get(h);// Determine if the eventType is retrieved and send the POSTsubscriptionFound |= postSingleEventForEventType(event, postingState, clazz); }}else {
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    if(! subscriptionFound) {// If it is not found && logNoSubscriberMessages == true, an exception is thrown, otherwise NoSubscriberEvent is sent
        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

Code logic is very simple, use postSingleEventForEventType send events, and send the results, if you send the result returns false, is whether logNoSubscriberMessages to true, true words ran out of the registration method of anomaly not found

Then see postSingleEventForEventType

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class
        eventClass) {
     CopyOnWriteArrayList<Subscription> subscriptions;
     synchronized (this) {
         // Subscribe to eventbus.getDefault ().register()
         // Take the event of the registered method (that is, the method argument to which we set the @SUBSCRIBE annotation) as the key,
         // combine the prioritized methods as value.
         // The subscription already contains a method object, which is invoked to invoke message passing
         subscriptions = subscriptionsByEventType.get(eventClass);
     }
     if(subscriptions ! =null && !subscriptions.isEmpty()) {
         for (Subscription subscription : subscriptions) {
             postingState.event = event;
             postingState.subscription = subscription;
             boolean aborted;
             try {
                 // This is important!! Remember when we defined threadMode in the @SUBSCRIBE annotation, this is where it comes in handy
                 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

You can see the subscriptionsByEventType used in the previous register. If you read the previous article, you will know that it is a map set with the event class method as the key and all annotation parameter methods whose parameter is event are set as value.

Then traverse, call postToSubscription (subscription, event, postingState. IsMainThread); The contact event is sent.

Then look at postToSubscription (subscription, event, postingState. IsMainThread); Method implementation.

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    // Specify which poster is used to send an event according to the threadMode on the annotation method
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING:
            invokeSubscriber(subscription, event);
            break;
        case MAIN:
            if (isMainThread) {
                //invokeSubscriber itself does not care about the thread, and is sent directly in the current thread
                invokeSubscriber(subscription, event);
            } else {
                // Use handler to switch to the main thread
                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

In fact, three posters and five threadmodes are used in the core here

Let’s start with three types of poster

  • mainThreadPoster

The createPoster(EventBus EventBus) method is actually HandlerPoster, and MainLooper is used to create HandlerPoster. The idea is that no matter which thread you’re sending a message from, you’ll end up sending it from the main thread.

  • backgroundPoster

This poster is inherited from Runnable, and each time you send, the event is added to a queue and thrown into the thread pool for execution. A quick list of the core code.

final class BackgroundPoster implements Runnable.Poster {
    private final PendingPostQueue queue;
    private final EventBus eventBus;
    private volatile boolean executorRunning;
    BackgroundPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        queue = new PendingPostQueue();
    }
    // When called, it will be queued
    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); }}}@Override
    public void run(a) {
        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
  • asyncPoster

The core idea of this poster is the same as the above backgroundPoster, which is also through the thread pool. The only difference is that backgroundPoster is sent one at a time, and thread synchronization and flag bit judgment are performed to ensure the execution order.

class AsyncPoster implements Runnable.Poster {
    private final PendingPostQueue queue;
    private final EventBus eventBus;
    AsyncPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        queue = new PendingPostQueue();
    }
    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        queue.enqueue(pendingPost);
        eventBus.getExecutorService().execute(this);
    }
    @Override
    public void run(a) {
        PendingPost pendingPost = queue.poll();
        if(pendingPost == null) {
            throw new IllegalStateException("No pending post available"); } eventBus.invokeSubscriber(pendingPost); }}Copy the code

In addition to the above three types of poster, you may also see a method called invokeSubscriber(Subscription, Object event)

void invokeSubscriber(Subscription subscription, Object event) {
  try {
        // It is very simple to use reflection to call the corresponding method to implement the function of notification
        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

In fact, the above three methods, whether synchronous, asynchronous, or main thread, will ultimately execute this method.

After understanding the three types of poster and the final notification mode, ThreadMode in 5 is not difficult to understand.

The five threadmodes are

  • POSTINGThe thread that sends it receives it directly
  • MAINNo matter which thread it is sent from, it is eventually returned to the main thread
  • MAIN_ORDEREDThe main thread is sent sequentially using Handler
  • BACKGROUNDWhether sent by the main thread or a child thread, it is eventually returned to the child thread for use
  • ASYNCDoes not determine the thread at all, directly to the child thread sent to, and eventually back to the child thread to use

At this point, you should be clear about the logical use of slave register, unregister, and send in EventBus. If something is “postSticky” (Object event), it is likely to be sticky. 😄

Sticky eventspostSticky(Object event)

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

The logic itself is simple, adding an event to stickyEvents and calling the POST method we just described. StickyEvents is easy to see, it’s a map. As for users, look further down.

You remember in the last article, said postSingleEventForEventType method, has a part is sticky, I said back to tell? So let’s take that code out.

EventBus#subscribe

./ / the subscribe method
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); }}...Copy the code

Here with the help of a checkPostStickyEventToSubscription method

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
    if(stickyEvent ! =null) {
        // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
        // --> Strange corner case, which we don't take care of here.postToSubscription(newSubscription, stickyEvent, isMainThread()); }}Copy the code

PostToSubscription (newSubscription, stickyEvent, isMainThread()) was executed; The method, if you’re familiar with what we just talked about.

I believe you have already figured out the principle of using postSticky, and I will sum it up at last

If it’s a stickyEvent, it gets pulled out of stickyEvent and executes the post process directly.