EventBus is a widely used tripartite library for transferring data between different interfaces (activities/Fragments) that developers love for its simplicity.

EventBus is easier and faster to use than Bundle or interface callbacks, but it is important to note that you need to have a clear understanding of how your business distributes notifications. Otherwise, you can easily distribute too many useless notifications, resulting in a performance drain.

This article provides a brief introduction to EventBus

Simple use

1. The oncreate () in the registration

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

2. OnDestroy ()

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

Use 3.@SubscribeAnnotation method to receive notification of events

@Subscribe
public void onMainEvent(String str){
    System.out.println("event = "+str);
}
Copy the code

4. Send notifications

EventBus.getDefault().post("12456");
Copy the code

The four steps above can accomplish a simple event dispatch, which is part of the reason EventBus is so widely used.

The source code interpretation

Explain a few key elements before diving into the source code

Subscription. Class (Subscription information)

Final class Subscription {// Final Object subscriber (activity/fragment); // SubscriberMethod object, which mainly includes the subscribed final SubscriberMethod SubscriberMethod; }Copy the code

SubscriberMethod.class

Public class SubscriberMethod {// Method final Method; // Final ThreadMode ThreadMode; // Event type Final Class<? > eventType; // Final int priority; Final Boolean sticky; }Copy the code

Findstate.class (Search state, mainly used to store variables and results during the search)

static class FindState { final List<SubscriberMethod> subscriberMethods = new ArrayList<>(); Final Map<Class, Object> anyMethodByEventType = new HashMap<>(); // Event type is key,method is value final Map<Class, Object> anyMethodByEventType = new HashMap<>(); / / method for the key, the subscriber class object is the value of final Map < String, class > subscriberClassByMethodKey = new HashMap < > (); final StringBuilder methodKeyBuilder = new StringBuilder(128); }Copy the code

There are also several more important Map objects

  1. subscriptionsByEventTypeThe type as the key,CopyOnWriteArrayList<Subscription>In EventBus, a type of type can correspond to many subscribers.
  2. TypesBySubscriber is the opposite,Subscription is a key and all event types are values
  3. METHOD_CACHEThe class object is a key, the List is a value, and the Subscription is a SubscriptionMETHOD_CACHEUSES aConcurrentHashMap()This data model is optimized for multiple concurrency.

Let’s take a look at the internals of this powerful library.

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

The main job of the register process is 1. Get the subscriber’s class name (such as MainActivity) 2. Find a subscriber’s subscription method (annotated @subscribe and publicly decorated) using the findSubscriberMethods method 2.1 Find a subscription method

List<SubscriberMethod> findSubscriberMethods(Class<? > subscriberClass) { List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods ! = null) { return subscriberMethods; } // The ignoreGeneratedIndex attribute indicates whether to ignore the MyEventBusIndex generated by the annotator. The default is false if (ignoreGeneratedIndex) {subscriberMethods = findUsingReflection(subscriberClass); } else { subscriberMethods = findUsingInfo(subscriberClass); } / / if the Subscriber is not @ the Subscribe and public statement in the class of methods will be submitted to the exception if (subscriberMethods. IsEmpty ()) {throw new EventBusException (" Subscriber" + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation"); } else { METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; }}Copy the code

findUsingInfo()

List<SubscriberMethod> findUsingInfo(Class<? > subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz ! = 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 { findUsingReflectionInSingleClass(findState); } findState.moveToSuperclass(); } return getMethodsAndRelease(findState); }Copy the code

The FindState object is retrieved as a thread pool and the Subscriber Subscriber object is initialized

FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
Copy the code

Normally the first use of if (findState.subscriberInfo! = null) this judgment to false, then enter the findUsingReflectionInSingleClass (findState); process

In addition it is important to note each cycle will be called findState. MoveToSuperclass () to retrieve the parent class method, so for a subscriber to subclass and method in the superclass will be retrieved, the order is a subclass – > the superclass

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) { int modifiers = method.getModifiers(); if ((modifiers & Modifier.PUBLIC) ! = 0 && (modifiers & MODIFIERS_IGNORE) == 0) { Class<? >[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1) { Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); if (subscribeAnnotation ! = null) { Class<? > eventType = parameterTypes[0]; if (findState.checkAdd(method, eventType)) { ThreadMode threadMode = subscribeAnnotation.threadMode(); findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky())); } } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException("@Subscribe method " + methodName + "must have exactly 1 parameter but has " + parameterTypes.length); } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException(methodName + " is a illegal @Subscribe method: must be public, non-static, and non-abstract"); }}}Copy the code
  1. Get all methods in all classes by radiating
  2. Filter method 1. Public modifier 2. Non-static, non-abstract 3. The function has an annotation at Subscribe
  3. findState.checkAdd(method, eventType)Verify whether it can be added to the list

