1. Introduction to EventBus
EventBus is an event publishing-subscription bus for Android, developed by GreenRobot. It simplifies the complexity of communication between components within an application, especially between fragments, and avoids the inconvenience of using broadcast communication.
EventBus can replace Android's traditional Intent,Handler,Broadcast, or interface functions to transfer data and execute methods between Fragment, Activity, and Service threads.
This article will not cover the basic use of EventBus, but will focus on the source code and the implementation of EventBus.
2. Source code parsing?
void test() { EventBus.getDefault().register(this); } @Subscribe(threadMode = ThreadMode.MAIN) public void onGetMessage(MessageBean message) { //Processing messages } class MessageBean { int messageId; }
2.1 register
Let's first look at how EventBus was created, EventBus.getDefault()
public static EventBus getDefault() { if (defaultInstance == null) { synchronized (EventBus.class) { if (defaultInstance == null) { defaultInstance = new EventBus(); } } } return defaultInstance; }
getDefault returns an EventBus object for a global singleton. Getting this object then calls the register method to register an object, which can be viewed as an observer.
public void register(Object subscriber) { Class<?> subscriberClass = subscriber.getClass(); //Get all the Subscribe annotated methods inside the subscriber object List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { //Traverse, adding methods to the list for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } }
findSubscriberMethods Get Subscriber Methods by Reflecting or Compiling Note Information
This concept is temporarily understood as a way to get all Subscribe annotations through reflection, and how compile-time annotations are presented in the next article.
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { //Getting it from the cache first improves efficiency List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } // if (ignoreGeneratedIndex) { //Using reflection to get subscriberMethods = findUsingReflection(subscriberClass); } else { //Compile-time annotation generated class acquisition. subscriberMethods = findUsingInfo(subscriberClass); } if (subscriberMethods.isEmpty()) { throw new EventBusException("Subscriber " + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation"); } else { // Save last. That is, a class is only registered for the first time //Get the subscriber method and use the cache when registering again. METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; } }
Here is a brief description of the difference between findUsingReflection and findUsingInfo.
FindUsingReflection mainly reflects back all the methods annotated by Subscribe. Since Java does not provide an api to get all the methods that have this annotation in an object based on the type of annotation, we have to go through all the methods in the object and decide whether or not this method has this annotation individually. This process is time consuming. To solve this problem, EventBus introduces compile-time annotations. If a method is annotated by Subscribe, some corresponding code will be generated during compilation, such as the name of the method, the parameter type, etc. Then all methods annotated by Subscribe can be obtained directly from this information during registration, avoiding the process of reflection and traversal. Save time and improve efficiency, and the compile-time notes on EventBus are freed up for the next article, where only findUsingReflection is analyzed.
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) { //findState is reusable and has a reuse pool. //Think of it as a tool class FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { //Annotate Get Reflection findUsingReflectionInSingleClass(findState);, //Switch to the parent class to find the method annotated by Subscribe within it findState.moveToSuperclass(); } return getMethodsAndRelease(findState); }
This is mainly a while loop in which the object itself begins to iterate up its ancestor classes to get the method annotated by Subscribe.
private void findUsingReflectionInSingleClass(FindState findState) { Method[] methods; try { // This is faster than getMethods, especially when subscribers are fat classes like Activities //Get all methods, including private methods methods = findState.clazz.getDeclaredMethods(); } catch (Throwable th) { // Workaround for java.lang.NoClassDefFoundError, see methods = findState.clazz.getMethods(); findState.skipSuperClasses = true; } for (Method method : methods) { int modifiers = method.getModifiers(); // Subscriber method, must be of type public and cannot be abstract //static method if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { //Get the formal parameters of the method Class<?>[] parameterTypes = method.getParameterTypes(); //There can only be one parameter if (parameterTypes.length == 1) { //Annotations must be used. Previous versions required method names, but now there is no requirement Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); if (subscribeAnnotation != null) { Class<?> eventType = parameterTypes[0]; if (findState.checkAdd(method, eventType)) { ThreadMode threadMode = subscribeAnnotation.threadMode(); findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky())); } } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { // Too many parameters, errors will be made in strict mode, and ignored in non-strict mode. //You can choose what you want 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)) { // Static method, abstract method using Subscribe annotation String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException(methodName + " is a illegal @Subscribe method: must be public, non-static, and non-abstract"); } } }
This is to iterate through all the methods of the class to determine if they have Subscribe annotations, and to check for this method, the first method annotated by Subscribe must be of type public, not abstract or static. On the other hand, the number of formal parameters of the method is checked, where only one parameter is required. If EventBus is running in strict mode and the above conditions do not meet, an EventBusException exception will be thrown.
If everything matches, a SubscriberMethod object is eventually created that contains information about the annotation connection method.
After while traversal, getMethodsAndRelease is called
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) { List<SubscriberMethod> subscriberMethods = new ArrayList<> (findState.subscriberMethods); //Recycling means emptying the map inside, etc. findState.recycle(); //Save it, somewhat like Message, and it can be reused. 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; }
The main purpose here is to recycle the findState object and register all the methods annotated by Subscribe within the class.
Let's go back to the register method
findSubscriberMethods traverses the List after returning
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { // Get parameter type, message type received Class<?> eventType = subscriberMethod.eventType; Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //Determine if this message type has been registered before. CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); if (subscriptions == null) { //If this is the first time you have registered this message type, create a list for this message subscriptions = new CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); } else { //Not going here, } // Insert into the list by priority, //The larger the priority, the more behind it; If the values are the same, sort them in the order they were added 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 all message types that subscriber can receive List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } //Save message type. subscribedEvents.add(eventType); //Sticky message if (subscriberMethod.sticky) { //eventInheritance defaults to true if (eventInheritance) { //stickyEvents, saves historical messages, key is message type, value is last //Messages distributed. 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(); //Distribute sticky messages. checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { //Whether eventInheritance receives historical messages of subtypes or not, //The default is true, and if false, the value receives messages of this type. //For example, charsequence when eventInheritance =true //Can receive messages of type String, false can only receive messages of type charsequence //Subtypes cannot be received. Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } }
Two important objects of the table are involved here
1 subscriptionsByEventType:
The type of subscriptionsByEventType is Map<Class<?>, CopyOnWriteArrayList>, used to hold all message types, the key is the class of the message, the value is a list, this list holds callbacks to receive this message type.
EventBus.getDefault().post(new MessageBean());
When you distribute a message, you can quickly get all the recipients of this message type through subscriptionsByEventType, and then pass the message to the appropriate method, which is described later.
1 typesBySubscriber:
The type of typesBySubscriber is Map<Object, List<Class<?>>, This saves all the types that the registered class can receive.
EventBus.getDefault().unregister(this);
When you unregister, you can quickly get all the message types that the this object can receive, and then delete the corresponding information. Unregister comes later.
Finally, the sticky message is processed, and if it is received, the cached message is passed to the corresponding method.
Last
@Subscribe(threadMode = ThreadMode.MAIN,priority =10 ) public void onGetMessage(MessageBean message) { //Processing messages }
If there are two ways to receive the message Bean, to whom will EventBus give priority when a message arrives? From the source above, you can see who gets the bigger priority first, because the bigger priority is always at the front of the list.
2.1 post
After the registration is complete, you can wait for the message to arrive. Let's see below EventBus How messages are distributed to individual observers.
EventBus.getDefault().post(new MessageBean());
/** Posts the given event to the event bus. */ public void post(Object event) { PostingThreadState postingState = currentPostingThreadState.get(); List<Object> eventQueue = postingState.eventQueue; //Queue messages first eventQueue.add(event); if (!postingState.isPosting) { postingState.isMainThread = isMainThread(); postingState.isPosting = true; if (postingState.canceled) { throw new EventBusException("Internal error. Abort state was not reset"); } try { while (!eventQueue.isEmpty()) { //Start distributing messages postSingleEvent(eventQueue.remove(0), postingState); } } finally { postingState.isPosting = false; postingState.isMainThread = false; } } }
This shows queuing messages. Then call postSingleEvent to distribute the message, a little confused about why.
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<?> eventClass = event.getClass(); boolean subscriptionFound = false; if (eventInheritance) { //lookupAllEventTypes queries for subclasses of eventClass. //For example, post a MainActivity object, the data type received by the recipient's small A registration is Activity. //Since MainActivity inherits Activity, Little A can also receive this message at this time. List<Class<?>> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); for (int h = 0; h < countTypes; h++) { Class<?> clazz = eventTypes.get(h); //Distribute message subscriptionFound |= postSingleEventForEventType(event, postingState, clazz); } } else { //Subtypes of messages are not allowed. subscriptionFound = postSingleEventForEventType(event, postingState, eventClass); } if (!subscriptionFound) { if (logNoSubscriberMessages) { logger.log(Level.FINE, "No subscribers registered for event " + eventClass); } if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) { //Unreceived messages are converted to NoSubscriberEvent-type messages //We can register to listen for messages that nobody receives post(new NoSubscriberEvent(this, event)); } } }
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; synchronized (this) { //subscriptionsByEventType see previous explanation //Get all methods that receive this message subscriptions = subscriptionsByEventType.get(eventClass); } if (subscriptions != null && !subscriptions.isEmpty()) { for (Subscription subscription : subscriptions) { postingState.event = event; postingState.subscription = subscription; boolean aborted = false; try { //Real Start Distributing Messages postToSubscription(subscription, event, postingState.isMainThread); //The user canceled the message aborted = postingState.canceled; } finally { postingState.event = null; postingState.subscription = null; postingState.canceled = false; } if (aborted) { break; } } return true; } return false; }
This is mainly to deal with parent-child type issues, such as small A registration receiving a type of message. When a type two message is thrown, inherited or implemented as one, whether small A can receive messages of a subtype at this time depends on the eventInheritance parameter, which can be set through EventBusBuilder's eventInheritance. The actual distributed message is the invoked postSingleEventForEventType.
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) { case POSTING: //Distribute messages within the same thread invokeSubscriber(subscription, event); break; case MAIN: //Non-android Main Thread if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(subscription, event); } break; case MAIN_ORDERED: //This is the main thread of android. //mainThreadPoster is a handler if (mainThreadPoster != null) { mainThreadPoster.enqueue(subscription, event); } else { // temporary: technically not correct as poster not decoupled from subscriber invokeSubscriber(subscription, event); } break; case BACKGROUND: //Execute on a non-primary thread if (isMainThread) { backgroundPoster.enqueue(subscription, event); } else { invokeSubscriber(subscription, event); } break; case ASYNC: //Opens a subthread execution with a thread pool inside. asyncPoster.enqueue(subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); } }
Distribute messages based on ThreadMode.
EventBus3.0 has the following four ThreadMode s:
POSTING (default): If the thread model is specified as POSTING using an event handler, then in which thread the event was published, the event handler runs in this thread, that is, the publishing and receiving events are on the same thread. Avoid time-consuming operations in event handlers where the threading model is POSTING, as it can block the delivery of events and possibly even cause ANR s.
MAIN: Processing of events is performed in the UI thread. Event processing time cannot be too long, it will be ANR long.
BACKGROUND: If an event is published in a UI thread, the event handler will run in a new thread. If the event was originally published in a child thread, the event handler will execute directly in the thread that published the event. UI update operations are prohibited in this event handler.
ASYNC: The event handler executes in a new child thread regardless of which thread the event is published. Similarly, UI updates are prohibited in this event handler.
We will only analyze the implementation of MAIN here
case MAIN: if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(subscription, event); }
void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); synchronized (this) { //Message Join Queue queue.enqueue(pendingPost); if (!handlerActive) { handlerActive = true; //Handle handles the message if (!sendMessage(obtainMessage())) { throw new EventBusException("Could not send handler message"); } } } }
Let's see how Hnadler handles this message
@Override public void handleMessage(Message msg) { boolean rescheduled = false; try { long started = SystemClock.uptimeMillis(); while (true) { //Get out the message PendingPost pendingPost = queue.poll(); if (pendingPost == null) { synchronized (this) { // Check again, this time in synchronized pendingPost = queue.poll(); if (pendingPost == null) { //No more messages, handlerActive set false handlerActive = false; //No message exited handleMessage return; } } } //Call Subscriber's Method eventBus.invokeSubscriber(pendingPost); long timeInMethod = SystemClock.uptimeMillis() - started; //Processing time has expired, at which point there may be messages that have not been distributed. //This will exit while and wait for the schedule to be rescheduled. if (timeInMethod >= maxMillisInsideHandleMessage) { //Send a message waiting to be scheduled again if (!sendMessage(obtainMessage())) { throw new EventBusException ("Could not send handler message"); } rescheduled = true; //Timed out while waiting to be scheduled again return; } } } finally { handlerActive = rescheduled; } }
It simply takes out the message and calls the subscriber.
More interesting is the processing of message processing timeouts, which has never been used before and has never been introduced in other people's articles.
2.3 Anti-registration
To avoid memory leaks, we usually unregister pages when they are destroyed
EventBus.getDefault().unregister(this);
See the source code for the anti-registration below
public synchronized void unregister(Object subscriber) { List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber); if (subscribedTypes != null) { for (Class<?> eventType : subscribedTypes) { unsubscribeByEventType(subscriber, eventType); } //TypeesBySubscriber is described above typesBySubscriber.remove(subscriber); } else { Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass()); } }
typesBySubscriber saves all message types subscriber subscribes get these message types and call unsubscribeByEventType to unsubscribe
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) { //By type, find all subscribers to this message, that is, the way to receive it 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); //Subscriber's class is subscriber if (subscription.subscriber == subscriber) { subscription.active = false; //Remove Subscriber subscriptions.remove(i); i--; size--; } } } }
The unsubscribeByEventType is the method of finding all received eventType messages within a subscriber and deleting the corresponding subscription. The Subscription class is a wrapper for the method of receiving messages, as described earlier.
All Subscription s are eventually removed through the for loop inside unregister.
summary
EventBus source analysis is now complete, and one of the remaining issues is the compile-time annotations mentioned in this article. EventBus avoids reflection time by compile-time annotations and improves the efficiency of EventBus execution. In the next article, we'll cover compile-time annotations, followed by EventBus's compile-time annotations and their dynamically generated code.