A brief introduction to EventBus
EventBus is pretty familiar, but I can’t say more about it. Usually we call it an event bus, but it’s more like a broadcast, observer mode, where one side sends a message and two sides receive it. In creating a subscription for EventBus, the most important thing is that there are two key maps that store the subscription methods and classes that we define.
The following code is based on EventBus3.1.1
Create and subscribe messages
registered
Signing up for EventBus is simple,
@Override
public void onStart(a) {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop(a) {
super.onStop();
EventBus.getDefault().unregister(this);
}
Copy the code
It is important to remember to log out in onStop to avoid being associated with EventBus after the Activity is closed and causing a memory leak.
Let’s go to the method and have a look:
The singleton pattern
EventBus.getDefault
public static EventBus getDefault(a) {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = newEventBus(); }}}return defaultInstance;
}
Copy the code
This method is very simple, just double check singleton, continue to look at register method:
The register method
EventBus.register
public void register(Object subscriber) { Class<? > subscriberClass = subscriber.getClass();// The registered class
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);// Find the subscription method
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {// Loop through all the methods foundsubscribe(subscriber, subscriberMethod); }}}Copy the code
In register, the parameter passed is called subscriber, which means subscriber, and of type Object. So inside this method there’s a List of lists, so what’s the SubscriberMethod?
public class SubscriberMethod {
final Method method;// The corresponding method
final ThreadMode threadMode;// Thread mode
finalClass<? > eventType;// Message type
final int priority;/ / priority
final boolean sticky;// Whether stickiness is supported
/** Used for efficient comparison */
String methodString;// For equal comparison
}
Copy the code
In fact, this class holds all the information in the methods defined in our subscriber.
The threaded
ThreadMode is defined as follows:
-
Threadmode. POSTING: The default thread schema, where events are sent by one thread and events are processed by the corresponding thread, prevents thread switching.
-
Threadmode. MAIN: If an event is sent on the MAIN thread (UI thread), the event is handled directly on the MAIN thread; If an event is sent in a child thread, it is first queued and then switched to the main thread by Handler, which processes the event in turn.
-
Threadmode. MAIN_ORDERED: Regardless of which thread sends the event, the event is first queued and then switched by Handler to the main thread, which processes the event in turn.
-
Threadmode. BACKGROUND: If an event is sent on the main thread, the event is queued first and then processed through the thread pool. If an event is sent in a child thread, the event is processed directly in the thread that sent the event.
-
Threadmode. ASYNC: Regardless of which thread sends the event, the event is queued and processed through the thread pool.
So this list in the Register method is basically our current subscriber class that contains all the receiving methods. Here by subscriberMethodFinder. FindSubscriberMethods (subscriberClass) method to obtain:
Find a way to subscribe to events (a way to receive messages)
SubscriberMethodFinder findSubscriberMethods
private static finalMap<Class<? >, List<SubscriberMethod>> METHOD_CACHE =new ConcurrentHashMap<>();
List<SubscriberMethod> findSubscriberMethods(Class
subscriberClass) {
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);// Get it from the cache first
if(subscriberMethods ! =null) {
return subscriberMethods;// If there is a cache, return it directly
}
if (ignoreGeneratedIndex) {// Whether to ignore annotations
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);// Add to the cache
returnsubscriberMethods; }}Copy the code
Here we notice that there is an ignoreGeneratedIndex parameter, the value of which is obtained in the constructor
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex);
Copy the code
/** Forces the use of reflection even if there's a generated index (default: false). */
public EventBusBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex) {
this.ignoreGeneratedIndex = ignoreGeneratedIndex;
return this;
}
Copy the code
As you can see from the official comment, the default value is false, so the findUsingInfo method is called:
private List<SubscriberMethod> findUsingInfo(Class
subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);// Initialize the lookup helper class
while(findState.clazz ! =null) {// Here is our register class
findState.subscriberInfo = getSubscriberInfo(findState);
if(findState.subscriberInfo ! =null) {// The initial state is empty, so else is executed
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if(findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); }}}else {
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();// Find the parent class
}
return getMethodsAndRelease(findState);// Release resources
}
Copy the code
FindState is a lookup helper class that stores information about found subscription methods and related classes:
static class FindState {
final List<SubscriberMethod> subscriberMethods = new ArrayList<>();// The subscription method
final Map<Class, Object> anyMethodByEventType = new HashMap<>();// Get methods by message type
final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();// Get the subscribed class through the method
final StringBuilder methodKeyBuilder = new StringBuilder(128); Class<? > subscriberClass; Class<? > clazz;boolean skipSuperClasses;
SubscriberInfo subscriberInfo;
}
Copy the code
Here will perform the else inside findUsingReflectionInSingleClass method, through the reflection to find subscription method
SubscriberMethodFinder findUsingReflectionInSingleClass
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// 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;
}
// Loop through all methods
for (Method method : methods) {
int modifiers = method.getModifiers();// Get the modifier
if((modifiers & Modifier.PUBLIC) ! =0 && (modifiers & MODIFIERS_IGNORE) == 0) {//public staticClass<? >[] parameterTypes = method.getParameterTypes();// Get the parameter type
if (parameterTypes.length == 1) {// There can be only one argument
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);// Get the Subscribe comment
if(subscribeAnnotation ! =null) { Class<? > eventType = parameterTypes[0];// Get the parameter type, that is, the type of our custom message
if (findState.checkAdd(method, eventType)) {// Whether this method has been added
ThreadMode threadMode = subscribeAnnotation.threadMode();// Get the thread mode
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));// Add to array}}}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"); }}}Copy the code
The whole process is to go through all the methods, judge the method with a Subscribe annotation, and then add the method information to the array. Once you’ve done the lookup, go back to the Register method
public void register(Object subscriber) { Class<? > subscriberClass = subscriber.getClass(); List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);synchronized (this) {
for(SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); }}}Copy the code
Circulation subscription
Get the array of all the subscription methods we just had, and then execute the subscribe method in the loop, which performs the subscribe operation:
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<? > eventType = subscriberMethod.eventType;// Get our custom message type
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);// Create a new subscription class that contains the subscription class and method properties
/ / subscriptionsByEventType is key, eventType Subscription array for the value of the map
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {// Create an array if it is empty and add it to the map
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {// Subscribe already, throw exception
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) {// Add the subscription method with higher priority to the array
subscriptions.add(i, newSubscription);
break; }}//typesBySubscriber is a map whose object is the key and the array of subscribed methods is the valueList<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
// Sticky event correlation
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 should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).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(); checkPostStickyEventToSubscription(newSubscription, stickyEvent); }}}else{ Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); }}}Copy the code
After the
EventBus’s register method is a simple, two-step process:
- We use a Subscribe method with the Subscribe annotation by reflection lookup
- Loop through the found methods and add them to the two key maps (subscriptionsByEventType and typesBySubscriber)