Android EventBus source code analysis brings you a deep understanding of EventBus

EventBus definition: is a publish/subscribe event bus.
So it should contain 4 components: publisher, subscriber, event, bus.
So what is the relationship between these four?
Obviously: subscribers subscribe events to the bus, senders publish events.

1. Overview
Generally use the component class of EventBus, similar to the following way:
public class SampleComponent extends Fragment
{

	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate (savedInstanceState);
		EventBus.getDefault().register(this);
	}

	public void onEventMainThread(param)
	{
	}
	
	public void onEventPostThread(param)
	{
		
	}
	
	public void onEventBackgroundThread(param)
	{
		
	}
	
	public void onEventAsync(param)
	{
		
	}
	
	@Override
	public void onDestroy()
	{
		super.onDestroy ();
		EventBus.getDefault().unregister(this);
	}
	
}

In most cases, register in onCreate and unregister in onDestory;
after reading the code, you may have some questions:
1. There are some methods starting with onEvent in the code. What are these methods for?
Before answering this question, I have a question, why don't you ask what register(this) is for? In fact, register(this) is to go to the current class, traverse all the methods, find the beginning of onEvent and then store it. Now you know what the method at the beginning of onEvent is for.
2. What signs should those MainThreads behind onEvent be?
Well, yes, four methods can be written after onEvent, that is, the four methods appearing above, which determine which thread the current method will run on and how it will run. You can refer to the previous blog or read it down.

Now that it is registered, it must be said how to call it.
EventBus.getDefault().post(param);

The call is very simple. In a word, you can also call it release. As long as the param is released, EventBus will scan in the method stored in it, find the parameter matching, and use reflection to call.
Now, do you feel that, aside from professional terms: Actually, EventBus stores a bunch of methods at the beginning of onEvent, and then when post, according to the parameters passed in by post, to find a matching method, and call it by reflection.
Well, I tell you, it uses Map internally for storage, and the key is the Class type of the parameter. Knowing that it is this type, do you think it is still a thing to search based on the parameters passed in by post?

Let's take a look at the real face of EventBus's register and post.

2. register
EventBus.getDefault().register(this);
First of all:
EventBus.getDefault() is actually a singleton, which has the same meaning as our traditional getInstance:
/** Convenience singleton for apps using a process-wide EventBus instance. */  
   public static EventBus getDefault() {  
       if (defaultInstance == null) {  
           synchronized (EventBus.class) {  
               if (defaultInstance == null) {  
                   defaultInstance = new EventBus();  
               }  
           }  
       }  
       return defaultInstance;  
   }

The double judgment method is used to prevent concurrency problems and greatly improve efficiency.
Then register should be a common method, let's take a look:
There are 4 registers announced for us to use:
public void register(Object subscriber) {  
        register(subscriber, DEFAULT_METHOD_NAME, false, 0);  
    }  
 public void register(Object subscriber, int priority) {  
        register(subscriber, DEFAULT_METHOD_NAME, false, priority);  
    }  
public void registerSticky(Object subscriber) {  
        register(subscriber, DEFAULT_METHOD_NAME, true, 0);  
    }  
public void registerSticky(Object subscriber, int priority) {  
        register(subscriber, DEFAULT_METHOD_NAME, true, priority);  
    }

Essentially the same one is called:
private synchronized void register(Object subscriber, String methodName, boolean sticky, int priority) {  
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),  
                methodName);  
        for (SubscriberMethod subscriberMethod : subscriberMethods) {  
            subscribe(subscriber, subscriberMethod, sticky, priority);  
        }  
    }

four parameters
  • subscriber is the object of our scan class, which is the common this in our code;
  • methodName This is hard-coded: "onEvent", the method used to determine what to scan, it can be seen that our classes all start with this.
  • The sticky parameter is explained when explaining the source code, so don't worry about it for the time being
  • priority Priority, the higher the priority, the earlier it will be called when it is called.

Let's start looking at the code:
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(),  
                methodName);

