Method of use

Add it to the dependencies section of build.gradle in your app.

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

Use three steps:

(1) Define events

public class MessageEvent {
    public final String message;
 
    public MessageEvent(String message) {
        this.message = message; }}Copy the code

(2) Define registration and logout

// The method to be called when the corresponding message is sent
// Depending on the entity class
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
    Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}

@Subscribe
public void handleSomethingElse(SomeOtherEvent event) {
    doSomethingWith(event);
}
// Register in lifecycle start
@Override
public void onStart(a) {
    super.onStart();
    EventBus.getDefault().register(this);
}
// Life cycle stop logout
@Override
public void onStop(a) {
    EventBus.getDefault().unregister(this);
    super.onStop();
}
Copy the code

(3) Send messages

EventBus.getDefault().post(new MessageEvent("Hello everyone!"));
Copy the code

Introduction to the source code

Every time we look at the source code, the first thing we need to do is to know what it does, before we start to understand how it works.

The eventbus.getDefault () method is created in a singleton mode.

(1) The use of the Subscribe annotation

(2) Registration and cancellation

(3) Send and process events

(4) Sticky events

The use of the Subscribe annotation

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    // The schema for threads is POSTING by default
    ThreadMode threadMode(a) default ThreadMode.POSTING;
    // Whether to insist on sticky events, default is not supported
    boolean sticky(a) default false;
    // A priority identifier, default is 0
    int priority(a) default 0;
}
Copy the code

We can see that there is an internal variable of ThreadMode, which we have used in our custom use, so it is up to us to explore how this works.

public enum ThreadMode {
    // Process directly in the current thread.
    POSTING,
    // Send events directly to the main thread, but requires small processing capacity; In child threads, it is sent by Handler and then processed by the main thread
    MAIN,
    // It is always queued to wait for the event to be sent by the Handler and then processed by the main thread
    MAIN_ORDERED,
    // Send events in the main thread. If an event is sent in a child thread, it is processed directly.
    BACKGROUND,
    // It is always queued and processed asynchronously through the thread pool.
    ASYNC
}
Copy the code

In the rest of the article, I’ll extract a pattern for validation.

Registration and logout

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

This time, however, we’ll take the opposite approach and look at EventBus as a whole from the point of logout, which is roughly what operations are involved.

The cancellation

public synchronized void unregister(Object subscriber) {
        // Get the set of parameter types for the subscribed event corresponding to contextList<Class<? >> subscribedTypes = typesBySubscriber.get(subscriber);if(subscribedTypes ! =null) {
            // Delete the corresponding data
            for(Class<? > eventType : subscribedTypes) {// Delete the eventType corresponding to content
                unsubscribeByEventType(subscriber, eventType); / / 1 - >
            }
            // Context is a map, and context is a key
            // The key corresponds to the ActivitytypesBySubscriber.remove(subscriber); }}// 1 --> The method called directly from comment 1
private void unsubscribeByEventType(Object subscriber, Class
        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 == subscriber) {
                    subscription.active = false; subscriptions.remove(i); i--; size--; }}}}Copy the code

There are several global variables we can see on both ends of the code.

// Map class. When used, Key corresponds to Activity and value corresponds to type
// Stores all event types corresponding to the Activity
private finalMap<Object, List<Class<? >>> typesBySubscriber;// Map class. When used, Key corresponds to type and value corresponds to subscription collection
// Stores the corresponding SUBSCRIPTIONS under type
private finalMap<Class<? >, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;Copy the code

registered

The evemtbus.getDefault () function is called in a singleton pattern, but we need to verify this in code.

static volatile EventBus defaultInstance;

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

It is clear that this is a DCL pattern for the creation of complete singletons.

If you are not sure about the DCL singleton creation pattern, you are advised to take a look at this article: The Eighteen Techniques of Design Patterns

At this point, you can see why we can use eventbus.getDefault () to perform operations between different activities. Then the next thing to do is get the all-important registration.

public void register(Object subscriber) { Class<? > subscriberClass = subscriber.getClass();// Is the set of methods found in the current Activity with the @SUBSCRIBE annotation
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            // Thread synchronization completes the binding of subscription events
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod); / / 1 - >}}}Copy the code

The query

What does the query look for? In fact, it’s a very simple logic, we added a signature comment in the code, remember, query is our @Subscribe method.

List<SubscriberMethod> findSubscriberMethods(Class
        subscriberClass) {
        // Retrieve from the cache, return if there is one, retrieve if there is none
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if(subscriberMethods ! =null) {
            return subscriberMethods;
        }
        // Since we usually use getDefault() for a registration, ignoreGeneratedIndex defaults to false. Just look at EventBusBuilder and you'll see that the value is unassigned, which is the default value false
        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
            // The default method called to collect subscribed methods
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            // Make a cache of the queried data
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            returnsubscriberMethods; }}Copy the code

The workflow is pretty clear, and our next step is to go into the findUsingInfo() function.

