EventBus 3.0 Advancement: Complete Analysis of Source Code and Design Patterns

Posted by davey10101 on Thu, 16 May 2019 08:11:30 +0200

EventBus 3.0 Advancement: Complete Analysis of Source Code and Design Patterns

Preface

In the last article: Preliminary Exploration of EventBus 3.0: Introduction and Full Analysis of Its Use In this article, the author introduces the usage of EventBus 3.0 for you, and I believe you are familiar with the use of EventBus 3.0. We learn to use an open source library, not only to know how to use it, but also to have a certain familiarity with its implementation principles, learning and learning from its excellent implementation ideas, which is of great significance to our future development or our own open source projects. So today's article is the advance of EventBus 3.0. It analyses the principle of its implementation and the design pattern it uses.

This article is an introduction.

Because EventBus is complex, this article is also quite long, so this article is divided into the following parts: creation, registration, sending events, analysis of sticky events, and final thinking. Readers can selectively select a part to read.

Realization principle

Establish

As mentioned in the previous article, if you want to register as a subscriber, you must call the following in a class:

EventBus.getDefault().register(this);

So let's look at the source code for getDefault(). EventBus#getDefault():

public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
}

As you can see, the singleton pattern is used here, and it is a double-checked singleton to ensure that only one instance of EvenBus exists in different threads.

Singleton pattern: A class has only one instance, and it instantiates itself to the whole system.

However, let's look at how EventBus is constructed:

/** 
  * Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a 
  * central bus, consider {@link #getDefault()}.
  */
public EventBus() {
    this(DEFAULT_BUILDER);
}

Its construction method is not set to private! Why is that? From the annotations, we can see that each EventBus is independent and handles its own events, so there can be multiple EventBus. The instance obtained by getDefault() method is the EventBus that has been built for us. It is a singleton, and the instance obtained by this method is the same instance at any time. In addition, we can build EventBus with different functions through builders.
Let's continue to see that this(DEFAULT_BUILDER) above invokes another constructor:

  EventBus(EventBusBuilder builder) {
    subscriptionsByEventType = new HashMap<>();
    typesBySubscriber = new HashMap<>();
    stickyEvents = new ConcurrentHashMap<>();
    mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
    backgroundPoster = new BackgroundPoster(this);
    asyncPoster = new AsyncPoster(this);
    //A series of builder assignments
}

As you can see, member variables are initialized in a series of ways, so let's look at some key member variables:

private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
private final Map<Object, List<Class<?>>> typesBySubscriber;
private final Map<Class<?>, Object> stickyEvents;

Subscription ByEventType: With event (event class) as the key and Subscription as the value, Subscription is a thread-safe container for searching for subscribers here after the event is sent, and Subscription is a CopyOnWriteArrayList. You'll be curious to see what Subscription is, but you'll see it later. Now let's say in advance: Subscription is an encapsulation class that encapsulates both subscribers and subscription methods.
TypeesBySubscriber: With the subscriber class as the key and the event event class as the value, this map will be manipulated when a register or unregister operation is performed.
Sticky Events: Sticky events are preserved, as described in detail in the previous article.
Back to the construction method of EventBus, we instantiate three Posters: mainThreadPoster, backgroundPoster, asyncPoster, etc. These three Posters are used to deal with sticky events, which we will discuss next. Next, there is a series of assignments to builder, which uses the builder model.

Builder pattern: Separate the construction of a complex object from its representation so that the same construction process can create different representations.

The builder here is EventBusBuilder. A series of methods are used to configure the properties of EventBus. Examples obtained by getDefault() method will have default configurations. As mentioned above, the construction method of EventBus is public, so we can get EventBus with different functions by setting different properties to EventBusBuilder. So let's list some common attributes to explain:

//By default, EventBus considers a superclass of events, that is, if an event inherits from a superclass, it will also be sent to the subscriber as an event.
//If set to false, EventBus only considers the event class itself.
boolean eventInheritance = true;
public EventBusBuilder eventInheritance(boolean eventInheritance) {
    this.eventInheritance = eventInheritance;
    return this;
}

