Welcome to follow my official account, scan the qr code below or search the official id: MXSZGG

  • preface
  • register()
    • Gets information about all subscription methods for the current registered object
    • subscribe()
  • post()
    • postSingleEvent()
  • unregister()
  • threadMode
    • invokeSubscriber()
    • Poster#enqueue()
  • sticky
  • To optimize the operation
    • eventInheritance
    • APT
  • Design skills
    • The reflection method
    • FindState
    • AsyncPoster, BackgroundPoster
  • Afterword.

preface

I’ve looked at the source code of some well-known open source projects and found EventBus to be one of the simplest, if not the most complex. There are a few variables and classes that EventBus already knows about.

  • METHOD_CACHE:Map<Class<? >, List<SubscriberMethod>>Type. The key is the Class of the registered Class, and the value is the list of methods for all EventBus callbacks in that Class@SubscribeMethods of marking).
  • typesBySubscriber:Map<Object, List<Class<? >>>Type. The key is the object itself (such as the Activity object), and the value is the class type of all the Events in the object. This field is only used to determine if an object is registered,Almost nothing in everyday use(Thanks for pointing it out in the comment section).
  • SubscriptionClass (subscription information in this article) : Focus on two fields in the class, one isObjectThe type ofsubscriber, which is the registered object (often an Activity in Android); The other one isSubscriberMethodThe type ofsubscriberMethod, details are as follows:
    • subscriberMethod:SubscriberMethodType (called subscription method in this article). There is a field in the concern classeventTypeClass<? >Type, representing the class type of the Event.
  • subscribtionsByEventType:Map<Class<? >, CopyonWriteArrayList<Subscribtion>>Type. The key is the class type of Event and the value is the elementSubscription(Subscription information) linked list. Core fields.

register()

EventBus#register()

public void register(Object subscriber) { Class<? > subscriberClass = subscriber.getClass(); // Get List<SubscriberMethod> List<SubscriberMethod> subscriberMethods = according to the current registered class subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : Subscribe (subscriber, subscribe) {// subscribe to each SubscriberMethod in List<SubscriberMethod> subscriberMethod); }}}Copy the code

Gets information about all subscription methods for the current registered object

Start by looking at how to get the List’s SubscriberMethodFinder#findSubscriberMethods(Class
subscriberClass

List<SubscriberMethod> findSubscriberMethods(Class<? > subscriberClass) { List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); // Return if (subscriberMethods! = null) { return subscriberMethods; } subscriberMethods = findUsingReflection(subscriberClass); METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; }Copy the code

METHOD_CACHE, as mentioned earlier, is a key-value pair that holds the registered class with a list of all Event methods that need to be called back. Return METHOD_CACHE if it already exists, but otherwise you need to find the findUsingReflection(subscriberClass) method and return it. Otherwise, METHOD_CACHE has no purpose.

SubscriberMethodFinder#findUsingReflection()

private List<SubscriberMethod> findUsingReflection(Class<? > subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz ! = null) {/ / @ the Subscribe by pure reflection to obtain the modified method findUsingReflectionInSingleClass (findState); / / the current class parent class assigned to findState. Clazz findState. MoveToSuperclass (); } // reset FindState to facilitate the next recycle of return getMethodsAndRelease(FindState); }Copy the code

After initializing the FindState object, it enters a while loop that continuously reflects the current class and its parent. (Note that in Java, if the current class implements an interface, even if the method of that interface is modified by @SUBSCRIBE, the method in the current class does not contain the annotation attribute.) So it doesn’t do any good to Subscribe to a method in an interface and then have the class implement that interface), add it to the list, and finally return the list and reset the FindState object for the next reuse. Reflection gets the current class and its parent class subscription method source simplified as follows:

private void findUsingReflectionInSingleClass(FindState findState) { Method[] methods; Try {/ / returns the current class itself method and explicit overloaded the superclass method. The methods = findState clazz. GetDeclaredMethods (); } catch (Throwable th) { 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]; // needCheck if (findState.checkAdd(method, eventType)) { ThreadMode threadMode = subscribeAnnotation.threadMode(); findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky())); } } } } } }Copy the code

The point here is that once you get the @subscribe target method, it is not mindlessly added to the subscriberMethods, but actually needs to be filtered. Before you explain the checkAdd() source code, you should think about the following questions:

  • If the current class subscribes to the same Event multiple times using multiple methods on the object, how will the current class call those methods when the Event is emitted?
  • For the same Event, the parent class makes a subscription to the object, and the subclass overrides the subscription method. Then, when the Event is emitted, how will the parent subclass handle these methods?

To resolve these methods, look at the underlying implementation of checkAdd()

boolean checkAdd(Method method, Class<?> eventType) {
    Object existing = anyMethodByEventType.put(eventType, method);
    if (existing == null) {
        return true;
    } else {
        return checkAddWithMethodSignature(method, eventType);
    }
}
Copy the code

AnyMethodByEventType uses the Event Class as the key, which seems to mean that a Class can only subscribe to the same Event once. Still have to continue to see checkAddWithMethodSignature (), and its source simplified as follows:

private boolean checkAddWithMethodSignature(Method method, Class<? > eventType) { methodKeyBuilder.setLength(0); methodKeyBuilder.append(method.getName()); methodKeyBuilder.append('>').append(eventType.getName()); String methodKey = methodKeyBuilder.toString(); Class<? > methodClass = method.getDeclaringClass(); Class<? > methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass); if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) { return true; } else { subscriberClassByMethodKey.put(methodKey, methodClassOld); return false; }}Copy the code

Can see subscriberClassByMethodKey use method name + ‘>’ + event type as the key, this means that for the same class, SubscriberClassByMethodKey won’t duplicates (after all, in a class can’t the method name and the same method parameter, number are the same), so it will return true. This means that if a class registers multiple methods on the same Event object, all methods will be called back when the Event object is emitted.

But when perform the operations of the parent Class, if there is a “show” subclass implementation of the parent Class subscription method, then subscriberClassByMethodKey. At this time the put (methodKey methodClass) return value will not empty, and as a subclass of the Class, The if upper branch will determine whether the subclass isAssignableFrom the parent Class, which is bound to be false, and that will walk into the if lower branch and return false. This means that when a subclass “displays” the subclass’s subscription method, if the Event is emitted, the subclass’s subscription method will not be executed, but only the subclass’s subscription method will be executed.

subscribe()

After obtaining the corresponding SubscriberMethod linked list, you will need to subscribe to the SubscriberMethod object in the linked list.

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); } int size = subscriptions.size(); for (int i = 0; i <= size; I++) {if (I = = size | | subscriberMethod. Priority > subscriptions. Get (I). SubscriberMethod. Priority) {/ / according to priority Add (I, newSubscription); break; } } List<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); // omit sticky event}Copy the code

SubscriptionsByEventType gets the list of subscription information based on the Event class type, of course, new one if it doesn’t exist and put it in it. It is then stuffed into the linked list according to the priority of the subscription method. Finally, typesBySubscriber retrieves the list of all Event types for the subsciber and adds the current Event type. The specific content of the sticky event will be explained in sticky.

This is the end of the EventBus#register(Object) method.

post()

EventBus#post(Object)

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;
        try {
            while (!eventQueue.isEmpty()) {
            	// 分发 Event 事件
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
        	// 最后要 reset flag
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}
Copy the code

CurrentPostingThreadState is a ref; through which access to PostingThreadState object, according to the object again to get the event list (associated with the message mechanism in the Android?) And stuffs the event passed into the list. To prevent events from being called more than once, the PostingThreadState object has an isPosting to indicate whether the list is already being called back. The event is also removed from the list.

postSingleEvent()

PostSingleEvent () postSingleEvent()

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<? > eventClass = event.getClass(); postSingleEventForEventType(event, postingState, eventClass); }Copy the code

Trace EventBus# postSingleEventForEventType () source streamline is as follows:

private void 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;
            try {
                postToSubscription(subscription, event, postingState.isMainThread);
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
        }
    }
}
Copy the code