private 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 {
                // Generally enter this method, through the way of reflection to determine whether the method meets some basic conditions
                // Whether it is a public modifier, whether it is a parameter, whether it is static, whether it is abstract
                findUsingReflectionInSingleClass(findState);
            }
            // Used to skip some Android SDK queries
            // Can prevent some ClassNotFoundException
            findState.moveToSuperclass();
        }
        // Refactor the iterated method
        // Recycle findState
        return getMethodsAndRelease(findState);
    }
Copy the code

Ok, fine!!!!! At this point we can get our annotated methods.

Officially registered

What follows is a ridiculously long section of code, but I’ll try to give detailed comments to help understand it.

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
        // Find the parameter type of the corresponding functionClass<? > eventType = subscriberMethod.eventType;// New subscription method
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        // Subscribe to the event collection
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        // If the corresponding eventType does not exist in subscriptionsByEventType, create and store it
        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); }}// Insert the method of the new subscription into the subscription collection
        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; }}// Use context as the key to store subscription eventsList<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);
        // A specific action on sticky events
        if (subscriberMethod.sticky) {
           //...}}Copy the code

Such a large string of look down, think a lot of people very skip, after all, the code is really too much. In fact, it is the same as typesBySubscriber and subscriptionsByEventType. Why do you say that? Is a backward thinking, the registration of things to save, when the cancellation is must be deleted, then we go to see the cancellation will know.

conclusion

registered The cancellation

Send and process events

Send the event

EventBus.getDefault().post(new MessageEvent("MessageEvent"));
Copy the code

If you want to know how to send events, you have to look at the post() function, and of course there is another function called postSticky(), which is for sending sticky events, which we’ll talk about later.

public void post(Object event) { 
        // a ThreadLocal of type PostingThreadState
        // PostingThreadState is a class that stores information about event queues, thread patterns, etc
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        // Place the event in the event queue
        eventQueue.add(event);
        
        if(! postingState.isPosting) {// Determine whether the thread is the master thread
            postingState.isMainThread = isMainThread();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                // Send events in a loop
                while(! eventQueue.isEmpty()) { postSingleEvent(eventQueue.remove(0), postingState); }}finally {
                postingState.isPosting = false;
                postingState.isMainThread = false; }}}Copy the code

You can see that postSingleEvent() doesn’t really start until postSingleEvent(), because if you recall that ThreadMode is a variable we talked about at the beginning, and it gives you a way to send messages depending on the context of the thread, And I’m sure we’ll use that as well.

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<? > eventClass = event.getClass();boolean subscriptionFound = false;
        // Determine whether to query the parent class
        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);
        }
        //...
    }
Copy the code

We found that have not been used, but the above query methods there was a common feature, is called the postSingleEventForEventType (), and then further explore.

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class
        eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            // Obtain the corresponding subscriptions collection
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if(subscriptions ! =null && !subscriptions.isEmpty()) {
            // Handle events in rotation
            for (Subscription subscription : subscriptions) {
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted;
                try {
                    // Do the actual processing of the event
                    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

This postToSubscription() method, which is essentially what we called threadMode at the beginning, gives us the corresponding processing based on the value we set in @subscribe.

Handle events

I’m only going to show you one of them here, because the rest are still based on handlers, so I’ll just stick with POSTING as the default method.

Handler Handler Handler Handler Handler Handler Handler Handler Handler

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                // If it is in the main thread, it is processed directly
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else { // If it is not in the main thread, it is sent to the main thread for processing
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            //...}}Copy the code

This is the unexplained part of the sending part, which already overlapses with the processing part. If the current thread can handle the scheme directly, they will end up calling a function called invokeSubscriber().

As mentioned earlier, POSTING handles the POSTING method directly on the current thread. If you look at the source code, you can see that it doesn’t evaluate the isMainThread variable.

void invokeSubscriber(Subscription subscription, Object event) {
        try {
            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

Of course, we end up calling a method.invoke () Method to process our Method, which corresponds directly to our corresponding defined Method.

About viscous events

EventBus.getDefault().postSticky(new MessageEvent("MessageEvent"));
Copy the code

Make a call with the postSticky() method

public void postSticky(Object event) {
        synchronized (stickyEvents) {
            // For each sticky event, make sure we get the latest one when we finally get it
            stickyEvents.put(event.getClass(), event);
        }
        // Use lock mechanism to store before sending, prevent direct consumption
        post(event);
    }
Copy the code

We end up calling a post() method again, but here we need to remember the method in subscribe() that deals with sticky events that we haven’t analyzed yet. (You can go back to the registration to view)

if (subscriberMethod.sticky) {
            if (eventInheritance) {
                // this is a merging of the parent classesSet<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); / / 1 - >}}}else {
                // Create a layer of cache for storing data
                Object stickyEvent = stickyEvents.get(eventType);
                checkPostStickyEventToSubscription(newSubscription, stickyEvent); / / 1 - >}}// 1 -- the comment 1 method that either if or else will eventually be called
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.
            // This method has been analyzed beforepostToSubscription(newSubscription, stickyEvent, isMainThread()); }}Copy the code

StickyEvents ends up being the same as send events, which requires a call to postToSubscription() to send a method, but provides an additional place for caching stickyEvents to store a stickyEvents operation.

conclusion

The resources

  1. EventBus — She Huan