//When subscription methods start with onEvent, they can be called to skip validation of method names, which are stored in List s.
List<Class<?>> skipMethodVerificationForClasses;
public EventBusBuilder skipMethodVerificationFor(Class<?> clazz) {
    if (skipMethodVerificationForClasses == null) {
        skipMethodVerificationForClasses = new ArrayList<>();
    }
    skipMethodVerificationForClasses.add(clazz);
    return this;
}

//Refer to the source code for more property configurations, with comments
//...

Instead of using the getDefault() method, we create an EventBus manually through the builder pattern:

EventBus eventBus = EventBus.builder()
        .eventInheritance(false)
        .sendNoSubscriberEvent(true)
        .skipMethodVerificationFor(MainActivity.class)
        .build();

register

Okay, that's a big push on the creation of EventBus. Next, let's go on with the registration process. To make a class a subscriber, the class must have a subscription method that annotates the method marked with @Subscribe, and then calls register() to register. So let's look directly at EventBus#register().

register

public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

First we get the class of the subscriber class, and then we give it to SubscriberMethodFinder.findSubscriberMethods() for processing. The returned results are stored in List < SubscriberMethod >. From this, we can infer that the subscription method is found by the above method and saved in the collection. Then we can directly look at this method, SubscriberMethodFinder#dSubscriberMethod ().

findSubscriberMethods

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    //First, take subscriberMethodss out of the cache, and if so, return the obtained method directly.
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
    //From EventBusBuilder, ignoreGenerateIndex is generally false
    if (ignoreGeneratedIndex) {
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        //Put the acquired subscriberMeyhods into the cache
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

As you can see from the above logic, the findUsingInfo method is usually called. Let's look at the SubscriberMethodFinder#findUsingInfo method:

findUsingInfo

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    //Prepare a FindState that holds information about the subscriber class
    FindState findState = prepareFindState();
    //Initialization of FindState
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        //Get the subscriber's information and return null at first
        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 {
            //1. Here we are
            findUsingReflectionInSingleClass(findState);
        }
        //Move to the parent class to continue searching
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}

The internal class FindState is used to store subscriber class information. Let's look at its internal structure:

FindState

static class FindState {
    //List of subscription methods
    final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
    //Take event as key and method as value
    final Map<Class, Object> anyMethodByEventType = new HashMap<>();
    //Generate a method as key with the name of the method and value with the subscriber class
    final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
    final StringBuilder methodKeyBuilder = new StringBuilder(128);

    Class<?> subscriberClass;
    Class<?> clazz;
    boolean skipSuperClasses;
    SubscriberInfo subscriberInfo;

    //Initialization of FindState
    void initForSubscriber(Class<?> subscriberClass) {
        this.subscriberClass = clazz = subscriberClass;
        skipSuperClasses = false;
        subscriberInfo = null;
    }

    //Omission...
}

As you can see, the inner class saves the information of subscribers and their subscription methods, saves them with Map one by one, and then initializes them with initForSubscriber, where subscriberInfo is initialized as null, so in Subscriber MethodFinder#findUsingInfo, the number 1 code is called directly, that is, Subscriber MethodFinder#UsingReflectionInSingleClass is called. This method is very important!!! Inside this method, we use reflection to scan the subscriber class, find out the subscription method, and save it with the Map above. Let's look at this method.

findUsingReflectionInSingleClass

  private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        // This is faster than getMethods, especially when subscribers are fat classes like Activities
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
        methods = findState.clazz.getMethods();
        findState.skipSuperClasses = true;
    }
    for (Method method : methods) {
        //Get modifiers for methods
        int modifiers = method.getModifiers();
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            //Get the parameter type of the method
            Class<?>[] parameterTypes = method.getParameterTypes();
            //If the number of parameters is one, continue
            if (parameterTypes.length == 1) {
                //Get the @Subscribe annotation for this method
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    //The parameter type is the event type.
                    Class<?> eventType = parameterTypes[0];
                    // 2. Call checkAdd method
                    if (findState.checkAdd(method, eventType)) {
                        //Extracting threadMode from Annotations
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        //Create a new SubscriberMethod object and add it to the subscriberMethod collection of findState
                        findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                    }
                }
            //If strict validation is enabled and the current method has the @Subscribe annotation, an exception will be thrown for a method that does not meet the requirements.
            } 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");
        }
    }
}