Findstate.checkadd (method, eventType

  1. anyMethodByEventType()Returns true if not found in
  2. anyMethodByEventType()Some of them, do two checks,

This time, the method name > parameter name is used to complete the verification, because there is no function with the same name and argument in the same class, and the function with the same name and different arguments was filtered in the previous step, so there is no double registration in the same class.

But if a subclass overrides a parent class’s method, the same methodKey appears. EventBus does a validation and keeps the subscription information for the subclass. Because the scan is sequential from subclass to parent, methodClassOld should be retained and methodClass ignored. If a comment on your code fails to Revert the PUT

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

After the checkAdd ()

ThreadMode threadMode = subscribeAnnotation.threadMode();
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
Copy the code

Add to findState and save it as an arrayList

Next, back in the getMethodsAndRelease() method,return a List of methods

private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
    List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
    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;
}
Copy the code

3. Return to the register() method at the beginning

for (SubscriberMethod subscriberMethod : subscriberMethods) {
    subscribe(subscriber, subscriberMethod);
}
Copy the code

Relatively speaking, the registration process is relatively simple. It is mainly to fill data into the two maps (subscriptionsByEventType and typesBySubscriber) mentioned at the beginning, which can be used for event distribution. Add at most some priority/stickiness judgment.

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<? > eventType = subscriberMethod.eventType; Subscription newSubscription = new Subscription(subscriber, subscriberMethod); CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); } else { if (subscriptions.contains(newSubscription)) { throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } 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; } } List<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); if (subscriberMethod.sticky) { if (eventInheritance) { // Existing sticky events of all subclasses of eventType have to be considered. // Note: Iterating over all events may be inefficient with lots of sticky events, // thus data structure should be changed to allow a more efficient lookup // (e.g. an additional map storing sub classes  of super classes: Class -> List<Class>). Set<Map.Entry<Class<? >, Object>> entries = stickyEvents.entrySet(); for (Map.Entry<Class<? >, Object> entry : entries) { Class<? > candidateEventType = entry.getKey(); if (eventType.isAssignableFrom(candidateEventType)) { Object stickyEvent = entry.getValue(); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); }}}Copy the code

The post process

Again, a few important classes are introduced

Postingthreadstate.class (Event dispatch state)

Final static class PostingThreadState {// eventQueue final List<Object> eventQueue = new ArrayList<>(); // Whether it is being distributed to prevent multiple Posting of the same event in concurrent state; Boolean isMainThread; Subscription subscription; Object event; boolean canceled; }Copy the code

Threadlocal. class (threadLocal. class) stores data in a specified thread. Once the data is stored, only the specified thread can retrieve the previously stored data.

The post process

public void post(Object event) {
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    eventQueue.add(event);

    if (!postingState.isPosting) {
        postingState.isMainThread = isMainThread();
        postingState.isPosting = true;
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            while (!eventQueue.isEmpty()) {
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}
Copy the code

The core part is just the following sentence, which iterates through the events in the send eventQueue and removes those that have already been sent

postSingleEvent(eventQueue.remove(0), postingState);
Copy the code

postSingleEvent.class

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<? > eventClass = event.getClass(); boolean subscriptionFound = false; if (eventInheritance) { List<Class<? >> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); for (int h = 0; h < countTypes; h++) { Class<? > clazz = eventTypes.get(h); subscriptionFound |= 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)); }}}Copy the code

EventInheritance variables by default the assigned value is true, will traverse eventclass lookupAllEventTypes function, its parent class and interface class class, the core part in postSingleEventForEventTy provided by the event pe()

postSingleEventForEventType.class

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<? > eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; synchronized (this) { subscriptions = subscriptionsByEventType.get(eventClass); } if (subscriptions ! = null && ! subscriptions.isEmpty()) { for (Subscription subscription : subscriptions) { postingState.event = event; postingState.subscription = subscription; boolean aborted = false; try { postToSubscription(subscription, event, postingState.isMainThread); aborted = postingState.canceled; } finally { postingState.event = null; postingState.subscription = null; postingState.canceled = false; } if (aborted) { break; } } return true; } return false; }Copy the code

The logic of this function is also very simple, retrieving the list of subscribers from the subscriptionsByEventType list based on The eventClass and then iterating through the list of subscribers to call back the event. When you read this, you see eventClass as the key instead of event as the key, presumably because the class object can be traced back to its parent class and interface implementation.

