preface

I’m sure you’ve all used itEventBusThis open source library is based onPublish/subscriber modelFor communicating between components, decoupling avoids callback hell and is simple and easy to use. Such open source library is worth our learning, today to learn about his source code and design ideas.


Method of use

Use method is very simple, according to the official documentation, divided into three steps.

Step 1: Define events

public static class MessageEvent {}Copy the code

Step 2: Prepare subscribers

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(a) {
     super.onStart();
     EventBus.getDefault().register(this);
 }

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

Step 3: Send events

Sends defined events to subscribers.

 EventBus.getDefault().post(new MessageEvent());
Copy the code

The source code parsing

The method of use is very simple, the following according to the use of the steps to parse the source code.

Prepare subscribers

The step of preparing subscribers is divided into two steps: registering and unregistering and preparing subscription methods.

Prepare subscription method

You can also see from using methods that the Subscribe method is implemented with the @subscribe annotation.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    /** * Thread mode, default is POSTING */
    ThreadMode threadMode(a) default ThreadMode.POSTING;

    /** * is a sticky event. Default is false */
    boolean sticky(a) default false;

    /** * Priority of event subscription, default is 0. * Priority comes into play when the subscribers are in the same thread mode, and the higher-priority subscribers will receive pushed events first. * /
    int priority(a) default 0;
}
Copy the code

The source code uses three meta-annotations, which are:

  • @Documented: indicates that the element using the annotation should be documented by Javadoc or similar tools.
  • @Retention(RetentionPolicy.RUNTIME): indicates that annotations are retained in the class file and recognized at runtime, so you can use reflection to retrieve annotation information.
  • @Target({ElementType.METHOD}): indicates the scope of use. This annotation is used to describe the method.

Each subscriber has a thread mode, which determines the thread in which its subscription method runs. These thread modes are:

  • POSTING: The default thread mode in which events are sent and processed in the corresponding thread.
  • MAINIf the event is sent on the main thread, the event is handled directly on the main thread. Conversely, if you send an event in a child thread, you need to switch to the main thread to handle the event. (Mostly used on Android)
  • MAIN_ORDERED: No matter which thread sends events, they are queued and executed in order on the main thread.
  • BACKGROUND: If the event is sent in a child thread, the event is processed directly in that child thread. On the other hand, if an event is sent in the main thread, it needs to be queued, switched to a child thread, and processed in order with a thread pool. (Always use this mode if not in Android)
  • ASYNC: No matter which thread sends the event, the event is queued to the message and processed on the child threads through the thread pool. Use this pattern if the execution of the subscriber method is likely to take some time (such as network access).

registered

As described in the above usage, only one line is required to register a subscriber.

EventBus.getDefault().register(this);
Copy the code

The eventbus.getDefault () method returns an instance of EventBus in singleton mode, so let’s look directly at the register method.

public void register(Object subscriber) {
    // Get the subscriber classClass<? > subscriberClass = subscriber.getClass();// Get the subscription method according to the subscriber class
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            // Iterate over the subscription method, calling the subscription methodsubscribe(subscriber, subscriberMethod); }}}Copy the code

The parameter subscriber in the method is the this passed in when we call the method, so it also represents Activity and Fragment. To recap, we get the class object of the subscriber, find its subscribe method, and call subscribe to subscribe. So the point is how does he find the subscription method and what does he do in the subscription method? Go down:

Finding a subscription method

/** * find the subscription method **@paramSubscriberClass subscriberClass *@returnSubscribe method list */
List<SubscriberMethod> findSubscriberMethods(Class
        subscriberClass) {
    // Find the subscription method in the cache
    //METHOD_CACHE -> Map
      
       , List
       
        >: key is the subscriber class and value is the List of subscribed methods
       
      >
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if(subscriberMethods ! =null) {
        // If there is one in the cache, use it 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 declare the subscription method using the @subscribe method
        // That is, if the user registers without any @subscribe 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 in the cache for reuse. Key is the class name of the subscription class
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        returnsubscriberMethods; }}Copy the code

METHOD_CACHE is used to search the subscriber class. If the subscriber index is not used, findUsingInfo or findUsingReflection is used. Find the list of subscription methods and add them to METHOD_CACHE for next use, otherwise, no subscription methods are found and throw an exception.

To see how to find and return the subscriber list, look at the findUsingReflection method, which uses reflection directly without subscriber index:

private List<SubscriberMethod> findUsingReflection(Class
        subscriberClass) {
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while(findState.clazz ! =null) {
        findUsingReflectionInSingleClass(findState);
        // Find the parent class. See the skipSuperClasses flag
        findState.moveToSuperclass();
    }
    // Returns a list of subscription methods
    return getMethodsAndRelease(findState);
}

/** * Extract subscription information from 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 class declared methods, including public, protected, default (package), but not inherited methods
        // Therefore, this method is much faster than getMethods, especially in complex classes such as activities.
        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.";
            }
            // Can't find a method in this class, throw an exception
            throw new EventBusException(msg, error);
        }
        Because the getMethods() method already gets the methods of the superclass, the superclass is not checked here
        findState.skipSuperClasses = true;
    }
    // Iterate over the found method
    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 is not the abstract | static class
        if((modifiers & Modifier.PUBLIC) ! =0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            // Get the method parameter typeClass<? >[] parameterTypes = method.getParameterTypes();if (parameterTypes.length == 1) {
                // Get the annotation of the method Subscribe
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if(subscribeAnnotation ! =null) {
                    // The first argument is the event typeClass<? > eventType = parameterTypes[0];
                    // Check if a subscription method has been added to subscribe to events of this type, true-> Not added; False - > has been added
                    if (findState.checkAdd(method, eventType)) {
                        Create a subscription method object based on the parameters found and add it to the subscriberMethods list
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        findState.subscriberMethods.add(newSubscriberMethod(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

Take the checkAdd method a step 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 does not have multiple subscription methods to subscribe to the same type of event.

    // Extension: the return value of the HashMap put() method
    // If the same key already exists, the value of 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 no subscription method exists to subscribe to this type of event
        return true;
    } else {
        // A subscription method already exists to subscribe to this type of event
        // Existing is a subscription method that stores anyMethodByEventType events first
        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);
        }
        returncheckAddWithMethodSignature(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"
    // Intent: If there are multiple subscribing methods in the same class that subscribe to the same event, all subscribing methods will receive the event when it is distributed.String methodKey = methodKeyBuilder.toString(); Class<? > methodClass = method.getDeclaringClass(); Class<? > methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);IsAssignableFrom () : isAssignableFrom(); isAssignableFrom() : isAssignableFrom(); * Returns true if it is, false otherwise. * /
    //methodClassOld is null, which means that methodClassOld has not been added, or that methodClassOld is the parent 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 a subclass
        return true;
    } else {
        // Revert the put, old class is further down the class hierarchy
        subscriberClassByMethodKey.put(methodKey, methodClassOld);
        return false; }}Copy the code
Using the Subscriber Index

We just looked at calling the findUsingReflection method directly to find subscription methods using reflection, then we’ll look at how to find subscription methods using the Subscribe Index.

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

As explained on the official website, EventBus recommends that you use an annotation handler to avoid using reflection to look up subscription methods at runtime and instead look up them at compile time.

Use annotationProcessor to generate 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')}}Copy the code

The project rebuild generates MyEventBusIndex, for example:

/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
    private static finalMap<Class<? >, SubscriberInfo> SUBSCRIBER_INDEX;static {
        //key -> subscriber class object; Value -> Subscription information
        SUBSCRIBER_INDEX = newHashMap<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; }}}Copy the code

The MyEventBusIndex instance is then passed to EventBus.

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

Now let’s go back to the Subscriber Index and find the subscription 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);
    // we use a while loop, which means that when the subclass is done, the parent class will continue the search
    while(findState.clazz ! =null) {
        // Go to the index file to find subscription information
        findState.subscriberInfo = getSubscriberInfo(findState);
        if(findState.subscriberInfo ! =null) {
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            // Iterate over the subscription method
            for (SubscriberMethod subscriberMethod : array) {
                // Check if the subscription method has been added
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    // Not added, add the found subscription method to the subscription method listfindState.subscriberMethods.add(subscriberMethod); }}}else {
            // If EventBusIndex returns an empty subscription method, reflection is used to find the subscription method
            findUsingReflectionInSingleClass(findState);
        }
        // Find the 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 is found, and you need to look for the parent class this time
    if(findState.subscriberInfo ! =null&& findState.subscriberInfo.getSuperSubscriberInfo() ! =null) {
        SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
        // Make sure it is the parent class
        if (findState.clazz == superclassInfo.getSubscriberClass()) {
            returnsuperclassInfo; }}Eventbus.addindex (MyEventBusIndex()) is added to //subscriberInfoIndexes
    if(subscriberInfoIndexes ! =null) {
        for (SubscriberInfoIndex index : subscriberInfoIndexes) {
            // Get the subscription information by executing the getSubscriberInfo method in the MyEventBusIndex class
            SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
            if(info ! =null) {
                returninfo; }}}return null;
}
Copy the code

The principle is also very simple, the index file is generated at compile time, so that we do not need to look through reflection at runtime, directly through the index file. In addition, we can clearly see the distribution of our declared subscription methods through the generated index file.

Precautions for using Subscriber Index:

  • @SubscribeMethods and their classes must bepublic.
  • The event class must be public.
  • @SubscribeCannot be used in anonymous classes.

To subscribe to

Above we looked at how to find the subscription method, and then we took a closer look at how the subscription method implements the subscription action.

/** * A map collection of event types and Subscription object lists ** key -> eventType Event types * value -> Subscription object list, where Subscription is a wrapper class for subscribers and Subscription methods */
private finalMap<Class<? >, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;/** * A map collection that records the list of all event types to which the subscriber is subscribed ** key -> Subscriber * value -> list of all event types to which the subscriber is subscribed */
private finalMap<Object, List<Class<? >>> typesBySubscriber;private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    // Get the event type parameters from the subscription methodClass<? > eventType = subscriberMethod.eventType;// Construct a subscription object using subscriber and subscription methods
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    // By 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 no subscription method has been registered to subscribe to events of this type.
        // Create a new list and place the event type and the new list in the subscriptionsByEventType Map
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        // If the subscription object collection already contains this newSubscription
        if (subscriptions.contains(newSubscription)) {
            // Throws an exception to remind the user that the subscriber has subscribed to this type of event
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "+ eventType); }}// Iterate over the list of subscription objects
    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        // If the subscription method has a declared priority, the subscription method is added to the specified location according to the priority
        // Otherwise, add the subscription method to the end of the list of subscription objects
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            subscriptions.add(i, newSubscription);
            break; }}// Find subscribedEvents, a list of types for all events to which a subscriber subscribesList<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber);// If the subscriber does not already have any subscribe methods, subscribedEvents is null
    if (subscribedEvents == null) {
        // Create a list of all the event types subscribed to by the subscriber
        subscribedEvents = new ArrayList<>();
        // Place the subscriber and the new list in the typesBySubscriber map
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    // Add the event type to the list of event types
    subscribedEvents.add(eventType);

    // If the subscription method supports sticky events
    if (subscriberMethod.sticky) {
	// Whether to consider the event class hierarchy. Default 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 eventscheckPostStickyEventToSubscription(newSubscription, stickyEvent); }}}else {
	        // Get sticky events based on the event type
            Object stickyEvent = stickyEvents.get(eventType);
            // Check sending sticky eventscheckPostStickyEventToSubscription(newSubscription, stickyEvent); }}}Copy the code

Note that the subscribe method needs to run in a synchronized block, and you can refer to my other article on the synchronized keyword for Android programmers to relearn synchronized.

The cancellation

After looking at the registration, we then look at the logout, the method of logout is also very simple, a code can be completed.

EventBus.getDefault().unregister(this);
Copy the code

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 the subscriberList<Class<? >> subscribedTypes = typesBySubscriber.get(subscriber);if(subscribedTypes ! =null) {
        // Iterate over the list of event types
        for(Class<? > eventType : subscribedTypes) {// Unregister the subscriber by event type
            unsubscribeByEventType(subscriber, eventType);
        }
        // Remove the subscriber from the typesBySubscriber map
        typesBySubscriber.remove(subscriber);
    } else {
        //log prompt: Execute the logout action 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();
        // Iterate over 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 status to false, and remove it from the subscription object list
                subscription.active = false; subscriptions.remove(i); i--; size--; }}}}Copy the code