Call the findSubscriberMethods method of the internal class SubscriberMethodFinder, pass in the subscriber's class, and methodName, and return a List<SubscriberMethod>.
So needless to say, it must be traversed all the methods inside the class, and then matched according to the methodName, the successful matching is packaged into SubscriberMethod, and finally a List is returned. See the code below:
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass, String eventMethodName) {  
        String key = subscriberClass.getName() + '.' + eventMethodName;  
        List<SubscriberMethod> subscriberMethods;  
        synchronized (methodCache) {  
            subscriberMethods = methodCache.get(key);  
        }  
        if (subscriberMethods != null) {  
            return subscriberMethods;  
        }  
        subscriberMethods = new ArrayList<SubscriberMethod>();  
        Class<?> clazz = subscriberClass;  
        HashSet<String> eventTypesFound = new HashSet<String>();  
        StringBuilder methodKeyBuilder = new StringBuilder();  
        while (clazz != null) {  
            String name = clazz.getName();  
            if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {  
                // Skip system classes, this just degrades performance  
                break;  
            }  
  
            // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)  
            Method[] methods = clazz.getMethods();  
            for (Method method : methods) {  
                String methodName = method.getName();  
                if (methodName.startsWith(eventMethodName)) {  
                    int modifiers = method.getModifiers();  
                    if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {  
                        Class<?>[] parameterTypes = method.getParameterTypes();  
                        if (parameterTypes.length == 1) {  
                            String modifierString = methodName.substring(eventMethodName.length());  
                            ThreadMode threadMode;  
                            if (modifierString.length() == 0) {  
                                threadMode = ThreadMode.PostThread;  
                            } else if (modifierString.equals("MainThread")) {  
                                threadMode = ThreadMode.MainThread;  
                            } else if (modifierString.equals("BackgroundThread")) {  
                                threadMode = ThreadMode.BackgroundThread;  
                            } else if (modifierString.equals("Async")) {  
                                threadMode = ThreadMode.Async;  
                            } else {  
                                if (skipMethodVerificationForClasses.containsKey(clazz)) {  
                                    continue;  
                                } else {  
                                    throw new EventBusException("Illegal onEvent method, check for typos: " + method);  
                                }  
                            }  
                            Class<?> eventType = parameterTypes[0];  
                            methodKeyBuilder.setLength(0);  
                            methodKeyBuilder.append(methodName);  
                            methodKeyBuilder.append('>').append(eventType.getName());  
                            String methodKey = methodKeyBuilder.toString();  
                            if (eventTypesFound.add(methodKey)) {  
                                // Only add if not already found in a sub class  
                                subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));  
                            }  
                        }  
                    } else if (!skipMethodVerificationForClasses.containsKey(clazz)) {  
                        Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "."  
                                + methodName);  
                    }  
                }  
            }  
            clazz = clazz.getSuperclass();  
        }  
        if (subscriberMethods.isEmpty()) {  
            throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called "  
                    + eventMethodName);  
        } else {  
            synchronized (methodCache) {  
                methodCache.put(key, subscriberMethods);  
            }  
            return subscriberMethods;  
        }  
    }

Oh, the code is really long; but let's look directly at the core part:
Line 22: See, clazz.getMethods(); to get all the methods:
Lines 23-62: Start traversing each method to match the encapsulation .
Lines 25-29: Determine whether it starts with onEvent, whether it is a public and non-static and abstract method, and whether it is a parameter. If they are all compounded, enter the encapsulation part.
Lines 32-45: It is also relatively simple. According to the suffix of the method, threadMode is determined. threadMode is an enumeration type: there are four cases.
Finally, on line 54: the method, threadMode, eventType is passed into the construct: new SubscriberMethod(method, threadMode, eventType). Added to List, and finally put back.
Note the next line 63: clazz = clazz.getSuperclass(); As you can see, all superclasses are scanned, not just the current class.
Go back to register:
for (SubscriberMethod subscriberMethod : subscriberMethods) {  
            subscribe(subscriber, subscriberMethod, sticky, priority);  
        }

