Analysis of the version
Implementation 'org. Greenrobot: eventbus: 3.2.0'Copy the code
The basic use
The first step
Defines a class to host the information
data class MessageEvent (val age:String,val name:String)
Copy the code
The second step
Register in the onCreate of the Actvit or Fragment receiving the information
EventBus.getDefault().register(this)
Copy the code
Deregister in onDestroy
EventBus.getDefault().unregister(this)
Copy the code
And define a receive method
@Subscribe(threadMode = ThreadMode.MAIN) fun onMessageEvent(event: MessageEvent?) { Log.d("EventBus","${event? .name}") }Copy the code
The third step
In the class that sent the message
val messageEvent = MessageEvent("10", "Bob")
EventBus.getDefault().post(messageEvent)
Copy the code
complete
The principle of analysis
register
Let’s start with registration
EventBus.getDefault().register(this)
Copy the code
Let’s skip the getDefault method, which is a singleton that returns an EventBus object, and look at 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
Although they look a few lines of code to its associated information is quite rich, first to see subscriberMethodFinder. FindSubscriberMethods (subscriberClass), Its main function is to register the class to receive the event method through reflection to obtain its parameter type, thread mode, priority and so on and save.
List<SubscriberMethod> findSubscriberMethods(Class<? > subscriberClass) { List<SubscriberMethod> 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; }}Copy the code
At the beginning, determine whether there is a list of SubscriberMethod that has been saved. If there is, the SubscriberMethod list is returned directly; if there is no, the execution goes down. IgnoreGeneratedIndex default construction parameter is fasle, so next look at the findUsingInfo method, as follows
private List<SubscriberMethod> findUsingInfo(Class<? > subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz ! = null) { findState.subscriberInfo = getSubscriberInfo(findState); if (findState.subscriberInfo ! = null) { 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(); } return getMethodsAndRelease(findState); }Copy the code
So the first thing we’re going to do is get the FindState object from the prepareFindState method, so I’m not going to stick to this code because it’s relatively simple. The initForSubscriber method is called to store the class object of the registered class and initialize some of its variables. findState.subscriberInfo = getSubscriberInfo(findState); The first time that EventBus is called, and the subscriberInfo is null, so we’ll skip this and go back to it. Next is findUsingReflectionInSingleClass (findState), first introduce simple findState object, take a look at his member variables. It is a static inner class of SubscriberMethodFinder.
static class FindState { final List<SubscriberMethod> subscriberMethods = new ArrayList<>(); final Map<Class, Object> anyMethodByEventType = new HashMap<>(); final Map<String, Class> subscriberClassByMethodKey = new HashMap<>(); final StringBuilder methodKeyBuilder = new StringBuilder(128); Class<? > subscriberClass; Class<? > clazz; boolean skipSuperClasses; SubscriberInfo subscriberInfo;Copy the code
See findUsingReflectionInSingleClass method, he is a main method of reflection process
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 try { methods = findState.clazz.getMethods(); } catch (LinkageError error) { // super class of NoClassDefFoundError to be a bit more broad... String msg = "Could not inspect methods of " + findState.clazz.getName(); if (ignoreGeneratedIndex) { msg += ". Please consider using EventBus annotation processor to avoid reflection."; } else { msg += ". Please make this class visible to EventBus annotation processor to avoid reflection."; } throw new EventBusException(msg, error); } findState.skipSuperClasses = true; } for (Method method : methods) { int modifiers = method.getModifiers(); if ((modifiers & Modifier.PUBLIC) ! = 0 && (modifiers & MODIFIERS_IGNORE) == 0) { Class<? >[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1) { 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)) { 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
First, we reflect the Methods by registering the Class object of the Class. Then we iterate to determine whether the Int value of the method modifier satisfies the running condition. If it does, we get the parameter type of the method. Look at the findState.checkAdd Method to determine whether the same Method has been stored.
boolean checkAdd(Method method, Class<? > eventType) { // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required. // Usually a subscriber doesn't have methods listening to the same event type. Object existing = anyMethodByEventType.put(eventType, method); if (existing == null) { return true; } else { if (existing instanceof Method) { if (! checkAddWithMethodSignature((Method) existing, eventType)) { // Paranoia check throw new IllegalStateException(); } // Put any non-Method object to "consume" the existing Method anyMethodByEventType.put(eventType, this); } return checkAddWithMethodSignature(method, eventType); }}Copy the code
CheckAddWithMethodSignature ()
private boolean checkAddWithMethodSignature(Method method, Class<? > eventType) { methodKeyBuilder.setLength(0); methodKeyBuilder.append(method.getName()); methodKeyBuilder.append('>').append(eventType.getName()); String methodKey = methodKeyBuilder.toString(); Class<? > methodClass = method.getDeclaringClass(); Class<? > methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass); if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) { // Only add if not already found in a sub class return true; } else { // Revert the put, old class is further down the class hierarchy subscriberClassByMethodKey.put(methodKey, methodClassOld); return false; }}Copy the code
These two methods are mainly deposited into the new Method anyMethodByEventType variables and in anyMethodByEventType variables are of the same key replace Method into subscriberClassByMethodKey variable. It doesn’t matter if you don’t understand EventBus. You can just assume that it returns true by default. The next step is to get the thread pattern, priority, and Method and eventType parameter types from the annotation object and wrap them into a SubscriberMethod object that is stored in the list of the FindState object. See here first to tidy up your thoughts, as shown in the figure //TODO placeholder ~~~~ (first empty back I add, this mapping software is not very good)
Next look at the Subscribe (Subscriber, subscriberMethod) method in the Register method:
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<? > eventType = subscriberMethod.eventType; Subscription newSubscription = new Subscription(subscriber, subscriberMethod); CopyOnWriteArrayList<Subscription> 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; } } List<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); 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
First, the subscriberMethod parameter type is obtained from the wrapped subscriberMethod object, and the subscriber and subscriberMethod registered objects are wrapped into the Subscription object. The Subscription class is fairly simple and it stores these two variables and a few more comparison functions and doesn’t post code. SubscriptionsByEventType. Get (eventType) its generic Map < Class
, CopyOnWriteArrayList> subscriptionsByEventType. This variable is important to notice. It is initialized in the constructor of EventBus. If the List obtained from this Map is Null, a new List will be created (CopyOnWriteArrayList is a thread-safe List, don’t worry too much about it). If not Null, the wrapped newSubscription is added to the List based on index and priority.
typesBySubscriber.get(subscriber)
Its generic types Map<Object, List<Class<? >>> typesBySubscriber, which is also initialized in the constructor. The Map stores the key value of the registered object, and the value is the value of the registered Class. The Class that receives the parameters of the event method is the generic List. The rest of the code is the sticky event handling for EventBus, so I’m not going to talk about this for a second and then I’m going to talk about the post code.
post
Eventbus.getdefault ().post(messageEvent)
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
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()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
Copy the code
First look at the currentPostingThreadState. The get ()
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() { @Override protected PostingThreadState initialValue() { return new PostingThreadState(); }};Copy the code
ThreadLocal is a mechanism to avoid thread collisions, simply by setting a separate variable for each thread. I have found a link for you to refer to ThreadLocal. Its generic type is PostingThreadState, and one of the static inner classes of EventBus is relatively simple, so I won’t go into it.
final static class PostingThreadState {
final List<Object> eventQueue = new ArrayList<>();
boolean isPosting;
boolean isMainThread;
Subscription subscription;
Object event;
boolean canceled;
}
Copy the code
In the post() method, the previous stuff is pretty straightforward and WITHOUT further explanation, start with a postSingleEvent(EventQueue.remove (0), postingState)
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<? > eventClass = event.getClass(); boolean subscriptionFound = false; if (eventInheritance) { List<Class<? >> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); for (int h = 0; h < countTypes; h++) { Class<? > clazz = eventTypes.get(h); subscriptionFound |= postSingleEventForEventType(event, postingState, clazz); } } else { 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) { post(new NoSubscriberEvent(this, event)); }}}Copy the code
Eventqueue.remove (0) removes the first Event to be sent. The return value is the removed Event. EventInheritance with its default value of true goes into the lookupAllEventTypes(eventClass) method.
private static List<Class<? >> lookupAllEventTypes(Class<? > eventClass) { synchronized (eventTypesCache) { List<Class<? >> eventTypes = eventTypesCache.get(eventClass); if (eventTypes == null) { eventTypes = new ArrayList<>(); Class<? > clazz = eventClass; while (clazz ! = null) { eventTypes.add(clazz); addInterfaces(eventTypes, clazz.getInterfaces()); clazz = clazz.getSuperclass(); } eventTypesCache.put(eventClass, eventTypes); } return eventTypes; }}Copy the code
eventTypesCache.get(eventClass) private static final Map
> eventTypesCache = new HashMap<>(); Key is the Class object of the Event that was put in, and value is a generic Class List. So what this List puts in is the Class object of this Enevnt and the Class object of its base Class and the interface that it implements and the Class object of the base Class of that interface. addInterfaces(eventTypes, clazz.getInterfaces()); Add the Class of the interface and its base Class to the List.
static void addInterfaces(List<Class<? >> eventTypes, Class<? >[] interfaces) { for (Class<? > interfaceClass : interfaces) { if (! eventTypes.contains(interfaceClass)) { eventTypes.add(interfaceClass); addInterfaces(eventTypes, interfaceClass.getInterfaces()); }}}Copy the code
From after lookupAllEventTypes returns a list of open loop call postSingleEventForEventType (event, postingState clazz), into the cords respectively introduced from Post event events, PostingState object, and class object. The Class is the Class object of the event event the first time through.
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<? > eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; synchronized (this) { subscriptions = subscriptionsByEventType.get(eventClass); } if (subscriptions ! = null && ! subscriptions.isEmpty()) { for (Subscription subscription : subscriptions) { postingState.event = event; postingState.subscription = subscription; boolean aborted; try { postToSubscription(subscription, event, postingState.isMainThread); aborted = postingState.canceled; } finally { postingState.event = null; postingState.subscription = null; postingState.canceled = false; } if (aborted) { break; } } return true; } return false; }Copy the code
Okay, so that’s the important thing, remember this variable subscriptionsByEventType, the key value of this variable in the register process is stored in the Class that receives the input to the event method, The vlaue value is a List that generically wraps the properties of the methods that register class objects and receive events. So here postSingleEventForEventType method in the incoming class take out the corresponding registration in the variable in the class receives the event method, through reflection to invoke this method receive events and event into the can communication.
See next postToSubscription (subscription, event, postingState isMainThread) method, the first parameter contains the registered class objects and receive events method of various attributes, the second parameter to pass the event event, The third determines whether it is the main thread
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);
}
}
Copy the code
We execute the method by determining the pattern of the thread receiving the event. We first look at the invokeSubscriber(Subscription, event) call to the Main thread.
void invokeSubscriber(Subscription subscription, Object event) { try { subscription.subscriberMethod.method.invoke(subscription.subscriber, event); } catch (InvocationTargetException e) { handleSubscriberException(subscription, event, e.getCause()); } catch (IllegalAccessException e) { throw new IllegalStateException("Unexpected exception", e); }}Copy the code
It’s easy to call the method directly through reflection and pass in the event parameter.
MainThreadPoster. Enqueue (Subscription, Event)