Source code analysis of EventBus

Posted by jvalarta on Sat, 06 Nov 2021 05:17:21 +0100

usage method

The use method is very simple, according to Official documents The introduction is divided into three steps.

Step 1: define events

public static class MessageEvent { }

Step 2: prepare subscribers

Define subscription methods to handle received events.

@Subscribe(threadMode = ThreadMode.MAIN)  
public void onMessageEvent(MessageEvent event) {/* Do something */};

And register and log off in Activity and Fragment according to their life cycle.

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

 @Override
 public void onStop() {
     super.onStop();
     EventBus.getDefault().unregister(this);
 }

Step 3: send event

Send the defined event to the subscriber.

 EventBus.getDefault().post(new MessageEvent());

Source code analysis

The use method is very simple. Here is the source code according to the use steps.

Preparing subscribers

The step of preparing subscribers is divided into two steps: registration and logout and preparing subscription methods.

Prepare subscription method

It can also be seen from the usage method that the subscription method is implemented by annotating @ Subscribe.

@Documented Indicates that the element using this annotation should be javadoc Or similar tools
@Retention(RetentionPolicy.RUNTIME) Annotations are retained in the class In the file, it will also be recognized during operation, so the reflection mechanism can be used to obtain annotation information.
@Target({ElementType.METHOD}) Scope of use, indicating the method used to describe
public @interface Subscribe {
	/**
	 * Thread mode, the default is POSTING
	 */
    ThreadMode threadMode() default ThreadMode.POSTING;

    /**
     * Whether it is a sticky event. The default value is false
     */
    boolean sticky() default false;

    /**
     * Priority of event subscription. The default is 0.
     * When the subscribers are in the same thread mode, the priority will work, and the subscribers with high priority will receive the pushed events first.
     */
    int priority() default 0;
}

Three meta annotations are used in the source code:

  • @Documented: indicates that the element using this annotation should be documented by javadoc or similar tools.
  • @Retention(RetentionPolicy.RUNTIME): indicates that the annotation will be retained in the class file and recognized during operation, so the annotation information can be obtained using the reflection mechanism.
  • @Target({ElementType.METHOD}): indicates the scope of use. This annotation is used to describe the method.

Each subscriber will have a thread mode. The thread mode determines which thread its subscription method runs in. These thread modes are:

  • POSTING: the default thread mode. The thread that sends the event will process the event in the corresponding thread.
  • MAIN: if the event is sent in the MAIN thread, the event is processed directly in the MAIN thread. Conversely, if an event is sent in a child thread, you need to switch to the MAIN thread to process the event. (used more in Android)
  • MAIN_ORDERED: no matter which thread sends events, the events will be queued and executed orderly on the main thread.
  • Backgroup: if an event is sent in a child thread, the event is processed directly in the child thread. On the contrary, if you send an event in the main thread, you need to put the event in the message queue, switch to the sub thread, and use the thread pool to process the event in order. (always use this mode if not used in Android)
  • ASYNC: no matter which thread sends an event, the event will be put into the message queue and processed on the child thread through the thread pool. This mode should be used if the execution of the subscriber method may take some time, such as network access.

register

As described in the above usage method, only one line is required to complete the subscriber registration.

EventBus.getDefault().register(this);

The EventBus.getDefault() method actually returns an instance of EventBus through singleton mode. Let's take a look at the register method directly.

public void register(Object subscriber) {
    //Get subscriber class
    Class<?> subscriberClass = subscriber.getClass();
    //Get the subscription method according to the subscriber class
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            //Traverse the subscription method and call the subscription method
            subscribe(subscriber, subscriberMethod);
        }
    }
}

The parameter subscriber in the method is this passed in when we call the method, so it means Activity and Fragment. To sum up, we get the class object of the subscriber, find its subscription method, and call the subscribe subscription method to subscribe.
So the key point is to see how he finds the subscription method and what he does in the subscription method? Go down:

Subscription method found