At this point, the POST process is finished, and it’s much simpler than the registration process.

Thread scheduling postToSubscription()

Several types of Poster

1. MainThreadPoster is created from HandlerPoster

HandlerPoster.class

Private final PendingPostQueue queue; / / maximum number of seconds Normally for 10 s, more than may be an error, it's like radio onReciver callback 10 s didn't finish will quote ANR error handling some similar private final int maxMillisInsideHandleMessage; private final EventBus eventBus; // Flag whether active is available private Boolean handlerActive;Copy the code

The core logic handleMessage

@Override
public void handleMessage(Message msg) {
    boolean rescheduled = false;
    try {
        long started = SystemClock.uptimeMillis();
        while (true) {
            PendingPost pendingPost = queue.poll();
            if (pendingPost == null) {
                synchronized (this) {
                    // Check again, this time in synchronized
                    pendingPost = queue.poll();
                    if (pendingPost == null) {
                        handlerActive = false;
                        return;
                    }
                }
            }
            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

Traverse the queue, perform eventBus. InvokeSubscriber (pendingPost) method

Void invokeSubscriber(Subscription, Object event) {// Call the Subscription method directly by reflection. subscription.subscriberMethod.method.invoke(subscription.subscriber, event); } catch (InvocationTargetException e) { handleSubscriberException(subscription, event, e.getCause()); } catch (IllegalAccessException e) { throw new IllegalStateException("Unexpected exception", e); }}Copy the code

**2.BackgroundPoster:** Implement Runnable, which will distribute all events in the PendingPostQueue

**3.AsyncPoster:** Also implements Runnable and will only distribute events from a PendingPostQueue

postToSubscription.class

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 {
                // temporary: technically not correct as poster not decoupled from subscriber
                invokeSubscriber(subscription, event);
            }
            break;
        case BACKGROUND:
            if (isMainThread) {
                backgroundPoster.enqueue(subscription, event);
            } else {
                invokeSubscriber(subscription, event);
            }
            break;
        case ASYNC:
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
    }
}
Copy the code

Logic is also well understood when the same thread is directinvokeSubscriberThe reflection callback is sent by different threads to the same thread.

Unregister process

public synchronized void unregister(Object subscriber) { List<Class<? >> subscribedTypes = typesBySubscriber.get(subscriber); if (subscribedTypes ! = null) { for (Class<? > eventType : subscribedTypes) { unsubscribeByEventType(subscriber, eventType); } typesBySubscriber.remove(subscriber); } else { logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass()); }}Copy the code

The main work is divided into two steps

  1. Removes all subscription information for a subscriber
  2. Removes the association between the subscriber and all of its events

I also read a blog post about the optimization of EventBus. Note it down

  1. EventInheritance defaults to true and iterates through the class’s parent and interface objects. You can set this value to false if the program does not use eventInheritance
  2. IgnoreGeneratedIndex indicates whether the generated APT code is used to optimize the process of finding events, or to false if EventBus’s APT is not plugged into the project

supplement

hashMap.put()

Hashmap.put () returns a value based on the key. If there is no key, it is added to the map

Such as

map.put("222", "222");
String value2=map.put("222","333");
Copy the code

Value2 =”222″, map key=”222″ value =” 333″

The difference between instanceof isinstance, isAssignableFrom

The instanceof operator is used only for object reference variables to check that the object being tested on the left is not an instantiation of the class or interface on the right. If the object under test is null, the test result is always false. Figuratively: Instanceof own instance or subclass instanceof own class returns true

  String s=new String("javaisland"); 
  System.out.println(s instanceof String); //true 
Copy the code

The isInstance(Object obj) method of the Class. Obj is the Object being tested, and returns true if obj is an instance of the Class or interface that called the method. This method is the dynamic equivalent of the instanceof operator. Class. IsInstance (instance of itself or a subclass) returns true

  String s=new String("javaisland"); 
  System.out.println(String.class.isInstance(s)); //true 
Copy the code

The isAssignableFrom(Class CLS) method of the Class Class, which returns true if the Class or interface is the same as the Class or interface represented by the CLS argument, or if it is a parent of the Class or interface represented by the CLS argument. Class. IsAssignableFrom (own class or subclass. Class) returns true.

System.out.println(ArrayList.class.isAssignableFrom(Object.class));  //false 
System.out.println(Object.class.isAssignableFrom(ArrayList.class));  //true
Copy the code

Refer to the article

EventBus source analysis EventBus

In addition

Personal github spare time to write the story