A brief introduction to EventBus

EventBus is pretty familiar, but I can’t say more about it. Usually we call it an event bus, but it’s more like a broadcast, observer mode, where one side sends a message and two sides receive it. In creating a subscription for EventBus, the most important thing is that there are two key maps that store the subscription methods and classes that we define.

The following code is based on EventBus3.1.1

Create and subscribe messages

registered

Signing up for EventBus is simple,

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

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

It is important to remember to log out in onStop to avoid being associated with EventBus after the Activity is closed and causing a memory leak.

Let’s go to the method and have a look:

The singleton pattern

EventBus.getDefault

    public static EventBus getDefault(a) {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = newEventBus(); }}}return defaultInstance;
    }
Copy the code

This method is very simple, just double check singleton, continue to look at register method:

The register method

EventBus.register

    public void register(Object subscriber) { Class<? > subscriberClass = subscriber.getClass();// The registered class
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);// Find the subscription method
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {// Loop through all the methods foundsubscribe(subscriber, subscriberMethod); }}}Copy the code

In register, the parameter passed is called subscriber, which means subscriber, and of type Object. So inside this method there’s a List of lists, so what’s the SubscriberMethod?

public class SubscriberMethod {
    final Method method;// The corresponding method
    final ThreadMode threadMode;// Thread mode
    finalClass<? > eventType;// Message type
    final int priority;/ / priority
    final boolean sticky;// Whether stickiness is supported
    /** Used for efficient comparison */
    String methodString;// For equal comparison
}
Copy the code

In fact, this class holds all the information in the methods defined in our subscriber.

The threaded

ThreadMode is defined as follows:

  • Threadmode. POSTING: The default thread schema, where events are sent by one thread and events are processed by the corresponding thread, prevents thread switching.

  • Threadmode. MAIN: If an event is sent on the MAIN thread (UI thread), the event is handled directly on the MAIN thread; If an event is sent in a child thread, it is first queued and then switched to the main thread by Handler, which processes the event in turn.

  • Threadmode. MAIN_ORDERED: Regardless of which thread sends the event, the event is first queued and then switched by Handler to the main thread, which processes the event in turn.

  • Threadmode. BACKGROUND: If an event is sent on the main thread, the event is queued first and then processed through the thread pool. If an event is sent in a child thread, the event is processed directly in the thread that sent the event.

  • Threadmode. ASYNC: Regardless of which thread sends the event, the event is queued and processed through the thread pool.

So this list in the Register method is basically our current subscriber class that contains all the receiving methods. Here by subscriberMethodFinder. FindSubscriberMethods (subscriberClass) method to obtain:

Find a way to subscribe to events (a way to receive messages)

SubscriberMethodFinder findSubscriberMethods


    private static finalMap<Class<? >, List<SubscriberMethod>> METHOD_CACHE =new ConcurrentHashMap<>();

    List<SubscriberMethod> findSubscriberMethods(Class
        subscriberClass) {
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);// Get it from the cache first
        if(subscriberMethods ! =null) {
            return subscriberMethods;// If there is a cache, return it directly
        }

        if (ignoreGeneratedIndex) {// Whether to ignore annotations
            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 {
            METHOD_CACHE.put(subscriberClass, subscriberMethods);// Add to the cache
            returnsubscriberMethods; }}Copy the code

Here we notice that there is an ignoreGeneratedIndex parameter, the value of which is obtained in the constructor

subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
                builder.strictMethodVerification, builder.ignoreGeneratedIndex);
Copy the code
    /** Forces the use of reflection even if there's a generated index (default: false). */
    public EventBusBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex) {
        this.ignoreGeneratedIndex = ignoreGeneratedIndex;
        return this;
    }
Copy the code

As you can see from the official comment, the default value is false, so the findUsingInfo method is called:


    private List<SubscriberMethod> findUsingInfo(Class
        subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);// Initialize the lookup helper class
        while(findState.clazz ! =null) {// Here is our register class
            findState.subscriberInfo = getSubscriberInfo(findState);
            if(findState.subscriberInfo ! =null) {// The initial state is empty, so else is executed
                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();// Find the parent class
        }
        return getMethodsAndRelease(findState);// Release resources
    }

Copy the code

FindState is a lookup helper class that stores information about found subscription methods and related classes:

static class FindState {
        final List<SubscriberMethod> subscriberMethods = new ArrayList<>();// The subscription method
        final Map<Class, Object> anyMethodByEventType = new HashMap<>();// Get methods by message type
        final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();// Get the subscribed class through the method
        final StringBuilder methodKeyBuilder = new StringBuilder(128); Class<? > subscriberClass; Class<? > clazz;boolean skipSuperClasses;
        SubscriberInfo subscriberInfo;
}
Copy the code

Here will perform the else inside findUsingReflectionInSingleClass method, through the reflection to find subscription method

SubscriberMethodFinder 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;
        }
        // Loop through all methods
        for (Method method : methods) {
            int modifiers = method.getModifiers();// Get the modifier
            if((modifiers & Modifier.PUBLIC) ! =0 && (modifiers & MODIFIERS_IGNORE) == 0) {//public staticClass<? >[] parameterTypes = method.getParameterTypes();// Get the parameter type
                if (parameterTypes.length == 1) {// There can be only one argument
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);// Get the Subscribe comment
                    if(subscribeAnnotation ! =null) { Class<? > eventType = parameterTypes[0];// Get the parameter type, that is, the type of our custom message
                        if (findState.checkAdd(method, eventType)) {// Whether this method has been added
                            ThreadMode threadMode = subscribeAnnotation.threadMode();// Get the thread mode
                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky()));// Add to array}}}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

The whole process is to go through all the methods, judge the method with a Subscribe annotation, and then add the method information to the array. Once you’ve done the lookup, go back to the Register method

    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

Circulation subscription

Get the array of all the subscription methods we just had, and then execute the subscribe method in the loop, which performs the subscribe operation:

    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<? > eventType = subscriberMethod.eventType;// Get our custom message type
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);// Create a new subscription class that contains the subscription class and method properties
        / / subscriptionsByEventType is key, eventType Subscription array for the value of the map
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {// Create an array if it is empty and add it to the map
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            if (subscriptions.contains(newSubscription)) {// Subscribe already, throw exception
                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) {// Add the subscription method with higher priority to the array
                subscriptions.add(i, newSubscription);
                break; }}//typesBySubscriber is a map whose object is the key and the array of subscribed methods is the valueList<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);
        // Sticky event correlation
        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

After the

EventBus’s register method is a simple, two-step process:

  1. We use a Subscribe method with the Subscribe annotation by reflection lookup
  2. Loop through the found methods and add them to the two key maps (subscriptionsByEventType and typesBySubscriber)

SubscriptionsByEventType: use eventType as key and Subscription as value

TypesBySubscriber: uses the object as the key and the array value of the subscribed methods