The method scanned by the for loop, and then call the suscribe method.
// Must be called in synchronized block  
   private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {  
       subscribed = true;  
       Class<?> eventType = subscriberMethod.eventType;  
       CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);  
       Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);  
       if (subscriptions == null) {  
           subscriptions = new CopyOnWriteArrayList<Subscription>();  
           subscriptionsByEventType.put(eventType, subscriptions);  
       } else {  
           for (Subscription subscription : subscriptions) {  
               if (subscription.equals(newSubscription)) {  
                   throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "  
                           + eventType);  
               }  
           }  
       }  
  
       // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)  
       // subscriberMethod.method.setAccessible(true);  
  
       int size = subscriptions.size();  
       for (int i = 0; i <= size; i++) {  
           if (i == size || newSubscription.priority > subscriptions.get(i).priority) {  
               subscriptions.add(i, newSubscription);  
               break;  
           }  
       }  
  
       List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);  
       if (subscribedEvents == null) {  
           subscribedEvents = new ArrayList<Class<?>>();  
           typesBySubscriber.put(subscriber, subscribedEvents);  
       }  
       subscribedEvents.add(eventType);  
  
       if (sticky) {  
           Object stickyEvent;  
           synchronized (stickyEvents) {  
               stickyEvent = stickyEvents.get(eventType);  
           }  
           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.  
               postToSubscription (newSubscription, stickyEvent, Looper.getMainLooper () == Looper.myLooper ());  
           }  
       }  
   }

Our subscriberMethod saves method, threadMode, eventType, as mentioned above;
Line 4-17: According to subscriberMethod.eventType, go to subscriptionsByEventType to find a CopyOnWriteArrayList<Subscription>, if not, create it.
By the way, we encapsulate our incoming parameters into one: Subscription (subscriber, subscriberMethod, priority);
the subscriptionsByEventType here is a Map, key: eventType; value: CopyOnWriteArrayList<Subscription>; This Map is actually where the EventBus storage method is, it must be must remember!
Lines 22-28: In fact, newSubscription is added; and it is added according to priority. As you can see, the higher the priority, the more it will be inserted in front of the current List.
Lines 30-35: store all its eventTypes according to subscriber; still map; key: subscriber, value: List<eventType>; just know, non-core code, mainly used for isRegister judgment.
Lines 37-47: judge sticky; if it is true, find out whether there is a stickyEvent according to the eventType in stickyEvents, and if there is, publish it immediately for execution. stickyEvent is actually the parameter when we post.
The method postToSubscription will be introduced when we post.

At this point, our register is introduced.
You just need to remember one thing: all methods are scanned, and the matching method is finally saved in subscriptionsByEventType (Map, key: eventType; value: CopyOnWriteArrayList<Subscription> );
eventType is the Class of our method parameters, and Subscription is stored in subscriber, subscriberMethod(method, threadMode, eventType), priority; contains everything needed to execute the method.

3. After the post
register is completed, we know how EventBus stores our method. Let's see how the post calls our method.
Before looking at the source code, let's guess: when registering, the method exists in subscriptionsByEventType; then the post will definitely go to subscriptionsByEventType to get the method, and then call it.
See the source code below:
/** Posts the given event to the event bus. */  
   public void post(Object event) {  
       PostingThreadState postingState = currentPostingThreadState.get();  
       List<Object> eventQueue = postingState.eventQueue;  
       eventQueue.add(event);  
  
       if (postingState.isPosting) {  
           return;  
       } else {  
           postingState.isMainThread = Looper.getMainLooper () == Looper.myLooper ();  
           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;  
           }  
       }  
   }

currentPostingThreadState is a ThreadLocal type, which stores PostingThreadState; PostingThreadState contains an eventQueue and some flags.
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {  
       @Override  
       protected PostingThreadState initialValue() {  
           return new PostingThreadState();  
       }  
   }

Save the event we passed in to the eventQueue of a variable PostingThreadState in the current thread.
Line 10: Determine whether the current UI thread is.
Lines 16-18: Traverse all events in the queue and call the postSingleEvent (eventQueue.remove(0), postingState) method.
Will you have any doubts here, will the entire queue be called for each post, so will it not cause the method to be called multiple times?
You can see lines 7-8. There is a judgment to prevent this problem. If isPosting=true, it will not go down.

