EventBus source code parsing (3)-registration function implementation method, eventbus source code
1. Registration Process
The registration code of EventBus is as follows:
public void register(Object subscriber) { Class
subscriberClass = subscriber.getClass(); List
subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } }
The register method mainly involves three tasks:
Obtains the class Object of the subscriber class and finds all the subscription methods of the corresponding subscriber class based on the class object.
Ii. SubscriberMethodFinder
When searching for a subscription method, the findSubscriberMethods method of the SubscriberMethodFinder class is called and the class Object of the subscriber class is passed as a real parameter. So what is SubscriberMethodFinder?
The SubscriberMethodFinder class, as its name implies, is a subscriber method finder. There is a very important member variable METHOD_CACHE in it, that is, the subscriber method cache. It is a thread-safe HashMap, and the key is the class Object of the subscriber class, which is unique; the value is a set of subscription methods (methods with @ Subscribe annotation added.
private static final Map
, List
> METHOD_CACHE = new ConcurrentHashMap<>();
When searching the subscription method of the corresponding subscriber class, the following logic is executed:
List
findSubscriberMethods(Class
subscriberClass) { List
subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } 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 { METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; } }
The logic of this part of the code is clear, and the steps are as follows:
Find the corresponding collection of subscription methods in the METHOD_CACHE cache based on the subscriber class object. If the collection of subscription methods is not empty, return directly; otherwise, continue to run down. If the index technology is not used during compilation, jump to findUsingReflection, use reflection lookup if index technology is used during compilation, jump to findUsingInfo and use index lookup to save the searched set to METHOD_CACHE.
For findUsingReflection and findUsingInfo, do not go deep into this chapter. Our main line is still registration.
Iii. SubscriberMethod
So what is the SubscriberMethod? In fact, it corresponds to the public method marked with the @ Subscribe annotation in the code.
public class SubscriberMethod { final Method method; final ThreadMode threadMode; final Class
eventType; final int priority; final boolean sticky; ...}
Let's look at the general method of subscription when using EventBus, such as the following:
@Subscribe(threadMode = ThreadMode.MainThread,sticky=true)public void subscribe(Event event) {}
Corresponding to the SubscriberMethod Member, isn't it a single commit? Method indicates the subscription method, threadMode indicates the execution thread of the subscription method, eventType indicates the event type, priority indicates the event priority, and sticky indicates whether the event is sticky.
4. Subscription
Return to the first section. After finding the set of subscription methods, you need to traverse the set and subscribe each subscribe and subscription method.
Subscribe has a lot of code logic, but it is also very clear. We split this method completely (without omitting any Code) into three small methods for analysis in sequence. That is:
// This code is pseudocode private void subscribe (Object subscriber, SubscriberMethod subscriberMethod) {subscribe1 (subscriber, subscriberMethod); subscription (subscriber, subscriberMethod); subscription (subscriber, subscriberMethod );}
4.1
private void subscribe1(Object subscriber, SubscriberMethod subscriberMethod) { Class
eventType = subscriberMethod.eventType; Subscription newSubscription = new Subscription(subscriber, subscriberMethod); CopyOnWriteArrayList
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; } }}
Before analyzing this part of the code, we must first understand the subscribe class and subscriptionsByEventType variables.
4.1.1 subtasks
The internal structure of Subscribe is as follows:
final Object subscriber; final SubscriberMethod subscriberMethod; volatile boolean active;
This class is similar to the METHOD_CACHE mentioned in the second section, but it is different. METHOD_CACHE records all the Subscription methods in the subscriber class, which is a one-to-many relationship. The Subscriber records a specific Subscription method of the subscriber class, which is a one-to-one relationship. The active Boolean value in this class is set to false when the registration is canceled. We can understand this class as subscription information.
4.1.2 subscriptionsByEventType
SubscriptionsByEventType is mentioned in the concluding remarks in the second article of this series, but it is not discussed in detail. SubscriptionsByEventType is actually a data structure that stores the subscriber and subscription method information based on the event type. The definition of subscriptionsByEventType in the EventBus class is as follows:
Map
, CopyOnWriteArrayList
> subscriptionsByEventType;
SubscriptionsByEventType is also a Map structure. The key is a class Object of the event type, and the value is a set of thread-safe subscription information. Some readers may ask: why is the key a class Object of the "event type" instead of another? We can infer from the following code of subscribe1 (more intuitive, you can also know through the name ):
CopyOnWriteArrayList
subscriptions = subscriptionsByEventType.get(eventType);
No. The real parameters in the get method are class objects of the event type, and the above conclusions are drawn.
4.1.3 subscribe1 Process Analysis
After analyzing subscribe1 and subscriptionsByEventType, let's sort out the code logic of subscribe1:
Package the subscriber and a specific subscription method into a new subscription information object newsubcategory. Based on the event type, obtain the subscription information set subscriptions from subscriptionsByEventType. If the subscription information set is null, this is the first time you subscribe to this type of event. You can directly create an empty collection of subscription information and store it in subscriptionsByEventType. If the collection of subscription information is not null, it indicates that you have subscribed to this type of event before, determine the contains of the subscription information set. If newsubscribe exists in this set, it means that the subscriber has subscribed to the same type of events repeatedly. if an exception is thrown, newsubscribe information is saved to subscriptions of the subscription information set according to the priority.
Some readers may have doubts that newsubscribe is clearly new every time, and the object address is bound to be different. In this way, will not the subscriptions. contains (newsubtions) always return false? So that we will never throw this exception? However, in practice, many readers have encountered this exception.
The reason why EventBus dared to write this code is that it overwrites the subscriber's equals and hashCode methods. Interested readers can read it on their own.
4.2
private void subscribe2(Object subscriber, SubscriberMethod subscriberMethod) { List
> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType);}
By convention, we still need to understand the role of the typesBySubscriber variable. TypesBySubscriber is also a key variable mentioned in the concluding remarks in Chapter 2 of this series. Its definition in EventBus is as follows:
private final Map
>> typesBySubscriber;
TypesBySubscriber is also a Map structure. The key is the class Object of the subscriber class, and the value is a collection of subscribed event types of the subscriber class.
After knowing the role of this variable, the logic of subscribe2 is easy to understand.
Based on the subscriber, subscribedEvents of the subscribed event type set is retrieved from the typesBySubscriber set. If subscribedEvents is null, no events have been subscribed. Create an empty collection of event types and store the class Object of the event type in typesBySubscriber to the collection of event types.
4.3
private void subscribe3(Object subscriber, SubscriberMethod subscriberMethod) {if (subscriberMethod.sticky) { if (eventInheritance) { Set
, Object>> entries = stickyEvents.entrySet(); for (Map.Entry
, Object> entry : entries) { Class
candidateEventType = entry.getKey(); if (eventType.isAssignableFrom(candidateEventType)) { Object stickyEvent = entry.getValue(); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } }}
Subscribe3 is mainly used to pre-process sticky events. Here, we also need to know about the stickyEvents variable. It is also a Map structure. The key is a class Object of the event type, and the value is a specific viscous event.
private final Map
, Object> stickyEvents;
The execution logic of subscribe3 is as follows:
If the event type is inherited (the default configuration is inherited), traverse the event itself and its superclasses and execute checkpoststickyeventtosubcategory. If the event type is not inherited, run checkpoststickyeventtosubcategory directly.
So what operations does checkpoststickyeventtosubtasks perform?
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) { if (stickyEvent != null) { postToSubscription(newSubscription, stickyEvent, isMainThread()); } }private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { 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); } }
As you can see, the checkpoststickyeventtosubmode internally calls the posttosubmode method, which executes different operations according to different threadmodes.
If the thread mode is POSTING, The subscribe method is called directly through reflection invoke. If the thread mode is MAIN and the current subscription method is in the MAIN thread, the same operation is performed, otherwise, use mainThreadPoster (HandlerPoster type) to switch to the main thread to execute the subscription method. If the thread mode is MAIN_ORDERED, execute the opposite operation of 2. If the thread mode is BACKGROUND and the subscription method is defined in the main thread, the subscription method is asynchronously executed through backgroundPoster. Otherwise, the subscription method is directly executed through invoke. If the thread mode is ASYNC, the subscription method is asynchronously executed through asyncPoster.
From the above analysis, we can see that in the event of a sticky event, once the subscriber registers with EventBus, EventBus will immediately or indirectly process the subscription method of the sticky event. The specific processing is implemented by several Poster implementations such as mainThreadPoster, backgroundPoster, and asyncPoster. These Poster will be analyzed in detail in subsequent chapters, which will be skipped for the moment.
Conclusion 4.4
In summary, the subscribe method mainly performs the following operations:
Based on the event type, newsubscribe information is saved to subscriptions of the subscription information set according to the priority. Based on the class Object of the subscriber class, the subscriber subscribes to the event type and executes the subscription method corresponding to the viscous event.
V. Conclusion
This chapter analyzes the subscriber registration process of EventBus. Through the process analysis, we can know that when there is a subscription method with the same method name and the same event type in the same subscription class, eventBus throws the following exception:
"Subscriber " + subscriber.getClass() + " already registered to event " + eventType
For a sticky event, EventBus directly or indirectly executes its corresponding subscription method at the end of completing subscriber registration. This is why a sticky event is executed.