preface

Recently, I had a talk with a senior about learning methods. I talked a lot and gained a lot. In the process of communication, my senior gave me a saying: “Apply what you have learned to your knowledge.” After listening to them, I realized that there were big problems with the previous learning method. In the future, I need to combine more with practice to achieve the purpose of learning. Do you have a better way to learn? Welcome to leave a message.

Introduction to the

In this article, WE will talk about EventBus, which is familiar to everyone and used in daily development. Let’s take a look at the official introduction of EventBus. EventBus is an open source library for Android and Java that is loosely coupled using the publisher/subscriber model. EventBus simplifies code, eliminates dependencies, and speeds application development by enabling central communication to decouple classes with just a few lines of code.

EventBus

Simplify communication between components 2 separate event sender and receiver 3 Avoid problems caused by complex and error-prone dependencies and lifecycle 4.

EventBus features:

Simple but powerful: EventBus is a tiny library with an easy-to-learn API. However, by decoupling components, your software architecture can benefit greatly: subscribers do not know the sender when using events. Battle-tested: EventBus is one of the most commonly used Android libraries: Thousands of applications use EventBus, including very popular ones. More than a billion installed apps say it all. 3. High performance: Especially on Android, performance is critical. The EventBus was summarized and optimized. Could make it the fastest solution in its class. Convenient annotation-based APIS (without sacrificing performance) : Simply put the @SUBSCRIBE annotation into your subscriber method. Because the annotation creation time is indexed, EventBus does not need to reflect annotations while the application is running, which is very slow on Android. 5. Android mainthread delivery: When interacting with the UI, EventBus can deliver events in the main thread, regardless of how the event is published. Background thread delivery: EventBus can also use background threads to avoid UI blocking if your subscribers are running tasks. Event and Subscriber Inheritance: In EventBus, the object-oriented paradigm applies to the event and subscriber classes. Suppose event class A is A superclass of B. Published events of type B will also be published to subscribers interested in A. Similarly, consider inheritance of a subscriber class. Zero configuration: You can start using the default EventBus instance immediately, which is available anywhere in the code. Configurable: To adjust EventBus to your requirements, you can use builder mode to adjust its behavior.

EventBus is an EventBus. If you don’t use it, you’re Out.

use

This article uses the latest official version 3.1.1. Let’s write a small Demo according to the official usage steps.

Step 1: Define events

public class SayHelloEvent {
    private String message;

    public void sayHellow(String message) {
        this.message = message; }}Copy the code

Here the event is defined as a normal class, inside which a field is created and a method is created.

Step 2: Prepare subscribers


@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(SayHelloEvent event) { String message = event.getMessage(); . }@Override
public void onStart(a) {
    super.onStart();
    EventBus.getDefault().register(this);
}
 
@Override
public void onStop(a) {
    EventBus.getDefault().unregister(this);
    super.onStop();
}
Copy the code

Call eventbus.getDefault ().register(this) and unregister eventbus.getDefault ().unregister(this) where appropriate. Create a method that receives the event, onMessageEvent, annotated with @SUBSCRIBE, and the thread that receives the life event is the master thread.

Step 3: Send events

EventBus.getDefault().post(new SayHelloEvent("Hello EventBus!!"));
Copy the code

Call the eventbus.getDefault ().post method where you want to send the event, passing in the event object created in the first step. This is the end of how to use EventBus, which seems pretty simple, but there are a lot of caveats, which will be covered in code exploration.

Knowledge preparation

Before we start our source code analysis, we need to know three things about EventBus: role assignment, thread model, and event type.

Role assignment

Event

Event, which can be of any type, and EventBus sends globally based on the type of event.

Subscriber

Event subscribers, also known as event receivers. Prior to EventBus3.0, you used several receive methods that must begin with OnEvent to receive events. These methods are as follows: OnEvent, onEventMainThread, onEventBackgroundThread, and onEventAsync, but after 3.0, the processing method can be customized, but it is marked with the annotation @SUBSCRIBE and specifies the thread model. The threading model defaults to POSTING.