Look at postSingleEvent below
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {  
        Class<? extends Object> eventClass = event.getClass();  
        List<Class<?>> eventTypes = findEventTypes(eventClass);  
        boolean subscriptionFound = false;  
        int countTypes = eventTypes.size();  
        for (int h = 0; h < countTypes; h++) {  
            Class<?> clazz = eventTypes.get(h);  
            CopyOnWriteArrayList<Subscription> subscriptions;  
            synchronized (this) {  
                subscriptions = subscriptionsByEventType.get(clazz);  
            }  
            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;  
                    }  
                }  
                subscriptionFound = true;  
            }  
        }  
        if (!subscriptionFound) {  
            Log.d(TAG, "No subscribers registered for event " + eventClass);  
            if (eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) {  
                post(new NoSubscriberEvent(this, event));  
            }  
        }  
    }

Pass our event, the actual parameter passed in by post; and the postingState into postSingleEvent.
Lines 2-3: According to the Class of the event, to get a List<Class<?>>; in fact, it is to get the Class of the current object of the event, as well as the Class type of the parent class and interface; it is mainly used for matching, for example, you pass in Dog extends Dog, he will also install Animal into the List.
Lines 6-31: traverse all the classes, go to subscriptionsByEventType to find subscriptions; haha, familiar or not, remember where we stored the method in the register?
Is it this Map?
Lines 12-30: Traverse each subscription and call postToSubscription(subscription, event, postingState.isMainThread) in turn;
this method is to reflect the execution method, you still remember when register, if (sticky) , will also execute this method.
Let's see how it executes reflectively:
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {  
        switch (subscription.subscriberMethod.threadMode) {  
        case PostThread:  
            invokeSubscriber(subscription, event);  
            break;  
        case MainThread:  
            if (isMainThread) {  
                invokeSubscriber(subscription, event);  
            } else {  
                mainThreadPoster.enqueue(subscription, event);  
            }  
            break;  
        case BackgroundThread:  
            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);  
        }  
    }

As mentioned earlier, subscription contains all the things needed for execution, roughly: subscriber, subscriberMethod (method, threadMode, eventType), priority;
then this method: the first step is to determine which thread should execute the method according to threadMode;
case PostThread:
void invokeSubscriber(Subscription subscription, Object event) throws Error {  
          subscription.subscriberMethod.method.invoke(subscription.subscriber, event);  
}

Direct reflection call; that is to say, the method is called directly on the current thread;
case MainThread:
First, if it is a UI thread, it will be called directly; otherwise: mainThreadPoster.enqueue(subscription, event); add the current method to the queue , and then send a message directly through the handler, and execute our method in the handler's handleMessage. To put it bluntly, the message is sent through the Handler and then executed.
case BackgroundThread: If the current non-UI thread, call it directly; if it is a UI thread, add the task to a queue in the background, and finally call executorService = Executors.newCachedThreadPool();
from a thread pool in Eventbus . case Async: The task is added to a queue in the background, and finally called by a thread pool in Eventbus; the thread pool and BackgroundThread are the same. So what is the difference between BackgroundThread and Async? The tasks in the BackgroundThread are called one after another, using a boolean variable handlerActive in the middle. Async dynamically controls concurrency. At this point, our complete source code analysis is over. To sum up: register will store the matching method in the current class into a map, and post will search for the map according to the actual parameters and make a reflection call. After analyzing for so long, one sentence is finished~~







In fact, the words publisher, subscriber, event, and bus may be better understood. Later, everyone asked EventBus. It can be said that a map object is maintained within a singleton to store a bunch of methods; post is nothing more than Find the method according to the parameters and make a reflection call.

4. The other methods
introduced register and post; everyone can think of a word sticky. In register, how to sticky is true, it will go to stickyEvents to find the event, and then go to post immediately;
then when will this stickyEvents save the event?
In fact, in evevntbus, in addition to post publishing events, there is also a method:
public void postSticky(Object event) {  
       synchronized (stickyEvents) {  
           stickyEvents.put(event.getClass(), event);  
       }  
       // Should be posted after it is putted, in case the subscriber wants to remove immediately  
       post(event);  
   }

Similar to the post function, but the method will be stored in stickyEvents;
let's take a look at all the public methods in EventBus, which are nothing more than some methods of judging status, getting events, and removing events; there is nothing to introduce, basically see Name knowledge.

Well, now our source code analysis is over. I hope you can not only understand the internal mechanism of these excellent frameworks, but also understand many details of these frameworks, concurrent processing, many places, why it does this, etc. .

I have created a group for everyone to communicate with. Group number: 104286694

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326629105&siteId=291194637