What is EventBus?

Introduction to the

EventBus is an open source library for Android and Java that is loosely coupled using the publisher/subscriber pattern. EventBus uses bus control to decouple classes and simplify code in a few lines of code, eliminating dependencies and speeding up application development.

Below is the official sample image:

Official website: EventBus website

Making address: dead simple

The characteristics of

  • Simplify communication between components
  • The sender and receiver are highly decoupled
  • High performance and active community
  • Library files are small (<50KB)
  • Easy to configure threads, priority and other advanced features

How do I use EventBus?

Gradle is introduced

implementation 'org. Greenrobot: eventbus: 3.1.1'
Copy the code

2. Registration and cancellation

/ / registered EventBus. GetDefault (). The register (this); Eventbus.getdefault ().unregister(this);Copy the code

3. Event definition and sending

Public class MessageEvent {} // send eventbus.getDefault ().post(new MessageEvent());Copy the code

4. Event reception

 @Subscribe(threadMode = ThreadMode.POSTING)
    public void onEventMainThread(MessageEvent event) {
        // doSomeThing (); };Copy the code

What is the EventBus core execution process?

The use of EventBus includes registering, unregistering, sending event definitions, and receiving events. When the user registers, the set of event methods defined in the instance will be acquired through reflection, and then the set of event methods and subscribers will be added to the Map. When post is executed, the corresponding subscription set will be obtained from the set according to the event type, and the event of the subscriber will be called with the corresponding Poster through the configured threadMode. Finally, the event is executed by reflecting Method’s invoke.

Description of key class functions

class instructions
EventBus Bus scheduling class, getDefault() is singleton mode. Internal holds the subscriber and event collection. And the sender of each event. EventTypesCache (a cache of all sticky events), subscriptionsByEventType (key is an event, value is a Map of the subscriber collection), typesBySubscriber (key is a subscriber, The value for the event set Map), currentPostingThreadState (ThreadLocal, Collection of events for the current thread), mainThreadPoster (post for the main thread), backgroundPoster (Post for the background thread), asyncPoster (POST for the asynchronous thread), subscriberMethodFinder (get things in the subscriber A)
SubscriberMethod Class that subscribes to methods, including configuration properties such as Method, ThreadMode, priority, etc
Subscription Subscriber class, containing the Object instance of subscriber and subscriberMethod
PostingThreadState A collection of events that stores the current thread
SubscriberMethodFinder The event-receiving method used to get the definition in the subscription
PendingPost Subscription wrapper class that maintains a pendingPostPool, which is reused when PendingPost instances are in the pool
PendingPostQueue PendingPost internally maintains a PendingPost instance of head and tail, which provides enqueue and poll operations
HandlerPoster Used to handle event execution for the main thread
BackgroundPoster Used to handle event execution of background threads
AsyncPoster Event execution for asynchronous threads

Code execution flow

Register to register

Registration mainly obtains the set of event methods defined in the subscribers through reflection, adds the subscribers and the set of events to the corresponding Map, and then determines whether sticky events are supported, and sends the cache of previously sent sticky events to the subscribers.

1, source code implementation

Public void register(Object subscriber) {// Obtain the Class Class of the registered Object <? > subscriberClass = subscriber.getClass(); // Get all subscriberMethods in this Object through the subscriberMethodFinder List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); Synchronized (this) {// traverse the event collectionfor(SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } } private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<? > eventType = subscriberMethod.eventType; // Generate Subscription object (wrapper class for SubscriberMethod) Subscription newSubscription = newSubscription (subscriber, SubscriberMethod); // Get subscriptionsByEventType collection (key is the thing, The value set for a Subscription) 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; }} // Get the typesBySubscriber set (key for subscriber, value for event set) List<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber);if(subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } // Add (eventType) all events to the event collection according to the subscriber key. // Whether the event method supports (sticky things)if(subscriberMethod.sticky) {// Whether event methods support (inherit events)if(eventInheritance) {// Go through the stickyEvents to find all the parent events inherited from the event 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(); / / the cache before sending the subscriber viscous checkPostStickyEventToSubscription events (newSubscription stickyEvent); }}}else{ Object stickyEvent = stickyEvents.get(eventType); / / the cache before sending the subscriber viscous checkPostStickyEventToSubscription events (newSubscription stickyEvent); }}}Copy the code

2. Flow chart

Send a post –

1, source code implementation

Public void POST (Object event) {// Obtain the PostingThreadState of the current thread PostingThreadState postingState = currentPostingThreadState.get(); Postingstate. eventQueue List<Object> eventQueue = postingstate. eventQueue; // Add eventQueue.add(event); // Whether it is being sentif(! postingState.isPosting) { postingState.isMainThread = isMainThread(); postingState.isPosting =true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset"); } // loop to get events in eventQueue try {while(! PostSingleEvent (eventQueue.remove(0), postingState); eventQueue.isEmpty(); eventQueue.isEmpty(); } } finally { postingState.isPosting =false;
                postingState.isMainThread = false; } } } private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<? > eventClass = event.getClass(); boolean subscriptionFound =false; // Whether the event supports inheritanceif(eventInheritance) {// List<Class<? >> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); // iterate over the collectionfor(int h = 0; h < countTypes; h++) { Class<? > clazz = eventTypes.get(h); / / send subscriptionFound | = postSingleEventForEventType (event, postingState clazz); }}else{/ / send subscriptionFound = postSingleEventForEventType (event, postingState, eventClass); } // A NoSubscriberEvent event is sent if there are no subscribersif(! subscriptionFound) {if (logNoSubscriberMessages) {
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            if(sendNoSubscriberEvent && eventClass ! = NoSubscriberEvent.class && eventClass ! = SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event)); } } } private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<? > eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; Synchronized (this) {/ / according to the event for all subscribers subscriptions subscriptions = subscriptionsByEventType. Get (eventClass); }if(subscriptions ! = null && ! Subscription.isempty ()) {// Traverse all subscribersfor (Subscription subscription : subscriptions) {
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false; Try {/ / send events 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; } private void postToSubscription(Object event, Boolean isMainThread) { Using the corresponding post event processing 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); } } void invokeSubscriber(Subscription subscription, Object event) {try {/ / finally through reflection method perform event subscription. SubscriberMethod. Method. The 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

2. Flow chart

Unregister -unregister

1, source code implementation

Public synchronized void unregister(Object subscriber) {// Obtain the event set from the typesBySubscriber List<Class<? >> subscribedTypes = typesBySubscriber.get(subscriber);if(subscribedTypes ! = null) {// Iterate over the subscriber's eventsfor(Class<? > eventType : subscribedTypes) { unsubscribeByEventType(subscriber, eventType); } //typesBySubscriber Remove subscriber typesBySubscriber. Remove (subscriber); }else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: "+ subscriber.getClass()); } } private void unsubscribeByEventType(Object subscriber, Class<? > subscriptionsByEventType {// Obtain a Subscription collection from subscriptionsByEventType according to eventType List<Subscription> Subscriptions = subscriptionsByEventType.get(eventType);if(subscriptions ! = null) { int size = subscriptions.size();for (int i = 0; i < size; i++) {
                Subscription subscription = subscriptions.get(i);
                if(subscription == subscriber) {// Traverse the collection to remove the current subscriber subscription. Active =false; subscriptions.remove(i); i--; size--; }}}}Copy the code

2. Flow chart

How does EventBus recognize receive methods defined in a class?

One of the things that makes EventBus so popular is how easy it is to use. All we need to do is define methods in the class to receive the corresponding events and configure the corresponding annotations. How to retrieve the subscriber’s set of event methods is the essence of EventBus’s design. Through the above registration method, we know that mainly through the following method to obtain, we will mainly analyze the specific implementation.

List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
Copy the code

Then look at the events for findSubscriberMethods

List<SubscriberMethod> findSubscriberMethods(Class<? > subscriberClass) {// define a cache Map to avoid reflection fetch every time, List<SubscriberMethod> subscriberMethods = method_cache. get(subscriberClass);if(subscriberMethods ! = null) {return subscriberMethods;
        }
        if(ignoreGeneratedIndex) {// Get subscriberMethods by reflection = findUsingReflection(subscriberClass); }else{// Get subscriberMethods = findUsingInfo(subscriberClass) by index; }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);
            returnsubscriberMethods; }}Copy the code

Finally, indUsingReflection will be executed to obtain it, as follows:

private List<SubscriberMethod> findUsingReflection(Class<? > subscriberClass) {//FindState = prepareFindState(); findState.initForSubscriber(subscriberClass);while(findState.clazz ! = null) {/ / by reflection method for subscription information findUsingReflectionInSingleClass (findState); / / find the subscribe method of the parent findState. MoveToSuperclass (); }return getMethodsAndRelease(findState);
    }
Copy the code

The realization of the key in findUsingReflectionInSingleClass approach, implementation is as follows:

private void findUsingReflectionInSingleClass(FindState findState) { Method[] methods; Try {/ / / / method is obtained by reflecting the methods array = findState clazz. GetDeclaredMethods (); } catch (Throwable th) { // Workaroundfor java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true; } // iterate through Methodfor (Method method : methods) {
            int modifiers = method.getModifiers();
            if((modifiers & Modifier.PUBLIC) ! = 0 && (modifiers & MODIFIERS_IGNORE) == 0) { Class<? >[] parameterTypes = method.getParameterTypes(); //// is guaranteed to have only one event parameterif(parameterTypes.length == 1) { Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); // Get the annotationif(subscribeAnnotation ! = null) { Class<? > eventType = parameterTypes[0]; // Verify whether to add the methodif(findState.checkAdd(method, eventType)) { ThreadMode threadMode = subscribeAnnotation.threadMode(); . / / instantiate SubscriberMethod object and add 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

To summarize, EventBus retrieves the class’s collection of methods by reflecting getDeclaredMethods(), then iterates through the collection, adding methods that conform to the event definition (public, only one event parameter, annotations with Subcribe, etc.) to the collection. To identify the event methods defined in the subscriber.

What is the thread scheduling mechanism in EventBus?

As we know, EventBus can specify the thread of execution for the event callback by defining threadMode. The main configurations are as follows:

  • ThreadMode: POSTING: default, executed in the same thread.

  • ThreadMode: MAIN: MAIN thread execution

  • ThreadMode: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED: MAIN_ORDERED

  • ThreadMode: BACKGROUND: BACKGROUND process that handles operations such as saving to a database.

  • ThreadMode: ASYNC: asynchronously executed, another thread operation.

Post subscription postToSubscription specifies the type of threadMode used in the post.

Private void postToSubscription(Object event, Boolean isMainThread) { Using the corresponding post event processing 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

POSTING mode, directly implementing invokeSubscriber.

2. In MAIN mode, invokeSubscriber is executed if the current thread is considered to be the MAIN thread; otherwise, mainThreadPoster is executed as enqueue. MainThreadPoster is an instance of HandlerPoster that inherits from Handler and is created using MainLooper, whose handleMessage is executed in the main thread. Let’s look at the concrete implementation:

public void enqueue(Subscription subscription, Object event) {/ / build PendingPost Object PendingPost PendingPost = PendingPost. ObtainPendingPost (subscription, event); Synchronized (this) {queue. Enqueue (pendingPost); // If hander is not activatedif(! handlerActive) { handlerActive =true; // Send a messageif(! sendMessage(obtainMessage())) { throw new EventBusException("Could not send handler message");
                }
            }
        }
    }
    
      @Override
    public void handleMessage(Message msg) {
        boolean rescheduled = false;
        try {
            long started = SystemClock.uptimeMillis();
            while (truePendingPost pendingPost = queue.poll(); pendingPost pendingPost = queue.poll();if (pendingPost == null) {
                    synchronized (this) {
                        pendingPost = queue.poll();
                        if(pendingPost == null) {// If no data is available, update handlerActive =false;
                            return; }}} / / in the main thread of the method of implement event 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

HandlerPoster executes all event methods in the queue through the main thread’s Handler.

3, MAIN_ORDERED mode priority main queue execution

 if(mainThreadPoster ! = null) { mainThreadPoster.enqueue(subscription, event); }else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(subscription, event);
                }
Copy the code

4. BACKGROUND mode: if the main thread executes BACKGROUND thread execution, otherwise use the current thread

if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                
Copy the code

We’ll focus on the implementation of backgroundPoster, which inherits the Runnable thread pool to execute the run method, and does not start a new task every time through executorRunning control.

public void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); Synchronized (this) {queue. Enqueue (pendingPost); // Variable identifier, do not execute it every timeif(! executorRunning) { executorRunning =true; / / thread pool implementation eventBus. GetExecutorService (). The execute (this); } } } @Override public voidrun() {
        try {
            try {
                while (truePendingPost = queue. Poll (1000); PendingPost = queue.if (pendingPost == null) {
                        synchronized (this) {
                            // Check again, this time in synchronized
                            pendingPost = queue.poll();
                            if(pendingPost == null) {// Update the variable executorRunning = if there are no tasks leftfalse;
                                return; }}} / / in asynchronous thread execute the eventBus. The event method invokeSubscriber (pendingPost); } } catch (InterruptedException e) { eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() +" was interruppted", e);
            }
        } finally {
            executorRunning = false; }}Copy the code

BackgroundPoster opens a thread to execute all the event methods in the current queue.

5. ASYNC mode mainly uses AsyncPoster and also inherits the RUN interface. The implementation is as follows:

 public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        queue.enqueue(pendingPost);
        eventBus.getExecutorService().execute(this);
    }

    @Override
    public void run() {
        PendingPost pendingPost = queue.poll();
        if(pendingPost == null) {
            throw new IllegalStateException("No pending post available");
        }
        eventBus.invokeSubscriber(pendingPost);
    }
Copy the code

AsyncPoster Each event initiates an asynchronous task to be executed through the thread pool.

EventBus controls the execution of events in different threads by configuring threadMode. There are five patterns, namely POSTING, MAIN, MAIN_ORDERED, BACKGROUND, and ASYNC. The thread switch is mainly realized through HandlerPoster, backgroundPoster and AsyncPoster.

  • HandlerPoster opens a loop through the Handler of the main thread to execute all event methods in the queue.
  • BackgroundPoster opens a thread loop to execute all the event methods in the current queue.
  • AsyncPoster Each event initiates an asynchronous task to be executed through the thread pool.

How does EventBus send and receive events?

EventBus uses the observer mode to send and receive events. When register is used, subscribers and defined event receiving methods are added to the Map. When post is performed anywhere, event types are matched, all subscribers are found, and event methods are executed by reflection using different posters according to the configured threadMode. When unregister is used, subscribers are removed from the Map and unregistered.

What design patterns do the code in EventBus use? What are the clever ones?

1. Singleton mode

Eventbus.getdefault () uses the lazy singleton pattern.

2. Appearance mode

EventBus provides unified scheduling externally, shielding internal implementations.

3. Builder mode

The Event object is created using EventBusBuilder to separate the creation and presentation of complex objects. The caller does not need to know the complex creation process, and uses the relevant methods of Build to configure the creation of objects.

4. Strategy mode

Implementation policies for different Poster are used to execute event methods, depending on the threadMode setting

conclusion

1, the design of the framework is not complex but sophisticated

2. Using reflection and annotations simplifies many implementations

3. Use EventBus to avoid excessive abuse, which will lead to logic fragmentation and make it difficult to locate problems

recommended

Android source code series – decrypt OkHttp

Android source code series – Decrypt Retrofit

Android source code series – Decrypt Glide

Android source code series – Decrypt EventBus

Android source code series – decrypt RxJava

Android source code series – Decrypt LeakCanary

Android source code series – decrypt BlockCanary

about

Welcome to pay attention to my personal public number

Wechat search: Yizhaofusheng, or search the official ID: Life2Code

  • Author: Huang Junbin
  • Blog: junbin. Tech
  • GitHub: junbin1011
  • Zhihu: @ JunBin