SubscriptionsByEventType gets the subscription list for the Event, and passes the subscription, Event, and current thread information to the postToSubscription() method, This method is used to call back all subscription methods, and is analyzed in threadMode. This is actually the end of the post() process. So actually the source code of the core method post() is very simple and can be seen, and the core field is only subscriptionsByEventType.

unregister()

EventBus#unregister(Object)

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

As a whole, there are two steps. One is to remove the registered object and its linked list of all events, that is, typesBySubscriber removes the key value pairs. Then remove all subscriptions for subscriptionsByEventType from the unsubscribeByEventType() method (you can see that METHOD_CACHE is not actually removed). The next time you register, you can easily retrieve the previous information, which is where caching comes in).

threadMode

In EventBus, there are four threadModes, as follows:

public enum ThreadMode {
    POSTING,

    MAIN,

    MAIN_ORDERED,

    BACKGROUND,

    ASYNC
}
Copy the code
  • POSTINGThe receiving event method should execute on the thread that emits the event method. (Since the emitting event method thread may be the main thread, this means that the receiving method cannot perform time-consuming operations that would otherwise block the main thread.)
  • MAINIn Android, the receive event method should be executed on the main thread, otherwise (in Java projects) equivalentPOSTING. If the emitting event method is already on the main thread, the receiving event method is called “immediately” (this means that the receiving event method cannot perform time-consuming operations that would otherwise block the main thread; The emitted event method is blocked by the received event method because it is called “immediately”MAIN_ORDERED
  • MAIN_ORDEREDIn Android, the receiving event method is thrown into MessageQueue to wait for execution (meaning that the transmitting event method is not blocked), otherwise (in Java projects) equivalent toPOSTING.
  • BACKGROUND:
    • In the Android
      • If the emitted event method is executed on the main thread, the received event method should be executed on a child thread. However, the child thread is a single child thread maintained by EventBus, so the method should not be too time-consuming to prevent the child thread from blocking in order to avoid affecting the execution of other receive event methods.
      • If the emitting event method is executed in a child thread, the receiving event method should execute in the same thread as the emitting event method.
    • In a Java project, the receive event method is always executed in a single child thread maintained by EventBus.
  • ASYNCThe receiving method should execute on a different thread from the emitting event method. Often used for time-consuming operations, such as network access. Of course, try to avoid firing a large number of methods of this type at the same time, although EventBus specifically creates a thread pool to manage the recycling of these threads.

I hope you can read carefully about which of the above threadModes should avoid time-consuming operations and which thread blocks when time-consuming.

PostToSubscription () now that we have several threadmodes to look at, postToSubscription() is available as follows:

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

A close look at the source code shows that only two methods are used, invokeSubscriber meaning the method is called immediately, and xxxposter.enqueue () meaning another thread is needed to execute the method.

invokeSubscriber()

The source code is as follows:

void invokeSubscriber(Subscription subscription, Object event) {try {/ / pure reflex subscription. SubscriberMethod. Method. Invoke (subscription. The subscriber, event); } catch (InvocationTargetException e) { handleSubscriberException(subscription, event, e.getCause()); } catch (IllegalAccessException e) { throw new IllegalStateException("Unexpected exception", e); }}Copy the code

It is simple and crude direct and easy to understand, I admire.

So which situations would use the invokeSubscriber() method?

  • POSTING: Needless to say, since it is executed in the same thread as the firing event thread, then of course it is called directlyinvokeSubscriber()Can.
  • MAIN: called directly, ensuring that the emitting event thread is the main threadinvokeSubscriber().
  • MAIN_ORDERED: if the current project is not an Android project (pure Java project), it will be called directlyinvokeSubscriber().
  • BACKGROUNDIf the thread that emitted the event is not the main thread, the received event will be executed on the thread that emitted the event, so it will be called directlyinvokeSubscriber().

Android projects and pure Java projects have been mentioned several times in this article, because in most Cases Java projects do not need to distinguish between the main thread and child thread (this is also confirmed by Female piaoqiao). In RxJava, there is no schedulers.mainThread (), only the current thread of the schedulers.trampoline () table.

Poster#enqueue()

According to the source can be seen into the following three kinds:

The HandlerPoster source code is no longer in this extension, and readers familiar with Android should be able to guess that the underlying implementation of HandlerPoster is implemented through the Handler mechanism. The HandlerPoster#enqueue() method cannot be implemented without Hanlder#sendMessage(), and must be handled by calling invokeSubscriber() in Hanlder#handleMessage().

The BackgroundPoster source code is also not extended here. As mentioned earlier, EventBus maintains a single thread to execute the receive event method, so invokeSubscriber() must be called in Runnable#run().

The underlying implementation of AsyncPoster is essentially the same as BackgroundPoster, but the underlying implementation of BackgroundPoster maintains a “single” thread, which AsyncPoster certainly does not. I’ll leave the details here for the design techniques section.

sticky

I won’t expand on what a sticky event is here. EventBus#postSticky() = EventBus#postSticky()

public void postSticky(Object event) {
    synchronized (stickyEvents) {
        stickyEvents.put(event.getClass(), event);
    }
    post(event);
}
Copy the code

You can see that the first step is to put the event into stickyEvents and the second step is normal post(). To avoid multi-threading operations postSticky(Object) and removeStickyEvent(Class

if (subscriberMethod.sticky) {
    Object stickyEvent = stickyEvents.get(eventType);
    if (stickyEvent != null) {
        postToSubscription(newSubscription, stickyEvent, isMainThread());
    }
}
Copy the code

As you can see, there is nothing in particular to determine whether the current event is sticky, and if it is sticky, take the event from stickyEvents and execute the postToSubscription() method.

To optimize the operation

eventInheritance

I don’t know if there is an inheritance relationship between events in your daily use of EventBus, but I haven’t used it before. It is the conflict between the author who does not use events this way and the developer who does use Event inheritance that leads to this field. Global search this field is only used to determine whether the parent Event needs to be emitted when the Event is emitted. Since this field is true by default, you can set this field to false to improve performance if the Event does not have an inheritance relationship in the project development like the author.

APT

EventBus uses a lot of reflection internally to find ways to receive events. In fact, experienced people know that you can use APT to optimize. IgnoreGeneratedIndex is used to determine whether to use the generated APT code to optimize the search for received events. If this is enabled, Information about the receiving event method is quickly obtained by subscriberInfoIndexes. So if you don’t have APT for EventBus in your project, set ignoreGeneratedIndex to false to improve performance.

Design skills

The reflection method

EventBus uses getDeclaredMethods() instead of getMethods() to retrieve all methods in a class, which is more efficient than getMethods() because it only reflects the methods of the current class (not including implicitly inherited parent methods).

FindState

The following code gets FindState:

private FindState prepareFindState() { 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; return state; } } } return new FindState(); }Copy the code