Send the event

Then it is to send the event, send the event is also very simple, the same sentence can be done.

 EventBus.getDefault().post(new MessageEvent());
Copy the code

Let’s take a closer look at the source.

public void post(Object event) {
    //PostingThreadState encapsulates event 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 if it is in the main thread
        postingState.isMainThread = isMainThread();
        // Set to sending
        postingState.isPosting = true;
        // Check whether the send is cancelled, if cancelled, 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, sending the events one by one
                postSingleEvent(eventQueue.remove(0), postingState); }}finally {
            // Reset the sending status
            postingState.isPosting = false;
            postingState.isMainThread = false; }}}private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    // Get the class object of the eventClass<? > eventClass = event.getClass();boolean subscriptionFound = false;
    // Whether to consider the event class hierarchy. Default is true
    if (eventInheritance) {
        // Find all event types of the superclassList<Class<? >> eventTypes = lookupAllEventTypes(eventClass);int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) { Class<? > clazz = eventTypes.get(h);// Send events according to the event typesubscriptionFound |= postSingleEventForEventType(event, postingState, clazz); }}else {
        // Send events according to the event type
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    // No subscription object was found to subscribe to this type of event, that is, no subscription method exists 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)); }}}/** * Based on the event type, the event is sent to the subscribing method * that subscribed to the event type@param event
 * @param postingState
 * @param eventClass
 * @returnTrue -> Find subscription methods that subscribe to events of this type; False -> There is no subscription method */ to subscribe to this type of event
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class
        eventClass) {
    // Subscribe to a list of objects
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        // Query the list of subscribers subscribing to events of this type based on the event type
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    // If the list of subscribed objects is not empty, events are sent to those subscribers one by one
    if(subscriptions ! =null && !subscriptions.isEmpty()) {
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted;
            try {
                // Send the event to the subscription object
                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) {
    // Select which thread mode to use to distribute the event according to the thread mode selected by the subscriber
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING:
            // Call subscription methods directly with reflection
            invokeSubscriber(subscription, event);
            break;
        case MAIN:
            if (isMainThread) {
                // If currently in the main thread, the subscription method is directly reflected
                invokeSubscriber(subscription, event);
            } else {
                // Use Handler to switch to the main thread and finally execute invokeSubscriber
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case MAIN_ORDERED:
            if(mainThreadPoster ! =null) {
                // Queue events and execute them in order 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 the thread is currently in the main thread, the thread pool will be used, and the child thread will be switched to process, eventually calling invokeSubscriber again
                backgroundPoster.enqueue(subscription, event);
            } else {
                // If you are currently in a child thread, the event is handled directly in that child thread
                invokeSubscriber(subscription, event);
            }
            break;
        case ASYNC:
            // No matter what thread you are on, you end up using the thread pool, switching to a child thread, and still calling invokeSubscriber
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: "+ subscription.subscriberMethod.threadMode); }}void invokeSubscriber(Subscription subscription, Object event) {
    try {
        // Call the 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); }}Copy the code

A simple summary: Find the corresponding list of subscription objects based on the type of event, traverse the list, and determine which thread processes the event based on the thread pattern of the subscription object.

Send sticky events

If you do not register a subscriber before sending a normal event, then the event you send will not be received and the event will be recycled. You can send a sticky event, and then you can register a subscriber, and once you subscribe, that subscriber will receive the sticky event. Let’s see from the source code, is how to achieve it!

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

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

/** * Sticky event ** key -> sticky event class * value -> sticky event */
private finalMap<Class<? >, Object> stickyEvents;public void postSticky(Object event) {
    // Synchronizes the lock to store stickyEvents
    synchronized (stickyEvents) {
        stickyEvents.put(event.getClass(), event);
    }
    // After the event is added, the post() method is called as usual to send the event
    post(event);
}
Copy the code

A set of stickyEvents is used to store stickyEvents, which, when stored, invoke the post() method as usual. ?? Well?? According to the above usage scenario, I first send sticky events, and then to register the subscription, then execute the POST method to send events, there is no corresponding subscriber ah, must be sent failure. So, come to think of it, to do this, subscribers should sign up for the subscription and then send the saved event. Back to the register -> subscribe method:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    // Get the event type parameters from the subscription methodClass<? > eventType = subscriberMethod.eventType;// Construct a subscription object using subscriber and subscription methods
    Subscription newSubscription = newSubscription(subscriber, subscriberMethod); . . Omit some code....// If the subscription method supports sticky events
    if (subscriberMethod.sticky) {
        // Whether to consider the event class hierarchy. Default is true
        if(eventInheritance) { Set<Map.Entry<Class<? >, Object>> entries = stickyEvents.entrySet();for(Map.Entry<Class<? >, Object> entry : entries) { Class<? > candidateEventType = entry.getKey();//eventType is the parent of candidateEventType
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    // Check sending sticky eventscheckPostStickyEventToSubscription(newSubscription, stickyEvent); }}}else {
            // Get sticky events based on the event type
            Object stickyEvent = stickyEvents.get(eventType);
            // Check sending sticky eventscheckPostStickyEventToSubscription(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()); }}Copy the code

Sure enough, the subscriber in the registered subscription method, if the current subscription method supports stickyEvents, will check the stickyEvents collection for the corresponding sticky event, and if it finds the sticky event, will send the event.


thinking

Design patterns

  • Singleton mode: To avoid frequent creation and destructionEventBus instanceThe overhead is used hereDCLTo create a singleton.
  • Builder mode: Basically open source libraries have a lot of parameters that can be configured by the user, so use builder mode to createEventBus instanceIt makes sense.

Upgrade path for EventBus 2 to EventBus 3

The EventBus 2 subscription method must start with onEvent because the thread mode needs to be specified by name, for example: onEvent, onEventMainThread, onEventBackground, onEventAsync.

EventBus V2.4.0 = EventBus V2.4.0

EventBus V24.. 0List<SubscriberMethod> findSubscribe Methods(Class<? > subscriberClass) { ... Omit some code...// Return the method declared by the class
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
        String methodName = method.getName();
        // Find the method that begins with onEvent
        if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {
            int modifiers = method.getModifiers();
            if((modifiers & Modifier.PUBLIC) ! =0 && (modifiers & MODIFIERS_IGNORE) == 0) { Class<? >[] parameterTypes = method.getParameterTypes();if (parameterTypes.length == 1) {
                    // Determine the thread mode according to the name of the declaration
                    // If onEventMainThread is specified as threadmode.mainthread
                    String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());
                    ThreadMode threadMode;
                    if (modifierString.length() == 0) {
                        threadMode = ThreadMode.PostThread;
                    } else if (modifierString.equals("MainThread")) {
                        threadMode = ThreadMode.MainThread;
                    } else if (modifierString.equals("BackgroundThread")) {
                        threadMode = ThreadMode.BackgroundThread;
                    } else if (modifierString.equals("Async")) { threadMode = ThreadMode.Async; }... Omit some code... }Copy the code

Each subscription method must be named in such a way that the display is inflexible for use, So EventBus 3 is annotated with @subscribe (threadMode = threadMode.*MAIN*, sticky = true, priority = 9).

Now that annotations are in place, they essentially get the method name and method parameters by reflection, and Java reflection is inefficient. Therefore, to improve efficiency, EventBus 3 also introduces subscriber Index, which uses KPT technology to generate index file during compilation and obtain subscription method information from index file. In this way, compared with obtaining subscription method through reflection during runtime, EventBus 3 greatly improves operation efficiency.

In order to further improve the operation efficiency, the library also uses the cache and object pool to recycle object instances and reduce the overhead caused by repeatedly creating and destroying objects. For example, loop through the FindState object to find subscription methods.

/** * Recycle the FindState object using arrays to reduce the overhead of repeatedly creating and destroying objects */
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];

/** * Fetch the FindState object */ from the pool
private FindState prepareFindState() {
    // Use a synchronization lock
    synchronized (FIND_STATE_POOL) {
        for (int i = 0; i < POOL_SIZE; i++) {
            FindState state = FIND_STATE_POOL[i];
            if(state ! =null) {
                // If the current position of the object is not empty, return, and empty
                FIND_STATE_POOL[i] = null;
                returnstate; }}}// If the pool is empty, create a new FindState object
    return new FindState();
}
Copy the code

Here, if you want to learn more about the synchronized keyword, you can refer to my other article about Android programmers relearning synchronized.

Read the source code, really great, the author can think of using reflection to find a subscription method, really great. At the same time, with the upgrade of the library, from specifying the dead subscription method name at the beginning to using annotations to specify subscription parameters, it has become more flexible and easy to use. Further considering the efficiency, new cache and object pool, subscriber index has been improved, which is really great.


At this point, the source code for EventBus is analyzed. If you want to see all the comments, you can click on Github EventBus All Comments.

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 consult with an open mind. In addition, if you think this article is good and helpful, please give me a like as encouragement, thank you ~ Peace~!