/**
 * Subscription method found
 *
 * @param subscriberClass Subscriber class
 * @return Subscription method list
 */
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    //First find the subscription method from the cache
    //METHOD_ CACHE -> Map<Class<?>,  List < subscribermethod > >: key is the subscriber class and value is the subscription method list
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        //If there is in the cache, it is returned directly
        return subscriberMethods;
    }
	//Whether to use subscriber index. ignoreGeneratedIndex is false by default
    if (ignoreGeneratedIndex) {
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    if (subscriberMethods.isEmpty()) {
        //If no subscription method is found, an exception is thrown to remind the user to use the @ Subscribe method to declare the subscription method
        //That is, if the user register s but does not have any @ Subscribe subscription method, an exception will be thrown to prompt the user
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        //If the subscription method is not empty, put it into the cache to facilitate reuse next time. key is the class name of the subscription class
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

To sum up, first go to method according to the subscriber class_ Search in cache. If it is found, it will directly return the subscriber method list. If it is not found, it will determine whether to use findUsingInfo or findUsingReflection method according to whether to use subscriber index. Find the subscription method list and add it to METHOD_CACHE is convenient for the next use. On the contrary, if the subscription method cannot be found, an exception will be thrown.

Next, let's look at how to find and return the subscriber list. First, let's look at the findUsingReflection method:

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        findUsingReflectionInSingleClass(findState);
        //Find the parent class. The specific implementation depends on the skisupersuperclasses flag bit
        findState.moveToSuperclass();
    }
    //Returns a list of subscription methods
    return getMethodsAndRelease(findState);
}

/**
 * Extract subscription information through class reflection
 *
 * @param findState
 */
private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        // This is faster than getMethods, especially when subscribers are fat classes like Activities
        // getMethods(): returns all public methods declared by a class or interface and inherited from superclasses and superinterfaces.
        // getDeclaredMethods(): returns the methods declared by the class, including public, protected, default (package), but excluding inherited methods
        // Therefore, this method is faster than the getMethods method, especially in complex classes, such as Activity.
        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) {
                //Consider using the EventBus annotation processor to avoid reflection
                msg += ". Please consider using EventBus annotation processor to avoid reflection.";
            } else {
                msg += ". Please make this class visible to EventBus annotation processor to avoid reflection.";
            }
            //The method in this class cannot be found. An exception is thrown
            throw new EventBusException(msg, error);
        }
        // Because the getMethods() method has obtained the method of the superclass, it is not set here to check the superclass
        findState.skipSuperClasses = true;
    }
    //Traverse the methods found
    for (Method method : methods) {
        //Get method modifier: public - > 1; private->2; protected->4; static->8; final->16
        int modifiers = method.getModifiers();
        //If it is public and not abstract | static
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            //Get method parameter type
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 1) {
                //Get annotation of method Subscribe
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    //The first parameter is the event type
                    Class<?> eventType = parameterTypes[0];
                    //Check whether the subscription method for subscribing to this type of event has been added. True - > not added; False - > added
                    if (findState.checkAdd(method, eventType)) {
                        //Not added. Create a new subscription method object according to the found parameters and add it to the subscriber methods list
                        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");
        }
    }
}

For the checkAdd method, go further:

final Map<Class, Object> anyMethodByEventType = new HashMap<>();
final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();

boolean checkAdd(Method method, Class<?> eventType) {
    // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
    // Usually a subscriber doesn't have methods listening to the same event type.
    // Usually, a subscriber will not have multiple subscription methods to subscribe to the same type of event (in this case, the user writes blindly in JB)

    // Extension: return value description of HashMap put() method:
    // If the same key already exists, the value corresponding to the previous key is returned, and the new value of the key overwrites the old value;
    // If it is a new key, null is returned;
    Object existing = anyMethodByEventType.put(eventType, method);
    if (existing == null) {
        //Indicates that there is no subscription method to subscribe to this type of event
        return true;
    } else {
        //A subscription method for subscribing to this type of event already exists
        //existing is the subscription method that stores anyMethodByEventType to subscribe to unified type events
        if (existing instanceof Method) {
            if (!checkAddWithMethodSignature((Method) existing, eventType)) {
                // Paranoia check
                throw new IllegalStateException();
            }
            // Put any non-Method object to "consume" the existing Method
            anyMethodByEventType.put(eventType, this);
        }
        return checkAddWithMethodSignature(method, eventType);
    }
}