The following code is recycling FindState:

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

As you can see, EventBus’s use of FindState is not a simple new. Because FindState is frequently used in the registration process and is expensive to create, it is necessary to create a FindState pool to reuse FindState objects, as well as PendingPost. It is used for reflection calls to receive event methods that are not specified in this extension.

AsyncPoster, BackgroundPoster

As mentioned above, the underlying implementation of AsyncPoster and BackgroundPoster is the same, but some readers will be confused. The underlying implementation of BackgroundPoster is a “single” thread. While AsyncPoster must not ah, I only discovered by EventBus after reading the source the author posed a – by default, in fact, both the underlying maintenance are Executors. NewCachedThreadPool (), This is a thread pool that can be used or created without an upper limit. How does BackgroundPoster control the “single”? Synchronized was added to Executor#execute() and flag was set to ensure that only one task could be executed by the thread pool at any one time. AsyncPoster, on the other hand, simply stuffs incoming tasks mindlessly into the thread pool.

Afterword.

The EventBus source code is simple, but many of the design techniques are well worth learning, such as the reuse pool mentioned earlier and the synchronized keyword that permeates the EventBus source code. I hope you can also go deep into it to explore, find the author has not found the treasure.

In addition, I created a group, if you have any questions about this article, or want to discuss Android technology with me, feel free to join.