Although this method is relatively long, the logic is very clear, one by one to determine whether there is a subscription method in the subscriber class. If it meets the requirements, and the code 2 calls FindState#checkAdd method to return true, the method will be saved in subscriberMethods of findState. SubscriberMethod is a class used to save subscription methods. So let's see what FindState#checkAdd does.

checkAdd

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.
    Object existing = anyMethodByEventType.put(eventType, method);
    if (existing == null) {
        return true;
    } else {
        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);
    }
}

As can be seen from the annotations, there are two checks, the first one is to determine the type of event type, and the second one is to judge the complete signature of the method. First, put eventType and method into anyMethodByEventType Map by anyMethodByEventType (mentioned above), and the put method returns the last value of the same key, so if there is no other way to subscribe to the event before, existing should be null and can return true directly; otherwise, it is an instance of a subscription method. Next step is to be judged. Next, the checkAddWithMethodSignature() method is called.

checkAddWithMethodSignature

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

    //Method Key consists of method name and event name
    String methodKey = methodKeyBuilder.toString();
    //Gets the class name of the class where the current method resides
    Class<?> methodClass = method.getDeclaringClass();
    //Assign subscriberClassByMethodKey and return the class name of the previous same key
    Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
    if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
        // Only add if not already found in a sub class
        return true;
    } else {
        // Revert the put, old class is further down the class hierarchy
        subscriberClassByMethodKey.put(methodKey, methodClassOld);
        return false;
    }
}

From the above code, the method first obtains the method key, method Class, etc. of the current method, and assigns it to subscriberClassByMethodKey. If the method has the same signature, it returns the old value to methodClassOld. Then it is an if judgment to determine whether the method ClassOld is empty. Because the method Old must be null when the method is first invoked, it can be straightforward at this time. And back to true. However, there is a later judgment, methodClassOld. is Assignable From (methodClass), which means whether methodClassOld is the parent or the same class of methodClass. If both conditions are not met, then false is returned, and the current method will not be added as a subscription method.

So, I've talked a lot about the source code of checkAdd and checkAddWithMethodSignature methods, so what exactly do these two methods do? From the logic of the two methods, the first level judges whether there are multiple methods subscribing to the event based on EvetType, while the second level judges whether there are multiple methods subscribing to the event based on the complete method signature (including method name and parameter name). The following is the author's understanding:

The first case is that a class has multiple subscription methods with different method names, but their parameter types are the same (although they are not usually written like this, but this is not excluded). When traversing these methods, the checkAdd method will be called many times. Because existing is not null, the checkAddWithMethodSignature method will be called again, but because each party has the same parameter type. The names of the methods are different, so the method ClassOld will always be null, so it will return true. That is, a class is allowed to have multiple subscription methods with the same parameters.

The second case: Class B inherits from Class A, and each class has the same subscription method. In other words, the subscription method of Class B inherits and rewrites from Class A. They all have the same method signature. The method traversal starts with a subclass, class B. In the checkAddWithMethodSignature method, the method ClassOld is null, and the subscription method of class B is added to the list. Next, find the subscription method of class A upward, because method ClassOld is not null and obviously class B is not the parent class of class A. methodClassOld. isAssignable From (methodClass) also returns false, then false. That is to say, the subclass inherits and rewrites the subscription method of the parent class, so only the subscription method of the subclass is added to the subscriber list, and the method of the parent class is ignored.

Let's go back to the findUsingReflectionInSingleClass method. After traversing all the methods of the current class, we go back to the findUsingInfo method, and then we execute the last line of code, that is, return getMethodsAndRelease (findState). So let's move on to the SubscriberMethodFinder#getMethodsAndRelease method.

getMethodsAndRelease

private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
    //Get subscriberMethods from findState and put them in the new ArrayList
    List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
    //Recycle findState
    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 subscriberMethods;
}

