EventBus3.0 source code analysis
Preface
EventBus3.0 has made a big change. Instead of the method starting with OnEvent with extremely poor readability, it uses annotations.
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN,priority = 100)
public void test(String string) {
Log.e("cat", "str:" + string);
}
Like this. Since annotations are used, is reflection used to find a method to receive events? The answer is No. EventBus provides an EventBusAnnotationProcessor that allows you to generate files during compilation through annotations. This is very similar to Dagger2 and Butterknife. It not only saves the ability to write template code, but also does not cause performance loss due to reflection. Of course, compiling annotation is optional. If this option is not used, reflection will still be used. This can be seen from the source code below.
Source code analysis
Before talking about the source code, let's talk about several important classes to avoid confusion.
SubscribeMethod
// The annotation above the Method for receiving events is encapsulated into a SubscribeMethodpublic class SubscriberMethod {final method Method; final ThreadMode threadMode; final Class
EventType; final int priority; final boolean sticky;/** Used for efficient comparison */String methodString ;}
Subtasks
// Encapsulate the subscriber and Subscription method into a Subscritpionfinal class subscri{ final Object subscriber; final SubscriberMethod subscriberMethod; /*** Becomes false as soon as {@ link EventBus # unregister (Object)} is called, which is checked by queued event delivery * {@ link EventBus # invokeSubscriber (PendingPost )} to prevent race conditions. */volatile boolean active; subscriber (Object subscriber, SubscriberMethod subscriberMethod) {this. subscriber = subscriber; this. subscriberMethod = subscriberMethod; active = true ;}
PostingThreadState
// Stores thread information, subscribers, and events. Post events use/** For ThreadLocal, much faster to set (and get multiple values). */final static class PostingThreadState {final ListEventQueue = new ArrayList(); Boolean isPosting; boolean isMainThread; Subscribe subscribe; Object event; boolean canceled ;}
PendingPost
// Encapsulate events and subscriber into a PendingPost, which is mainly used when switching threads. Final class PendingPost {private final static List
PendingPostPool = new ArrayList
(); Object event; Subscribe subscribe; PendingPost next;
HandlerPost
// Send the event to the handler executed by the main thread. Final class HandlerPoster extends Handler {private final PendingPostQueue queue; private final int maxMillisInsideHandleMessage; private final EventBus eventBus; private boolean handlerActive ;}
BackgroudPoster
// EventBus maintains a thread pool for Executors. newCachedThreadPool. When BackGroudPost. enqueue is called, the event is obtained to the thread pool for execution. // BackGroundPost is a Runnable that maintains a queue internally. In the run method, the PendingPost will be continuously retrieved from the queue. When the queue does not have anything, it will wait at most 1 s, then jump out of the run method. Final class BackgroundPoster implements Runnable {private final PendingPostQueue queue; private final EventBus eventBus; private volatile boolean executorRunning ;}
AsyncPoster
// The implementation of AsyncPoster is very similar to that of BackgroundPoster. The difference is whether PendingPost is retrieved cyclically in the run method. // Although similar, the main difference is that asyncPost ensures that each task is executed in different threads, however, if BackgroundPoster is fast and receives events in the background, it will be executed in the same thread. Class AsyncPoster implements Runnable {private final PendingPostQueue queue; private final EventBus eventBus;
Now, several important classes have been introduced, and the analysis of the main process will begin.
EventBus Construction
EventBus can also be built in the form of Builder, but in actual use, it is generally implemented by default. EventBus is also a singleton. The Code implemented by the Singleton is too simple.
Public EventBus () {this (DEFAULT_BUILDER);} EventBus (EventBusBuilder builder) {subscriptionsByEventType = new HashMap <> (); // take the event type as the key, subscribe list is the map of value. TypesBySubscriber = new HashMap <> (); // The list of subscribed events is value map stickyEvents = new ConcurrentHashMap <> (); // map with the event type as key and event object as value. MainThreadPoster = new HandlerPoster (this, logoff. getmainloader (), 10); backgroundPoster = new BackgroundPoster (this); asyncPoster = new AsyncPoster (this); indexCount = builder. subscriberInfoIndexes! = Null? Builder. subscriberInfoIndexes. size (): 0; // This is only available when the annotation is used during compilation. SubscriberMethodFinder = new SubscriberMethodFinder (builder. subscriberInfoIndexes, // mainly used to find the builder of the subscriber method. strictMethodVerification, builder. ignoreGeneratedIndex); logSubscriberExceptions = builder. logSubscriberExceptions; logNoSubscriberMessages = builder. logNoSubscriberMessages; sendSubscriberExceptionEvent = builder. sendSubscriberExceptionEvent; sendNoSubscriberEvent = builder. sendNoSubscriberEve Nt; throwSubscriberException = builder. throwSubscriberException; eventInheritance = builder. eventInheritance; // whether to use Event-type inheritance. If it is true, for example, if the parameter is a string-type method, the object-type method can also receive events. ExecutorService = builder.exe cutorService; // thread pool, implementation class: Executors. newCachedThreadPool ()}
The above attributes seem to be quite a lot, and it doesn't matter if you don't understand them now. It's good to look at it. I may have said it before without comments, or the source code mentioned later is not mentioned.
Register SubscriverEventbus. register ()
Public void register (Object subscriber) {Class
SubscriberClass = subscriber. getClass (); // you can call this operation to find the List of methods for the subscriber to receive events.
SubscriberMethods = subscriberMethodFinder. findSubscriberMethods (subscriberClass); synchronized (this) {// stores events, event types, subscribers, etc., and receives stick events. For more information, see the source code. For (SubscriberMethod subscriberMethod: subscriberMethods) {subscribe (subscriber, subscriberMethod );}}}
SubscriberMethodFinder. findSubscriberMethods
List
FindSubscriberMethods (Class
SubscriberClass) {// Eventbus stores the subscriber as the key and the subscriber's method list for receiving events as the value in the METHOD_CACHE cache. If the subscriber receives the event, it returns the result directly. List
SubscriberMethods = METHOD_CACHE.get (subscriberClass); if (subscriberMethods! = Null) {return subscriberMethods;} // whether to ignore the file generated by the annotation during compilation if (ignoreGeneratedIndex) {subscriberMethods = findUsingReflection (subscriberClass );} else {subscriberMethods = findUsingInfo (subscriberClass);} if (subscriberMethods. isEmpty () {throw new EventBusException ("Subscriber" + subscriberClass + "and its super classes have no public methods with the @ Subscribe annotation ");} else {// put METHOD_CACHE.put (subscriberClass, subscriberMethods); return subscriberMethods ;}}
Note that the annotation during compilation is ignored above. If the file annotated during compilation is used, the meaning of the analysis will not continue. Therefore, the annotation during compilation will not be analyzed here. Then, click findUsingReflection (subscriberClass)
SubscriberMethodFinder. findUsingReflection
Private List
FindUsingReflection (Class
SubscriberClass) {// FindState is an object used to store methods and subscriber classes found. FindState findState = prepareFindState (); findState. initForSubscriber (subscriberClass); // The following is a real search method, including the method for receiving messages from the subscriber's parent class, which will also be found. Of course, this is configurable. While (findState. clazz! = Null) {// search for the message receiving method findUsingReflectionInSingleClass (findState); // jump to the parent class and continue searching until the parent class is empty. FindState. moveToSuperclass () ;}// return the searched method and clear the findState variable. Return getMethodsAndRelease (findState );}
SubscriberMethodFinder. findUsingReflectionInSingleClass
Next let's take a look at the method for actually executing reflection.
Private void findUsingReflectionInSingleClass (FindState findState) {Method [] methods; try {// findState. Clazz is the parent class of the subscriber or subscriber. // This is faster than getMethods, especially when subscribers are fat classes like Activities methods = findState. clazz. getDeclaredMethods ();} catch (Throwable th) {// Workaround for java. lang. noClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149 methods = findState. clazz. getMethods (); findState. skipSuperClasses = true;} for (Method method: methods ){ Int modifiers = method. getModifiers (); // The method must be public if (modifiers & Modifier. PUBLIC )! = 0 & (modifiers & MODIFIERS_IGNORE) = 0) {Class
[] ParameterTypes = method. getParameterTypes (); // The method parameter must be an if (parameterTypes. length = 1) {Subscribe subscribeAnnotation = method. getAnnotation (Subscribe. class); if (subscribeAnnotation! = Null) {Class
EventType = parameterTypes [0]; // check whether the parameter can be added. There are two levels of verification. The first level is the key based on eventType, and the second level is the key generated based on method and eventType, verify again, and view the detailed implementation on your own. If (findState. checkAdd (method, eventType) {// encapsulate SubscribeMethod and add it to subscriberMethods. ThreadMode threadMode = subscribeAnnotation. threadMode (); 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 ");}}}
The above implementation is very simple. I believe that you will be able to commit yourself for a long time before looking at the source code. It is mainly to find the method for receiving the message for verification, and then store it in the subscriberMethods in findState.
In this way, the entire search is complete and the register method is returned again.
Public void register (Object subscriber) {Class
SubscriberClass = subscriber. getClass (); // you can call this operation to find the List of methods for the subscriber to receive events.
SubscriberMethods = subscriberMethodFinder. findSubscriberMethods (subscriberClass); synchronized (this) {// stores events, event types, subscribers, etc., and receives stick events. For more information, see the source code. For (SubscriberMethod subscriberMethod: subscriberMethods) {subscribe (subscriber, subscriberMethod );}}}
Subscribe Method
EventBus. subscribe
Private void subscribe (Object subscriber, SubscriberMethod subscriberMethod) {Class
EventType = subscriberMethod. eventType; // encapsulate the subscriber and the method for receiving events into a subscriber. Why do I need a method and subscriber encapsulate the subscriber object? // The reason is that EventBus supports only unbinding a method from the subscriber. Subscriber newsubscriber = new subscriber (subscriberMethod); // retrieve all subscriber Based on the event type. Subscrpiption. It's too easy to leave blank and assign values. CopyOnWriteArrayList
Subscriptions = subscriptionsByEventType. get (eventType); if (subscriptions = null) {subscriptions = new CopyOnWriteArrayList <> (); subscriptionsByEventType. put (eventType, subscriptions);} else {if (subscriptions. contains (newsubception) {throw new EventBusException ("Subscriber" + subscriber. getClass () + "already registered to event" + eventType) ;}// this loop mainly puts the priority (priority) high before the list. Int size = subscriptions. size (); for (int I = 0; I <= size; I ++) {if (I = size | subscriberMethod. priority> subscriptions. get (I ). subscriberMethod. priority) {subscriptions. add (I, newsubtasks); break;} List
> SubscribedEvents = typesBySubscriber. get (subscriber); if (subscribedEvents = null) {subscribedEvents = new ArrayList <> (); typesBySubscriber. put (subscriber, subscribedEvents);} subscribedEvents. add (eventType); // here it is used to handle the stick event. Here stickEvents is assigned a value during postStick. If (subscriberMethod. sticky) {if (eventInheritance) {// Existing sticky events of all subclasses of eventType have to be considered. // Note: Iterating over all events may be inefficient with lots of sticky events, // thus data structure shocould be changed to allow a more efficient lookup // (e.g. an additional map storing sub classes of super classes: Class-> List
). Set
, Object> entries = stickyEvents. entrySet (); for (Map. Entry
, Object> entry: entries) {Class
CandidateEventType = entry. getKey (); if (eventType. isAssignableFrom (candidateEventType) {Object stickyEvent = entry. getValue (); checkpoststickyeventtosubtasks (newsubtasks, stickyEvent) ;}} else {Object stickyEvent = stickyEvents. get (eventType); checkPostStickyEventToSubscription (newsubevent, stickyEvent );}}}
The entire subscription process is complete. What should I do after subscription? Send events.
Event sending
PostSticky is directly viewed here, because the post method is also called internally.
EventBus. postSticky ()
Public void postSticky (Object event) {synchronized (stickyEvents) {// No. Here, the event type is key, and the event Object value is stored in the map. // As shown in the following figure, if postStick has two events of the same type, the previous event will be overwritten. StickyEvents. put (event. getClass (), event);} // shocould be posted after it is putted, in case the subscriber wants to remove immediately post (event );}
EventBus. post ()
/** Posts the given event to the event bus. */public void post (Object event) {// The PostingThreadState will use ThradLocal to obtain the PostingThreadState of the current thread Based on the thread. comrades who know the handler mechanism should be aware of it. PostingThreadState postingState = currentPostingThreadState. get (); ListEventQueue = postingState. eventQueue; eventQueue. add (event); if (! PostingState. isPosting) {// determines whether the current master thread is postingState. isMainThread = logoff. getmainlogoff () = logoff. mylogoff (); postingState. isPosting = true; if (postingState. canceled) {throw new EventBusException ("Internal error. abort state was not reset ");} try {while (! EventQueue. isEmpty () {// retrieve the first event queue to send the postSingleEvent (eventQueue. remove (0), postingState) ;}} finally {postingState. isPosting = false; postingState. isMainThread = false ;}}}
EventBus. postSingleEvent
Private void postSingleEvent (Object event, PostingThreadState postingState) throws Error {Class
EventClass = event. getClass (); boolean subscriptionFound = false; if (eventInheritance) {// retrieve all types of itself, parent class, and interface based on the event type. List
> EventTypes = lookupAllEventTypes (eventClass); int countTypes = eventTypes. size (); for (int h = 0; h <countTypes; h ++) {Class
Clazz = eventTypes. get (h); // execute sending and return whether subscriptionFound | = postSingleEventForEventType (event, postingState, clazz) ;}} else {subscriptionFound = postSingleEventForEventType (event, postingState, eventClass);} if (! SubscriptionFound) {if (logNoSubscriberMessages) {Log. d (TAG, "No subscribers registered for event" + eventClass);} if (sendNoSubscriberEvent & eventClass! = NoSubscriberEvent. class & eventClass! = SubscriberExceptionEvent. class) {post (new NoSubscriberEvent (this, event ));}}}
The above method determines the subscribers to be sent based on whether eventInheritance is true. If the value is true, the subscriber that receives events including the event itself, the parent class of the event, and the interface type will receive the event.
EventBus. postSingleEventForEventType ()
Private boolean postSingleEventForEventType (Object event, PostingThreadState postingState, Class
EventClass) {CopyOnWriteArrayList
Subscriptions; synchronized (this) {subscriptions = subscriptionsByEventType. get (eventClass);} if (subscriptions! = Null &&! Subscriptions. isEmpty () {// retrieve the subscriber without the value of postingState. For (subscribe subtions: subscriptions) {postingState. event = event; postingState. subtasks = subtasks; boolean aborted = false; try {// send the event based on the thread that receives the event in subtasks and the current thread. Posttosubtasks (subtasks, event, postingState. isMainThread); aborted = postingState. canceled;} finally {postingState. event = null; postingState. subtasks = null; postingState. canceled = false;} if (aborted) {break;} return true;} return false ;}
EventBus. posttosubparts ()
Private void posttosubtasks (subscribe subtasks, Object event, boolean isMainThread) {// the thread that receives the event specified by the method. Conclusion: The previous blog actually mentioned // 1. if it is posting, the received and sent events are executed in the same thread. // 2. If it is mainThread, if the current thread is not the main thread, it will be handed over to mainThreadPoster for execution. mainThreadPoster is a handler with internal message queue of the main thread, as mentioned above. // 3. If it is a background, if it is currently the main thread, it will be handed over to the backgroundPoster for execution and then to the eventBus thread pool for execution. Otherwise, it will be executed in the same thread. // 4. If it is Async, the current thread is handed over to asyncPoster for execution to ensure that each task is in a different thread. Switch (subpartition. subscriberMethod. threadMode) {case POSTING: invokeSubscriber (subscriber, event); break; case MAIN: if (isMainThread) {invokeSubscriber (subscriber, event);} else {mainThreadPoster. enqueue (subqueue, event);} break; case BACKGROUND: if (isMainThread) {backgroundPoster. enqueue (subqueue, event);} else {invokeSubscriber (subscriber, event);} break; case ASYNC: asyncPoster. enqueue (subqueue, event); break; default: throw new IllegalStateException ("Unknown thread mode:" + subqueue. subscriberMethod. threadMode );}}
The above is an explanation of thread distribution in the official documentation. If you are interested in implementation, you can also see it. It is not complicated.
Summary
The process is probably like this. After analyzing the register and post, it is not difficult to analyze others. Although some of the above storage events and the map or list of subscriber are useless in the source code I analyzed, you can see the code to unregister the subscriber.
In fact, the source code of EventBus is relatively simple. I probably guessed this process before looking at the Source Code. However, concurrency control is still good, and it is worth learning.