Publisher

Event publisher, which can publish events in any thread. When used, the eventBus.getDefault () method is usually called to get an EventBus object, followed by a chained programming call to the POST () method to send the event.

Threading model

The event model refers to the relationship between the thread of the event subscriber and the thread of the event publisher.

POSTING

Events are subscribed to and events are published on the same thread. This is the default setting. Event passing means minimal overhead because it completely avoids thread switching. Therefore, this is the recommended pattern for simple tasks that are known to be completed in a very short time and do not require a main thread. Event handlers using this pattern must return quickly to avoid blocking the publishing thread that might be the main thread.

MAIN

On Android, the user will be called in the Android main thread (UI thread). If the publishing thread is the main thread, the subscribing method is called directly, blocking the publishing thread. Otherwise, events are queued for delivery (non-blocking). Subscribers using this pattern must return quickly to avoid blocking the main thread. If not on Android, the behavior is the same as POSTING.

MAIN_ORDERED

On Android, the user will be called in the Android main thread (UI thread). Unlike {@link#MAIN}, events are always queued for delivery. This ensures that the POST call is non-blocking.

BACKGROUND

On Android, the user will be called in a background thread. If the publishing thread is not the main thread, the subscription method is called directly in the publishing thread. If the publishing thread is the main thread, EventBus uses a single background thread that passes all its events in sequence. Subscribers using this pattern should try to return quickly to avoid blocking background threads. If not on Android, always use background threads.

ASYNC

The subscriber will be called in a separate thread which is always separate from the publishing thread and the main thread. Publish events never wait for subscriber methods that use this pattern. Use this pattern if the execution of the subscriber method is likely to take some time (for network access, for example). Limit the number of concurrent threads by avoiding triggering a large number of long-running asynchronous subscriber methods at the same time. EventBus uses thread pools to effectively reuse threads notified from completed asynchronous subscribers.

The event type

Ordinary events

Normal event means that existing event subscribers can receive events sent by event senders, and registered event receivers cannot receive events after the event is sent. Normal events can be sent by calling the eventbus.getDefault ().post() method.

Viscous event

Sticky events are events that can be received either by event receivers registered before the event is sent or by event receivers registered after the event is sent. The difference here is that the event receiver needs to define the event receiver type, which can be declared by @SUBSCRIBE (threadMode = XXX, sticky = true); The eventBus.getDefault ().poststicky () method is called to send an event. The event type is normal by default.

Event priority

Subscriber priority to influence the order in which events are delivered. In the same delivery thread ThreadMode, a higher-priority subscriber receives events before other lower-priority subscribers. The default priority is 0. Note: Priority does not affect the delivery order between subscribers with different ThreadModes!

Source code analysis

Eventbus.getdefault () creates the EventBus object

This method is used for registering, unregistering, and sending events when used by EventBus, so let’s first look at what it does.

public class EventBus {
    static volatile EventBus defaultInstance;
    private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
    private finalMap<Class<? >, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;private finalMap<Object, List<Class<? >>> typesBySubscriber;private finalMap<Class<? >, Object> stickyEvents; .// 1. Singleton design returns EventBus
    public static EventBus getDefault(a) {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    // call the EventBus constructor
                    defaultInstance = newEventBus(); }}}returndefaultInstance; }...public EventBus(a) {
        // call the parameter constructor and pass in an EventBusBuilder object
        this(DEFAULT_BUILDER);
    }
    
    EventBus(EventBusBuilder builder) {
        / / log
        logger = builder.getLogger();
        // This collection can get subscribers based on the event type
        //key: event type, value: set of subscribers subscribing to the event
        subscriptionsByEventType = new HashMap<>();
        // The set of events to which the subscriber subscribes
        //key: subscriber, value: set of events to which the subscriber subscribes
        typesBySubscriber = new HashMap<>();
        // Set of sticky events
        //key: event Class object, value: event object
        stickyEvents = new ConcurrentHashMap<>();
        // The Android main thread handles eventsmainThreadSupport = builder.getMainThreadSupport(); mainThreadPoster = mainThreadSupport ! =null ? mainThreadSupport.createPoster(this) : null;
        //Background event sender
        backgroundPoster = new BackgroundPoster(this);
        // Asynchronous event sender
        asyncPoster = new AsyncPoster(this); indexCount = builder.subscriberInfoIndexes ! =null ? builder.subscriberInfoIndexes.size() : 0;
        // Subscribers subscribe to event lookups
        subscriberMethodFinder = newSubscriberMethodFinder(builder.subscriberInfoIndexes, builder.strictMethodVerification, builder.ignoreGeneratedIndex); logSubscriberExceptions = builder.logSubscriberExceptions; logNoSubscriberMessages = builder.logNoSubscriberMessages; sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent; sendNoSubscriberEvent = builder.sendNoSubscriberEvent; throwSubscriberException = builder.throwSubscriberException; eventInheritance = builder.eventInheritance; executorService = builder.executorService; }}Copy the code