private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
    methodKeyBuilder.setLength(0);
    methodKeyBuilder.append(method.getName());
    methodKeyBuilder.append('>').append(eventType.getName());

    // Method name > event type
    // Intention: if multiple subscription methods in the same class subscribe to the same event, all subscription methods will receive the event when the event is distributed.
    String methodKey = methodKeyBuilder.toString();
    Class<?> methodClass = method.getDeclaringClass();
    Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
    /* Extension: isAssignableFrom() method description:
     * Whether the Class represented by the current Class object is the parent Class, super interface, or the same type of the Class represented by the Class object passed in the parameter.
     * If yes, it returns true; otherwise, it returns false.
     */
    //methodClassOld is empty, which means it has not been added, or methodClassOld is the parent class of methodClass
    if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
        // Only add if not already found in a sub class
        // Add only if it is not found in the subclass
        return true;
    } else {
        // Revert the put, old class is further down the class hierarchy
        subscriberClassByMethodKey.put(methodKey, methodClassOld);
        return false;
    }
}
Using Subscriber Index

We just saw how to directly call the findUsingReflection method to find the subscription method using reflection, and then we saw how to use the Subscribe index to find the subscription method.

Note: we highly recommend the EventBus annotation processor with its subscriber index. This will avoid some reflection related problems seen in the wild.

As described on the official website, EventBus recommends that you use the annotation processor to avoid using reflection to find subscription methods at run time, but at compile time.

Use the annotation processor to generate the index

apply plugin: 'kotlin-kapt' // ensure kapt plugin is applied
 
dependencies {
    def eventbus_version = '3.2.0'
    implementation "org.greenrobot:eventbus:$eventbus_version"
    kapt "org.greenrobot:eventbus-annotation-processor:$eventbus_version"
}
 
kapt {
    arguments {
        arg('eventBusIndex', 'com.example.myapp.MyEventBusIndex')
    }
}

rebuild the project to generate MyEventBusIndex, for example:

/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

    static {
        //Key - > subscriber class object; Value - > subscription information
        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

        putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("firstOnTestEvent", TestEvent.class, ThreadMode.MAIN),
            new SubscriberMethodInfo("messageEvent", MessageEvent.class, ThreadMode.BACKGROUND, 6, true),
        }));

        putIndex(new SimpleSubscriberInfo(SecondActivity.class, true, new SubscriberMethodInfo[] {
            new SubscriberMethodInfo("onTestEvent", TestEvent.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 (info != null) {
            return info;
        } else {
            return null;
        }
    }
}

Then pass the MyEventBusIndex instance to EventBus.

EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();

Next, let's look back at the Subscriber Index lookup method findUsingInfo()

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    //Get a FindState object from the FindState pool and initialize it
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    //A while loop is used here, which means that after the subclass is searched, it will go to the parent class to continue searching
    while (findState.clazz != null) {
        //Go to the index to find subscription information
        findState.subscriberInfo = getSubscriberInfo(findState);
        if (findState.subscriberInfo != null) {
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            //Traversal subscription method
            for (SubscriberMethod subscriberMethod : array) {
                //Check whether the subscription method has been added
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    //Not added, add the found subscription method to the subscription method list
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            //Use the radial method to find subscription methods
            findUsingReflectionInSingleClass(findState);
        }
        //Find parent class
        findState.moveToSuperclass();
    }
    //Returns a list of subscription methods
    return getMethodsAndRelease(findState);
}

private SubscriberInfo getSubscriberInfo(FindState findState) {
    //subscriberInfo is not empty, which means that the subscription information has been found. You need to find the parent class this time
    if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
        SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
        //Confirm that the parent class is found this time
        if (findState.clazz == superclassInfo.getSubscriberClass()) {
            return superclassInfo;
        }
    }
    //subscriberInfoIndexes is added by EventBus.addIndex(MyEventBusIndex())
    if (subscriberInfoIndexes != null) {
        for (SubscriberInfoIndex index : subscriberInfoIndexes) {
            //This is to execute the getSubscriberInfo method in the MyEventBusIndex class to obtain subscription information
            SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
            if (info != null) {
                return info;
            }
        }
    }
    return null;
}

The principle is also very simple, that is, the index is generated during compilation, so we don't need to find it through reflection at runtime. We can find it directly through the index file. In addition, through the generated file, we can clearly see the distribution of our declared subscription methods.

Precautions for using Subscriber Index:

  • @The Subscribe method and its classes must be public.
  • Event classes must be public.
  • @Subscribe cannot be used in anonymous classes.

subscribe

Above, we have seen how to find the subscription method, and then we will take a further look at how to implement the subscription action in the subscription method.


/**
 * A map collection of event types and subscription object lists
 *
 * key -> eventType Event type
 * value -> Subscription Subscription object list, where subscription is an encapsulated class of subscribers and subscription methods
 */
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
/**
 * A map collection that records a list of all event types that subscribers subscribe to
 *
 * key -> subscriber
 * value -> List of all event types subscribed to by subscribers
 */
private final Map<Object, List<Class<?>>> typesBySubscriber;
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    //Get event type parameters through subscription method
    Class<?> eventType = subscriberMethod.eventType;
    //A subscription object is constructed by subscriber and subscription method
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    //Through the event type, find the collection of subscription objects in the form of CopyOnWriteArrayList
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        //If the subscription object collection is empty, it indicates that the subscription method subscribed to this type of event has not been registered.
        //Create a new list, and then put the event type and the new list into the subscriptionsByEventType Map
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        //If the subscription object collection already contains the newSubscription
        if (subscriptions.contains(newSubscription)) {
            //Throw an exception to prompt the user that the subscriber has subscribed to this type of event
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

    //Traverse the list of subscription objects
    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        //If there is a declared priority in the subscription method, the subscription method is added to the specified location according to the priority
        //Otherwise, add the subscription method to the end of the subscription object list
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }

    //Find the type list of all events subscribed to by subscribers subscribedEvents
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    //If the subscriber does not have any subscription method, that is, subscribedEvents is empty
    if (subscribedEvents == null) {
        //Create a new list to put all the event types subscribed by this subscriber
        subscribedEvents = new ArrayList<>();
        //And put the subscriber and the new list into the typesBySubscriber map
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    //Add the event type to the event type list
    subscribedEvents.add(eventType);

    //If the subscription method supports sticky events
    if (subscriberMethod.sticky) {
	    //Whether to consider the hierarchy of event classes. The default value is true
        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();
                    //Check sending sticky events
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
	        //Get sticky events based on event type
            Object stickyEvent = stickyEvents.get(eventType);
            //Check sending sticky events
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

Note that the subscribe method needs to run in the synchronize block. You can refer to another article about synchronize Learn more about the Synchronize keyword

cancellation

After reading the registration, we then look at the logout. The logout method is also very simple. It can be completed in one sentence of code.

EventBus.getDefault().unregister(this);

Let's take a closer look at the unregister method.

public synchronized void unregister(Object subscriber) {
    //Find a list of all event types subscribed to by subscribers
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        //Traverse the event type list
        for (Class<?> eventType : subscribedTypes) {
            //Unregister subscriber by event type
            unsubscribeByEventType(subscriber, eventType);
        }
        //Remove the subscriber from the typesBySubscriber map
        typesBySubscriber.remove(subscriber);
    } else {
        //log prompt: logout is performed without registration
        logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    //Find the list of related subscription objects by event type
    List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions != null) {
        int size = subscriptions.size();
        //Traverse the list of subscription objects
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
            if (subscription.subscriber == subscriber) {
                //Find the subscriber from the subscription object list, change its active state to false, and remove it from the subscription object list
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}

Send event

Then it is to send events. Sending events is also very simple. It can be done in the same sentence.

 EventBus.getDefault().post(new MessageEvent());

Let's take a closer look at the source code.

public void post(Object event) {
    //PostingThreadState is an encapsulated class of events and send states
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    //Add the event to the event queue
    eventQueue.add(event);

    if (!postingState.isPosting) {
        //Check whether it is in the main thread
        postingState.isMainThread = isMainThread();
        //Set to sending
        postingState.isPosting = true;
        //Check whether to cancel sending. If you cancel sending, an exception will be thrown
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            while (!eventQueue.isEmpty()) {
                //Traverse the event queue and send events one by one
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            //Reset send status
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    //Gets the class object of the event
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    //Whether to consider the hierarchy of event classes. The default value is true
    if (eventInheritance) {
        //Find all event types of the superclass
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            //Send events according to event type
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        //Send events according to event type
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    //No subscription object was found to subscribe to this type of event, that is, there is no subscription method to subscribe to this type of event
    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));
        }
    }
}

/**
 * According to the event type, send the event to the subscription method subscribed to the event type
 * @param event
 * @param postingState
 * @param eventClass
 * @return true->Find the subscription method that subscribed to this type of event; False - > there is no subscription method to subscribe to this type of event
 */
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
	//Subscription object list
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        //Query the list of subscribers subscribing to events of this type according to the event type
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    //If the subscription object list is not empty, events are sent to these subscribers one by one
    if (subscriptions != null && !subscriptions.isEmpty()) {
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted;
            try {
                //Send events to subscription objects
                postToSubscription(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                //Reset PostingState
                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) {
    //According to the thread mode selected by the subscriber, choose which thread mode to use to distribute and process the event
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING:
            //Call subscription methods directly using reflection
            invokeSubscriber(subscription, event);
            break;
        case MAIN:
            if (isMainThread) {
                //If you are currently in the main thread, direct reflection calls the subscription method
                invokeSubscriber(subscription, event);
            } else {
                //Use the Handler to switch to the main thread, and finally execute invokeSubscriber
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case MAIN_ORDERED:
            if (mainThreadPoster != null) {
                //Queue the events and execute them orderly on the main thread
                mainThreadPoster.enqueue(subscription, event);
            } else {
                // temporary: technically not correct as poster not decoupled from subscriber
                invokeSubscriber(subscription, event);
            }
            break;
        case BACKGROUND:
            if (isMainThread) {
                //If you are currently in the main thread, you will use the thread pool to switch to the child thread for processing, and finally call invokeSubscriber
                backgroundPoster.enqueue(subscription, event);
            } else {
                //If you are currently in a child thread, the event is processed directly in that child thread
                invokeSubscriber(subscription, event);
            }
            break;
        case ASYNC:
            //No matter what thread you are in, you will eventually use the thread pool to switch to the child thread for processing, and eventually call invokeSubscriber
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
    }
}


void invokeSubscriber(Subscription subscription, Object event) {
    try {
        //Call subscription method with reflection
        subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
    } catch (InvocationTargetException e) {
        handleSubscriberException(subscription, event, e.getCause());
    } catch (IllegalAccessException e) {
        throw new IllegalStateException("Unexpected exception", e);
    }
}

To sum up in one sentence: find the corresponding subscription object list according to the event type, and then determine which thread handles the event according to the thread mode of the subscription object.

Send sticky events

If you have not registered a subscriber before sending a normal event, the event you send will not be received and executed, and the event will be recycled.
The sticky event is different. You can register the subscriber after sending the sticky event. Once the subscription is completed, the subscriber will receive the sticky event.
Let's see how it is implemented from the source code!

EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));

Unlike sending normal events, sticky events are sent using the postSticky() method.

/**
 * Used to store sticky events
 *
 * key -> Class object for sticky events
 * value -> Viscous event
 */
private final Map<Class<?>, Object> stickyEvents;
public void postSticky(Object event) {
    //Sync lock to store sticky events in stickyEvents
    synchronized (stickyEvents) {
        stickyEvents.put(event.getClass(), event);
    }
    //After the event is added, just like normal events, call the post() method to send the event
    post(event);
}

A stickyEvents collection is used to save sticky events. After storing them, the post() method is called as usual.
?? Um??, At this time, I have a question. For the above usage scenario, I send sticky events first, and then register the subscription. At this time, I execute the post method to send events. There is no corresponding subscriber at all. It must have failed to send. So, think about it. To achieve this effect, the subscriber should send the saved event after registering the subscription.
Go back to register - > subscribe method:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    //Get event type parameters through subscription method
    Class<?> eventType = subscriberMethod.eventType;
    //A subscription object is constructed by subscriber and subscription method
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    ...
    .Omit some codes.
	...
    //If the subscription method supports sticky events
    if (subscriberMethod.sticky) {
        //Whether to consider the hierarchy of event classes. The default value is true
        if (eventInheritance) {
            Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry<Class<?>, Object> entry : entries) {
                Class<?> candidateEventType = entry.getKey();
                //Is eventType a parent of candidateEventType
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    //Check sending sticky events
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            //Get sticky events based on event type
            Object stickyEvent = stickyEvents.get(eventType);
            //Check sending sticky events
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
    //If the sticky event is not empty, send the event
    if (stickyEvent != null) {  
        postToSubscription(newSubscription, stickyEvent, isMainThread());
    }
}

If it is true, in the registered subscription method, if the current subscription method supports sticky events, the subscriber will check whether there is a corresponding sticky event in the stickyEvents collection. If a sticky event is found, the subscriber will send the event.

At this point, the source code analysis of EventBus is finished. If you want to view all comments, you can click Github EventBus all comments View.

In fact, the biggest purpose of sharing articles is to wait for someone to point out my mistakes. If you find any mistakes, please point them out without reservation and ask for advice with an open mind. In addition, if you think the article is good and helpful to you, please give me a praise and be encouraged. Thank you ~ Peace ~!

Topics: Java Android source code eventbus