Through this method, subscriberMethods are returned layer by layer until EventBus register () method is returned. Finally, each subscribe method is traversed and subscribe(subscriber, subscriberMethod) method is called. So let's continue to look at the EventBus subscribe method.

subscribe

In this method, we mainly realize the direct association between subscription methods and events, that is, registration, which is put into the key Map s mentioned above: subscriptions ByEventType, types BySubscriber, stickyEvents.

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;
    //Encapsulate subscriber and subscriberMethod as Subscription
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    //Get a specific Subscription based on the event type
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    //If null, the subscriber has not registered the event
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        //If it is not null and contains the subscription, then the subscriber has registered the event and throws an exception.
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

    //Set the location of subscriptions according to priority, and the higher priority will be notified first.
    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }

    //Get all its subscription events based on subscriber
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        //Put subscribers, events into the Map of typesBySubscriber
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    //Here's how to deal with sticky events
    if (subscriberMethod.sticky) {
        //As you can see from EventBusBuilder, EvetInheritance defaults to 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();
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            //According to EvetType, get specific events from the stickyEvents list
            Object stickyEvent = stickyEvents.get(eventType);
            //Distribution events
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

So far, the registration process has been basically analyzed, and on the handling of the last sticky event, not to mention here, the following will be described in detail. It can be seen that the whole registration process is very long, the method call stack is very deep, and the methods are constantly jumping. In order to facilitate the reader's understanding, a flow chart is given below. Readers can combine the flow chart and the above detailed explanation to understand.


Registration process

Cancellation

Revocation corresponds to registration. When the subscriber no longer needs events, we need to cancel the subscriber by calling the following code:

EventBus.getDefault().unregister(this);

So let's analyze how the logout process is implemented. First, look at EventBus#unregister:

unregister

public synchronized void unregister(Object subscriber) {
    //Get all events it subscribes to based on the current subscriber
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        //Traveling through all subscription events
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        //Remove the subscriber from typesBySubscriber
        typesBySubscriber.remove(subscriber);
    } else {
        Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

EventBus#unsubscribeByEventType is called above, and the subscribers and events are passed in as parameters, so the relationship between them should be broken.

unsubscribeByEventType

private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    //Get the corresponding subscriptions set from subscriptions ByEventType according to the event type
    List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions != null) {
        int size = subscriptions.size();
        //Traverse through all subscriptions and remove them one by one
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
            if (subscription.subscriber == subscriber) {
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}

As you can see, the logic of the above two methods is very clear. Both of them remove the relevant subscriber-related information from the type BySubscriber or subscriptions ByEventType. The logout process is much simpler than the registration process. In fact, the registration process mainly focuses on how to find the subscription method.

Sending events

Next, we analyze the process of sending an event. Generally, sending an event calls the following code:

EventBus.getDefault().post(new MessageEvent("Hello !....."));`

Let's look at the EventBus#post method.

post

public void post(Object event) {
    //1. Get a postingState
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    //Queuing events
    eventQueue.add(event);

    if (!postingState.isPosting) {
        //Determine whether the current thread is the main thread?
        postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
        postingState.isPosting = true;
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            //As long as the queue is not empty, events are continuously retrieved from the queue for distribution.
            while (!eventQueue.isEmpty()) {
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

The logic is very clear. First, get a Posting ThreadState. What is Posting ThreadState? Let's look at its class structure:

PostingThreadState

final static class PostingThreadState {
    final List<Object> eventQueue = new ArrayList<Object>();
    boolean isPosting;
    boolean isMainThread;
    Subscription subscription;
    Object event;
    boolean canceled;
}

As you can see, the Posting ThreadState encapsulates the information of the current thread, as well as subscribers, subscription events and so on. So how do you get this Posting ThreadState? Let's go back to the post() method and look at the number 1 code, where we get PostingThreadState through currentPostingThreadState.get(). So what is current Posting ThreadState?

private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
    @Override
    protected PostingThreadState initialValue() {
        return new PostingThreadState();
    }
};

CurrtPosting ThreadState was originally a ThreadLocal, and ThreadLocal was exclusive to each thread, and its data could not be accessed by other threads, so it was thread-safe. Let's go back to the Post() method again and move on to a while loop, where events are constantly taken out of the queue and distributed, calling the EventBus#postSingleEvent method.

postSingleEvent

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    //As mentioned above, the event Inheritance defaults to true, that is, EventBus considers the inheritance tree of events.
    //If the event inherits from the parent class, the parent class is also sent as an event.
    if (eventInheritance) {
        //Find all parent classes of the event
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        //Traveling through all events
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    //If no subscriber has been found to subscribe to the event
    if (!subscriptionFound) {
        if (logNoSubscriberMessages) {
            Log.d(TAG, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            post(new NoSubscriberEvent(this, event));
        }
    }
}

From the above logic, for an event, its parent class is searched by default and sent to the subscriber as one of the events. Then EventBus#postSingleEventForEventType is invoked to pass in the event, posting State, and event classes. Let's look at this method.

postSingleEventForEventType

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        //Getting subscriptions for responses from subscriptions ByEventType
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            try {
                //Sending events
                postToSubscription(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } 
            //...
        }
        return true;
    }
    return false;
}

Next, we call EventBus#postToSubscription. We can see that the subscription list is passed in as a parameter. Obviously, the subscriber and the subscription method are stored in the subscription list. So we can guess that the subscription method should be invoked by reflection. Specifically, let's look at the source code.

postToSubscription

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 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);
    }
}

First, get threadMode, which is the thread that the subscription method runs. If it is POSTING, call invokeSubscriber() directly. If it is MAIN, determine whether the current thread is a MAIN thread or invokeSubscriber() method directly. Otherwise, it will be handled by the main ThreadPoster. Other situations are similar. Three Posters will be used here, because sticky events will also be used in these three Posters, so I will put it below to specifically describe. The implementation of EventBus invokeSubscriber is also very simple. It mainly implements the invocation of subscription method by reflection, which realizes the process of sending events to subscribers and calling subscription method by subscribers. As follows:

invokeSubscriber

void invokeSubscriber(Subscription subscription, Object event) {
    try {
        subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
    } 
    //...
}

Up to now, the sending process of events has been explained, and the flow chart of the whole sending process has been given to facilitate understanding.

Sending process. jpg

Sending and Receiving Analysis of Viscous Events

Sticky events are different from ordinary events. Sticky events are sent out first and then received by subsequently registered subscribers. The sticky event is sent through the EventBus postSticky () method. Let's look at its source code:

public void postSticky(Object event) {
    synchronized (stickyEvents) {
        stickyEvents.put(event.getClass(), event);
    }
    // Should be posted after it is putted, in case the subscriber wants to remove immediately
    post(event);
}

Put this event in stickyEvents map, and then call the post() method. The process is the same as the analysis above, but no subscriber can be found to handle the event. So why can you receive matching events right away when registering subscribers? Remember that the EventBus#subscribe method above contains a section of code that specifically handles sticky events? Namely:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    //Above is omitted.
    //Here's how to deal with sticky events
    if (subscriberMethod.sticky) {
        //As you can see from EventBusBuilder, EvetInheritance defaults to 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>).
            //Get all sticky events from the list. Because of the nature of sticky events, we don't know which subscribers it corresponds to.
            //Therefore, all sticky events should be taken out and traversed one by one.
            Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry<Class<?>, Object> entry : entries) {
                Class<?> candidateEventType = entry.getKey();
                //If the event type subscribed by the subscriber is the same as the current sticky event type, then the event is distributed to the subscriber
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            //According to EvetType, get specific events from the stickyEvents list
            Object stickyEvent = stickyEvents.get(eventType);
            //Distribution events
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

The logic above is clear. EventBus does not know which sticky event the current subscriber corresponds to, so it needs to traverse all the time. After finding a matching sticky event, it calls the EventBus#checkPostStickyEventToSubscription method:

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

Next, we go back to the postToSubscription method. For ordinary events or sticky events, threadMode will be used to select the corresponding thread to perform the subscription method. The key to switching threads is mainThreadPoster, backgroundPoster and asyncPoster.

HandlerPoster

Let's first look at mainThreadPoster, whose type is HandlerPoster inherited from Handler:

final class HandlerPoster extends Handler {

    //Pending PostQueue queue, post queue to be sent
    private final PendingPostQueue queue;
    //Maximum run time is specified because the main thread cannot be blocked because it runs on the main thread
    private final int maxMillisInsideHandleMessage;
    private final EventBus eventBus;
    private boolean handlerActive;
    //Omission...
}

As you can see, there is a Pending Post Queue inside the handler, which is a queue that holds Pending Post, i.e. the post to be sent. The Pending Post encapsulates event and subscription to facilitate the interaction of information in the thread. In the postToSubscription method, if the current thread is not the main thread, the HandlerPoster#enqueue method is called:

void enqueue(Subscription subscription, Object event) {
    //Packing subscription and event into a Pending Post
    PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
    synchronized (this) {
        //Queue entry
        queue.enqueue(pendingPost);
        if (!handlerActive) {
            handlerActive = true;
            //Send messages, run in the main thread
            if (!sendMessage(obtainMessage())) {
                throw new EventBusException("Could not send handler message");
            }
        }
    }
}

First, a available PendingPost is obtained from PendingPostPool, and then the PendingPost is put into PendingPostQueue to send a message. Then, since the HandlerPoster acquires the Loper of the UI thread at initialization time, its handleMessage() method runs on the UI thread:

@Override
public void handleMessage(Message msg) {
    boolean rescheduled = false;
    try {
        long started = SystemClock.uptimeMillis();
        //Continuously pull pendingPost from the queue
        while (true) {
            //Omission...
            eventBus.invokeSubscriber(pendingPost);        
            //..
        }
    } finally {
        handlerActive = rescheduled;
    }
}

It calls the EventBus#invokeSubscriber method, in which PendingPost is unpacked for normal event distribution, as mentioned above, but not expanded.

BackgroundPoster

BackgroundPoster inherits from Runnable and is similar to HandlerPoster. It has a PendingPostQueue queue inside. When called to its enqueue, subscription and event are packaged into PendingPost:

public void enqueue(Subscription subscription, Object event) {
    PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
    synchronized (this) {
        queue.enqueue(pendingPost);
        //If the background thread is not running, run first
        if (!executorRunning) {
            executorRunning = true;
            //The run() method is called
            eventBus.getExecutorService().execute(this);
        }
    }
}

This method runs the run() method through Executor, and the EventBus#invokeSubscriber method is also called inside the run() method.

AsyncPoster

Similar to BackgroundPoster, it is also a Runnable, and its implementation principle is roughly the same as BackgroundPoster, but there is a difference that it does not need to determine whether a thread is already running before it has internal, it will use a new thread every post event.

On the Observer Model

The whole EventBus is based on the observer pattern, and the method of invoking the observer is the core of the observer pattern.

Observer pattern: Defines one-to-many dependencies between objects. When an object changes state, all its dependents are notified and updated automatically.

From the whole EventBus, we can see that the event is the observer, and the subscriber class is the observer. When the event occurs or sends a change, the observer will be notified through EventBus, so that the subscription method of the observer can be automatically invoked. Of course, this is different from the general observer model. In retrospect of the observer pattern we used, we would let events implement an interface or directly inherit from Java's built-in Observerable class. We would also have a list inside the event to hold all registered observers, and the event class had a way to notify observers. So from the point of view of the single responsibility principle, this event class has done too much! It is necessary to remember which observers are there, to inform the observer when the time is ripe, or to have other own methods. In this way, one or two event classes are good, but it is very cumbersome and inefficient to implement the same operation for each event class and each new and different requirement. Therefore, EventBus acts as an intermediary, pulling out a lot of responsibility for the event, so that the event itself does not need to implement anything, everything else is left to EventBus to operate on.

Well, EventBus has been explained in detail so far in this article. Because this article is very long, it is suggested that readers can choose the hard part to think over the source code so that they can understand it deeply. Finally, in order to insist on reading this article, thank you for your reading! ___________.

Topics: Java github