Internally, the method first creates an EventBus object through the singleton pattern, and eventually calls its parameter constructor when creating an EventBus, passing in an EventBus.Builder object. Inside the parameterized constructor we initialize the properties, which have several important properties: The roles of the subscriptionsByEventType, typesBySubscriber, stickyEvents, and subscriberMethodFinder attributes are given in the comments.

register(Object subscriber)

After creating the EventBus object, you need to register event subscribers with EventBus.

public class EventBus {
    /**
     * Registers the given subscriber to receive events. Subscribers must call {@link #unregister(Object)} once they
     * are no longer interested in receiving events.
     * <p/>
     * Subscribers have event handling methods that must be annotated by {@link Subscribe}.
     * The {@link Subscribe} annotation also allows configuration like {@link
     * ThreadMode} and priority.
     */
    public void register(Object subscriber) {
        // Get the subscriber's Class object by reflectionClass<? > subscriberClass = subscriber.getClass();// 2. Gets the collection of events subscribed to by the subscriber through the subscriberMethodFinder object
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            // 3
            for(SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); }}}private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        // get the event typeClass<? > eventType = subscriberMethod.eventType;// 5, encapsulate the Subscription object
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        // Get the set of subscribers for the event by event type
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        If no subscriber subscribed to the event
        if (subscriptions == null) {
            // Create a collection and store it in the subscriptionsByEventType collection
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else { If any subscriber has subscribed to the event
            // Determine if any of these subscribers have repeated subscriptions
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "+ eventType); }}int size = subscriptions.size();
        // all subscribers of the event are traversed
        for (int i = 0; i <= size; i++) {
            // Insert according to the priority, if the lowest priority, insert to the end of the set
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break; }}// get the collection of all events subscribed to by the event subscriberList<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        // add the event to the collection
        subscribedEvents.add(eventType);
        
        // 12. Determine whether the event is a sticky event
        if (subscriberMethod.sticky) {
            if (eventInheritance) { // 13, determine the inheritance of the event, default is not inheritance
                // get all sticky events and iterate through them to determine the inheritance relationshipSet<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();
                        / / 15, call checkPostStickyEventToSubscription methodcheckPostStickyEventToSubscription(newSubscription, stickyEvent); }}}else{ Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); }}}private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        if(stickyEvent ! =null) {
            // if the sticky event is not emptypostToSubscription(newSubscription, stickyEvent, isMainThread()); }}private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        Depending on the type of threadMode, choose whether to reflect the calling method directly or to queue the event
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                if (isMainThread) {
                    // call by reflection
                    invokeSubscriber(subscription, event);
                } else {
                    // Add sticky events to the queue
                    / / in the end will invoke the EventBus. InvokeSubscriber (PendingPost PendingPost) method.
                    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); }}void invokeSubscriber(PendingPost pendingPost) {
        Object event = pendingPost.event;
        Subscription subscription = pendingPost.subscription;
        PendingPost.releasePendingPost(pendingPost);
        if(subscription.active) { invokeSubscriber(subscription, event); }}void invokeSubscriber(Subscription subscription, Object event) {
        try {
            subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
        } catch (InvocationTargetException e) {
            handleSubscriberException(subscription, event, e.getCause());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Unexpected exception", e); }}}public class SubscriberMethod {
    final Method method; // The Method object that handles the event
    final ThreadMode threadMode; // Thread model
    finalClass<? > eventType;// Event type
    final int priority; // Event priority
    final boolean sticky; // Is it a sticky event
    String methodString;
}

final class Subscription {
    final Object subscriber;
    final SubscriberMethod subscriberMethod;
}
Copy the code

Remember the code above 2 place by calling subscriberMethodFinder. FindSubscriberMethods (subscriberClass) method, access to all subscribers subscribe to events collection.

class SubscriberMethodFinder {
    private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
    private static finalMap<Class<? >, List<SubscriberMethod>> METHOD_CACHE =new ConcurrentHashMap<>();
    private static final int POOL_SIZE = 4;
    private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];
    
    List<SubscriberMethod> findSubscriberMethods(Class
        subscriberClass) {
        // select * from the previous cache
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if(subscriberMethods ! =null) {
            // if the cache was cached before, return it directly
            return subscriberMethods;
        }

        if (ignoreGeneratedIndex) { //ignoreGeneratedIndex is generally false
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            // get all subscription methods
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            // add to the cache set
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            returnsubscriberMethods; }}private List<SubscriberMethod> findUsingInfo(Class
        subscriberClass) {
        Get the FindState object from the array
        If there is a direct return, if a new FindState object is not created
        FindState findState = prepareFindState();
        // Initialize findState based on event subscribers
        findState.initForSubscriber(subscriberClass);
        while(findState.clazz ! =null) {
            // get the subscriberInfo and initialize it to null
            findState.subscriberInfo = getSubscriberInfo(findState);
            if(findState.subscriberInfo ! =null) {
                SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                for (SubscriberMethod subscriberMethod : array) {
                    if(findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); }}}else {
                // Get Method from subscriber by reflection
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }
    
    private FindState prepareFindState(a) {
        synchronized (FIND_STATE_POOL) {
            for (int i = 0; i < POOL_SIZE; i++) {
                FindState state = FIND_STATE_POOL[i];
                if(state ! =null) {
                    FIND_STATE_POOL[i] = null;
                    returnstate; }}}return new FindState();
    }
    
    private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // all declared methods in the subscriber are placed in an array
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // get the public method declared in the subscriber and set the parent class to be skipped
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        // Iterate over these methods
        for (Method method : methods) {
            Get method modifiers :public, private, etc
            int modifiers = method.getModifiers();
            // 12、订阅方法为public同时不是abstract、static
            if((modifiers & Modifier.PUBLIC) ! =0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                // 13, method parameter type arrayClass<? >[] parameterTypes = method.getParameterTypes();if (parameterTypes.length == 1) {
                    // Get method annotations
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    // 15, if there are annotations
                    if(subscribeAnnotation ! =null) { Class<? > eventType = parameterTypes[0];
                        // Add method and eventType to findState
                        if (findState.checkAdd(method, eventType)) {
                            // Get the threadMode object in the annotation
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            // create a SubscriberMethod object and add it to findState
                            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"); }}}// Get all subscriber methods from findState and release them
    private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
        // Get a collection of all subscription methods for the subscriber
        List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
        // findState retrievals
        findState.recycle();
        synchronized (FIND_STATE_POOL) {
            for (int i = 0; i < POOL_SIZE; i++) {
                if (FIND_STATE_POOL[i] == null) {
                    FIND_STATE_POOL[i] = findState;
                    break; }}}// return the collection
        returnsubscriberMethods; }}Copy the code

Here, the registration process is almost finished, you can have a rest and relax.

summary

In the process of subscriber registration, the following steps are mainly carried out, detailed steps are in the code notes, you can view.

1. Create an EventBus object based on the singleton design pattern and initialize the EventBus by creating an EventBus.Builder object with three relatively important collections and a SubscriberMethodFinder object. 2. Call the Register method and first get the subscriber’s Class object through reflection. 3. Get the set of all subscribed events from the subscriber through the SubscriberMethodFinder object, which is first fetched from the cache and returned directly if there is one in the cache; If there are none in the cache, the annotated methods are reflected through the subscriber and returned in the collection. 4. Loop through the collection obtained in step 3 to bind subscribers to events. 5. After binding, it determines whether the bound event is a sticky event, and if it is, it calls postToSubscription directly to send the previously sent sticky event to the subscriber. And this makes a lot of sense, because when we talked about stickiness events, if a subscriber that was registered before the stickiness event was sent, when the stickiness event was sent, they would receive that event; That’s why subscribers who sign up after a sticky event is sent can also receive the event.

EventBus.getDefault().post(Object event)

public class EventBus {...public void post(Object event) {
        // get the PostingThreadState of the current thread, which is a ThreadLocal
        PostingThreadState postingState = currentPostingThreadState.get();
        // set of events for the current thread
        List<Object> eventQueue = postingState.eventQueue;
        // add the event to the collection
        eventQueue.add(event);

        // Check whether events are being sent
        if(! postingState.isPosting) {// Check whether it is the main thread
            postingState.isMainThread = isMainThread();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                // as long as there are events in the event set, keep sending
                while(! eventQueue.isEmpty()) { postSingleEvent(eventQueue.remove(0), postingState); }}finally {
                postingState.isPosting = false;
                postingState.isMainThread = false; }}}/ / currentPostingThreadState contains PostingThreadState ThreadLocal object
    ThreadLocal is an internal data store class that stores data in a specified thread independently of each other.
    // It creates an array of generic objects that it wraps around. Different threads have different array indexes. Each thread uses the get method to get the corresponding thread data.
    private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue(a) {
            return newPostingThreadState(); }};// Data stored in each thread
    final static class PostingThreadState {
        final List<Object> eventQueue = new ArrayList<>(); // The event queue for the thread
        boolean isPosting; // Whether it is being sent
        boolean isMainThread; // Whether to send in the main thread
        Subscription subscription; // Encapsulation of event subscribers and subscription events
        Object event; // Event object
        boolean canceled; // Whether to cancel sending}...private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        // Get the event Class objectClass<? > eventClass = event.getClass();boolean subscriptionFound = false;
        if (eventInheritance) { // eventInheritance is generally true
            // Find the class set of all the parent classes of the current event and the interface that implements itList<Class<? >> eventTypes = lookupAllEventTypes(eventClass);int countTypes = eventTypes.size();
            for (int h = 0; h < countTypes; h++) { Class<? > clazz = eventTypes.get(h);// send a single event through the collectionsubscriptionFound |= postSingleEventForEventType(event, postingState, clazz); }}else {
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        if(! subscriptionFound) {if (logNoSubscriberMessages) {
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            if(sendNoSubscriberEvent && eventClass ! = NoSubscriberEvent.class && eventClass ! = SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event)); }}}private staticList<Class<? >> lookupAllEventTypes(Class<? > eventClass) {synchronized (eventTypesCache) {
            // Get the set of eventsList<Class<? >> eventTypes = eventTypesCache.get(eventClass);if (eventTypes == null) { // If it is null
                eventTypes = newArrayList<>(); Class<? > clazz = eventClass;while(clazz ! =null) {
                    eventTypes.add(clazz); // Add events
                    addInterfaces(eventTypes, clazz.getInterfaces()); // Add the interface class for the current event
                    clazz = clazz.getSuperclass();// Gets the parent class of the current event
                }
                eventTypesCache.put(eventClass, eventTypes);
            }
            returneventTypes; }}// Loop to add the current event's interface class
    static void addInterfaces(List
       
        > eventTypes, Class
        [] interfaces)
       > {
        for(Class<? > interfaceClass : interfaces) {if(! eventTypes.contains(interfaceClass)) { eventTypes.add(interfaceClass); addInterfaces(eventTypes, interfaceClass.getInterfaces()); }}}private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class
        eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            // get all subscribers based on events
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if(subscriptions ! =null && !subscriptions.isEmpty()) {
            // 9
            for (Subscription subscription : subscriptions) {
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false;
                try {
                    // 10. Send events to subscribers
                    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) {
        Call the subscription method according to the thread pattern of the subscription method
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING: // The default type, which means that the send event operation calls the subscriber's response method directly, without switching between threads
                invokeSubscriber(subscription, event);
                break;
            case MAIN: // Main thread, indicating that the subscriber's response method receives events on the main thread
                if (isMainThread) { If the sender is in the main thread
                    invokeSubscriber(subscription, event);// Call the subscriber's response method directly
                } else { If the sender of the event is not the main thread
                    // Add to the queue of mainThreadPoster and call the response method on the main thread
                    mainThreadPoster.enqueue(subscription, event); 
                }
                break;
            case MAIN_ORDERED:// Main thread priority mode
                if(mainThreadPoster ! =null) {
                    
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    Call the response method in the sender's thread if it is not the main thread
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:
                if (isMainThread) {
                    If the event sender is on the main thread, join the queue of backgroundPoster and call the response method in the thread pool
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    // If it is not the main thread, the response method is called in the thread of the event sender
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:
                // There is no thread judgment, that is, the response method is called in the child thread whether it is in the main thread or not
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: "+ subscription.subscriberMethod.threadMode); }}... }Copy the code

summary

The analysis of sending events is over here. Let’s summarize what event sending does.

1. Get the current thread’s event collection, and add the event to the collection. 2. Through the loop, send events as long as there are events in the event set. 3. Get the Class object of the event. Find the Class collection of all the parent classes of the current event and the interface that implements it. Iterate over the collection and send by calling the method that sends a single event. 4. Get the set of all subscribers subscribing to it according to the event, traverse the set, and send the event to the subscribers. 5. When sent to the subscriber, the subscription method is called according to the thread mode of the subscription method. If thread switching is required, the subscription method is called by switching threads; Otherwise, call directly.

The issue of thread switching will be explored in a separate topic later in the article.

EventBus.getDefault().postSticky(Object event)

Next, let’s look at sending sticky events.

public class EventBus {
    public void postSticky(Object event) {
        synchronized (stickyEvents) {
            // add the event to the sticky event set
            stickyEvents.put(event.getClass(), event);
        }
        // 2. Send eventspost(event); }}Copy the code

summary

Sending sticky events does two things:

1. Add sticky events to the set of sticky events in the EventBus object. When a new subscriber enters, if the subscriber subscribes to the sticky event, it can be directly sent to the subscriber. 2. Send sticky events to existing event subscribers.

EventBus.getDefault().unregister(Object subscriber)

Having explored subscriber registration and message sending, let’s look at the method of unregistration.

public class EventBus {...public synchronized void unregister(Object subscriber) {
        // get all events subscribed to by the subscriberList<Class<? >> subscribedTypes = typesBySubscriber.get(subscriber);if(subscribedTypes ! =null) {
            // 2
            for(Class<? > eventType : subscribedTypes) {// 3. Remove the subscriber from the collection of all subscribers subscribing to the event
                unsubscribeByEventType(subscriber, eventType);
            }
            // Remove the subscriber from the collection
            typesBySubscriber.remove(subscriber);
        } else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: "+ subscriber.getClass()); }}private void unsubscribeByEventType(Object subscriber, Class
        eventType) {
        // Get all subscribers of the event
        List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if(subscriptions ! =null) {
            int size = subscriptions.size();
            // iterate over the collection
            for (int i = 0; i < size; i++) {
                Subscription subscription = subscriptions.get(i);
                // Remove the subscriber from the collection
                if (subscription.subscriber == subscriber) {
                    subscription.active = false; subscriptions.remove(i); i--; size--; }}}}... }Copy the code

summary

There are two things you can do to unregister:

1. Get all subscription methods of the subscriber and iterate over them. Then we get all the set of subscribers corresponding to each method and remove the subscribers from the set. Remove all subscription methods from the subscriber.

How does EventBus switch between threads?

Since the sending of the event involves the switching of the future, it is more likely to be mentioned in the usual interview, so it is studied as a separate topic.

    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED:
                if(mainThreadPoster ! =null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    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

This method calls the subscriber’s subscription method based on the ThreadMode type. ThreadMode is explained earlier, if not more clearly. There are two aspects of future switching involved in this method: mainThreadPoster and backgroundPoster. How ASYNC mode works will also be explored here.

mainThreadPoster.enqueue

MainThreadPoster is created when EventBus initializes, so let’s take a look.

.private final MainThreadSupport mainThreadSupport;
    private finalPoster mainThreadPoster; . EventBus(EventBusBuilder builder) { ...// create mainThreadSupport
        mainThreadSupport = builder.getMainThreadSupport();
        // Create mainThreadPostermainThreadPoster = mainThreadSupport ! =null ? mainThreadSupport.createPoster(this) : null; . }Copy the code

Two objects are created: mainThreadSupport and mainThreadPoster. Let’s first look at what mainThreadSupport is.

    MainThreadSupport getMainThreadSupport(a) {
        if(mainThreadSupport ! =null) {
            return mainThreadSupport;
        } else if (Logger.AndroidLogger.isAndroidLogAvailable()) {
            // Get the main thread Looper object, which may be empty again
            Object looperOrNull = getAndroidMainLooperOrNull();
            / / according to the main thread which object returns AndroidHandlerMainThreadSupport object
            return looperOrNull == null ? null :
                    new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull);
        } else {
            return null; }}Object getAndroidMainLooperOrNull(a) {
        try {
            // return the main thread Looper
            return Looper.getMainLooper();
        } catch (RuntimeException e) {
            return null; }}public interface MainThreadSupport {
    boolean isMainThread(a);
    Poster createPoster(EventBus eventBus);
    
    class AndroidHandlerMainThreadSupport implements MainThreadSupport {
        private final Looper looper;
        public AndroidHandlerMainThreadSupport(Looper looper) {
            // Local initialization based on the looper object passed in externally
            this.looper = looper;
        }

        @Override
        public boolean isMainThread(a) {
            // Determine whether the thread is the master thread
            return looper == Looper.myLooper();
        }

        @Override
        public Poster createPoster(EventBus eventBus) {
            // Create a HandlerPoster object when the createPoster method is called
            return new HandlerPoster(eventBus, looper, 10); }}}Copy the code

We see that mainThreadSupport is actually created from the main thread’s Looper object. MainThreadSupport is actually an interface, So here return AndroidHandlerMainThreadSupport object is its implementation class. Let’s have a look at mainThreadPoster object, when creating mainThreadPoster objects, invoke the mainThreadSupport. CreatePoster method, because the mainThreadSupport is an interface, So it actually calls the createPoster method of its implementation class object, creates a HandlerPoster object inside the method and returns, let’s look at HandlerPoster.

// This class inherits from Handler and implements the Poster interface
public class HandlerPoster extends Handler implements Poster {

    private final PendingPostQueue queue;
    private final int maxMillisInsideHandleMessage;
    private final EventBus eventBus;
    private boolean handlerActive;

    protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
        // This Handler object is the Handler for the main thread
        super(looper);
        this.eventBus = eventBus;
        this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
        // Create an event queue
        queue = new PendingPostQueue();
    }

    public void enqueue(Subscription subscription, Object event) {
        // Encapsulate a PendingPost object based on the arguments passed in
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            // Add PendingPost to the queue
            queue.enqueue(pendingPost);
            if(! handlerActive) { handlerActive =true;
                // Call sendMessage to send the event back to the main thread
                // The following handleMessage method is eventually called
                if(! sendMessage(obtainMessage())) {throw new EventBusException("Could not send handler message"); }}}}@Override
    public void handleMessage(Message msg) {
        boolean rescheduled = false;
        try {
            long started = SystemClock.uptimeMillis();
            // Infinite loop
            while (true) {
                PendingPost pendingPost = queue.poll();
                // Double check to make sure pendingPost is not null, if it is, jump out of the loop
                if (pendingPost == null) {
                    synchronized (this) {
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
                            handlerActive = false;
                            return; }}}// Call the subscriber's subscription method with the reflected method.
                eventBus.invokeSubscriber(pendingPost);
                long timeInMethod = SystemClock.uptimeMillis() - started;
                if (timeInMethod >= maxMillisInsideHandleMessage) {
                    if(! sendMessage(obtainMessage())) {throw new EventBusException("Could not send handler message");
                    }
                    rescheduled = true;
                    return; }}}finally{ handlerActive = rescheduled; }}}Copy the code

MainThreadPoster. Enqueue We’ve explored this, but it’s still running the Handler mechanism.

backgroundPoster.enqueue

BackgroundPoster is also initialized when EventBus is initialized

public class EventBus {...private final BackgroundPoster backgroundPoster;
    // Thread pool object
    private final ExecutorService executorService;
    EventBus(EventBusBuilder builder) {
        ...
        backgroundPoster = new BackgroundPoster(this); . }... }public class EventBusBuilder {
    // Create a thread pool
    private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();
    
    ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
}

public class Executors {
    public static ExecutorService newCachedThreadPool(a) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      newSynchronousQueue<Runnable>()); }}Copy the code

This also initializes a thread pool object, which will be used later. Take a look at the internal structure of BackgroundPoster.

final class BackgroundPoster implements Runnable.Poster {
    private final PendingPostQueue queue;
    private final EventBus eventBus;
    private volatile boolean executorRunning;

    BackgroundPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        // Initialize the queue
        queue = new PendingPostQueue();
    }
    
    public void enqueue(Subscription subscription, Object event) {
        // Encapsulate the PendingPost object
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            // Add the PendingPost object to the queue
            queue.enqueue(pendingPost);
            if(! executorRunning) { executorRunning =true;
                // The thread pool initialized earlier is used here
                eventBus.getExecutorService().execute(this); }}}// Thread pool execution callback
    @Override
    public void run(a) {
        try {
            try {
                // Infinite loop
                while (true) {
                    // Get PendingPost in the queue to double check
                    // If null is returned, end the loop
                    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; }}}// Call the subscriber's subscription method using reflectioneventBus.invokeSubscriber(pendingPost); }}catch (InterruptedException e) {
                eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e); }}finally {
            executorRunning = false; }}}Copy the code

From the code above we can see that backgroundposter.enqueue actually uses thread pooling.

asyncPoster.enqueue

Let’s first look at the internal structure of asyncPoster.

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

There is a sense of deja vu in its internal structure, which is, yes, a simplified version of BackgroundPoster, but the principle is the same, again using thread pools.

conclusion

This is the end of the exploration of EventBus. The key steps are given in the code notes. If there is anything wrong, please criticize and correct it. One final question: What’s wrong with EventBus?

The resources

EventBus website