Enter today’s topic with a question
  1. What are the functions and benefits of EventBus?
  2. How does EventBus communicate between components?

EventBus Github: github.com/greenrobot/…

Simple use of EventBus:
1, add dependent on implementation 'org. Greenrobot: eventbus: 3.2.0' 2, registered subscription eventbus. GetDefault (). The register (this); 3. Implement the Subscribe method. Since 3.0, replace the onEvent method with the Subscribe annotation. Subscribe(threadMode = threadmode.main) public void onMessageEvent(MessageEvent event) {Subscribe(threadMode = threadmode.main) public void onMessageEvent(MessageEvent event) { Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show(); } eventbus.getDefault ().unregister(this);Copy the code

Full source demo:

public class MainActivity extends AppCompatActivity { private TextView tvMsg; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvMsg = findViewById(R.id.tv_msg); Eventbus.getdefault ().register(this); } // Implement the subscription method, Subscribe(threadMode = threadmode.main) public void since 3.0, use the Subscribe annotation instead of the onEvent method onMessageEvent(String msg) { tvMsg.setText("onMessageEvent1:" + msg); } @Subscribe public void onMessageEvent(Event event) { tvMsg.setText("onMessageEvent2:" + event.toString()); } @Override protected void onDestroy() { super.onDestroy(); Eventbus.getdefault ().unregister(this); }Copy the code
Eventbus.getdefault ().post(" If you're using EventBus for the first time, pay attention!" );Copy the code
Design ideas

EventBus is an Android publish-subscribe EventBus framework that separates the receiver and sender of an event, simplifying communication between components and making it easy to use and execute efficiently.

Publiser sends events via post() to EventBus, which acts as a distributor or scheduler to inform the large Subcriber(subscribers) of the event.

Just copy someone’s map and don’t bother to do it yourself

Source code analysis:

EventBus.getDefault().register(this);

//getDefault() is a singleton method that ensures that there is currently only one EventBus instance:  public static EventBus getDefault() { if (defaultInstance == null) { synchronized (EventBus.class) { if (defaultInstance == null) { defaultInstance = new EventBus(); } } } return defaultInstance; }Copy the code

You can see that the register() method is divided into two parts: search and registration. First, look at the search process, starting with findSubscriberMethods()

Public void register(Object subscriber) {// Obtain the Class Object <? > subscriberClass = subscriber.getClass(); // Find the set of methods in the current Class that Subscribe to events according to Class (methods with a Subscribe annotation, public modifier, one argument) // The SubscriberMethod class mainly encapsulates the relevant information (Method object, thread mode, event type and priority) of the qualified methods subscriberMethodFinder.findSubscriberMethods(subscriberClass); Synchronized (this) {// loop through the collection of methods subscribed to the event to complete the registration for (SubscriberMethod SubscriberMethod: subscriberMethods) { subscribe(subscriber, subscriberMethod); }}}Copy the code

FindSubscriberMethods () looks in the cache first and returns directly if found, with no findUsingInfo() methods called.

List<SubscriberMethod> findSubscriberMethods(Class<? > subscriberClass) {// METHOD_CACHE is a ConcurrentHashMap that stores a collection of subscriberClass and corresponding SubscriberMethod to improve registration efficiency and prevent repeated searches. List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods ! = null) { return subscriberMethods; } // Since the default EventBusBuilder is used, the default is false, If (ignoreGeneratedIndex) {subscriberMethods = findUsingReflection(subscriberClass); } else { subscriberMethods = findUsingInfo(subscriberClass); } // If there is no method in the corresponding class, Will throw an exception if (subscriberMethods isEmpty ()) {throw new EventBusException subscriberClass (" Subscriber "+ +" and its super classes have no public methods with the @Subscribe annotation"); } else {// save the found List<SucribeMethod> collection to the cache. return subscriberMethods; }}Copy the code

The findUsingInfo() method loops through the class, parent, parent…….. Call findUsingReflectionInSingleClass (found) to subscribe to events

private List<SubscriberMethod> findUsingInfo(Class<? > subscriberClass) { FindState findState = prepareFindState(); / / FindState to save some data to find and state FindState initForSubscriber (subscriberClass); // Findstate. clazz is the subscriberClass while (findstate. clazz! = null) { findState.subscriberInfo = getSubscriberInfo(findState); EventBusIndex returns null 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 {/ / subscription via the reflection events of method findUsingReflectionInSingleClass (findState); } / / modify findState. Clazz for subscriberClass parent Class, which need to traverse the superclass findState. MoveToSuperclass (); } // The found methods are stored in the subscriberMethods collection of the FindState instance. Return getMethodsAndRelease(findState); return getMethodsAndRelease(findState); return getMethodsAndRelease(findState); }Copy the code

FindUsingReflectionInSingleClass () by the method of reflection find all eligible subscription, reflection and comments don’t know can go to baidu catch up with it!

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; } 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) {// Get the type of this parameter Class<? > eventType = parameterTypes[0]; The checkAdd() method is used to check whether the FindState anyMethodByEventType map has been added with the current eventType as the key pair. Never add returns true if (findState checkAdd (method, eventType)) {ThreadMode ThreadMode = subscribeAnnotation. ThreadMode (); // Create a SubscriberMethod object, And added to the collection subscriberMethods findState. SubscriberMethods. Add (new SubscriberMethod (method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky())); } } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {... } / / throw an exception} else if (strictMethodVerification && method. The isAnnotationPresent (Subscribe. Class)) {... }// throw an exception}}Copy the code

The findSubscriberMethods() process in this Register () method completes the analysis, and we have found the collection of methods that subscribe to events in the currently registered class and its parent class. Next, analyze the specific registration process, namely the subscribe() method in register() :

Private void subscribe(Object subscriber, SubscriberMethod SubscriberMethod) {// Get the parameter type of the method currently subscribed to the event Class<? > eventType = subscriberMethod.eventType; // Subscription class holds the class object to be registered as well as the current subscriberMethod Subscription newSubscription = newSubscription(subscriber,subscriberMethod); CopyOnWriteArrayList<Subscription> subscriptions=subscriptionsByEventType.get(eventType); If (subscriptions == null) {// If it does not exist, create a subscriptions, Save to subscriptionsByEventType Subscriptions = new CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); } else { if (subscriptions.contains(newSubscription)) { throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } // Add newSubscription object to subscriptions int size = subscription.size (); for (int i = 0; i <= size; i++) { if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) { subscriptions.add(i, newSubscription); break; }} // Check whether there is a corresponding set of parameter types List<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber); // Create a subscribedEvents and save it to typesBySubscriber if (subscribedEvents == null) {subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } // Save the parameter type of the method that is currently subscribed to the event subscribedevents.add (eventType); // Sticky event-related if (subscribermethod.sticky) {... }}Copy the code

The subscribe() method obtains the subscriptionsByEventType and typesBySubscriber hashmaps. SubscriptionsByEventType is used when sending events to complete the processing of events. When canceling the registration, typesBySubscriber and subscriptionsByEventType are used to release related resources.

private final Map<Class<? >, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType; private final Map<Object, List<Class<? >>> typesBySubscriber; What exactly does subscriptionsByEventType contain? Give me an example. CopyOnWriteArrayList<Subscription> listC; listC.add(new Subscription(UserA, UserAMethod)) listC.add(new Subscription(UserA, UserAMethod2)) listC.add(new Subscription(UserB, UserBMethod) subscriptionsByEventType. Put (String class, listC) UserAMethod, UserAMethod2 subscription in UserA parameters of type String methods in class Subscribe(threadMode = threadmode.main) public void @subscribe (threadMode = threadmode.main) public void onMessageEvent(String msg) { tvMsg.setText("onMessageEvent1:" + msg); } There may be many other types besides String:  subscriptionsByEventType.put(Interger.class,listx) subscriptionsByEventType.put(MyLoginEvent.class,listxx) SubscriptionsByEventType. Put (MyLogin2Event. Class, listxxx) and so on What to store so much? EventBus is a dispenser or scheduler that notifysubcribers of events by parameter type. The dispenser notifysubcribers of all subcribers of the same parameter type, that's all! What's in typesBySubscriber? UserA,UserB have a subscription method of type String List<Class<? >> list list.add(usera.class) list.add(userb.class) typesBySubscriber. Put (stirng. class,list) typesBySubscriber. TypesBySubscriber and subscriptionsByEventType are used to cancel registrationCopy the code

The post() method is easy to understand if you know what subscriptionsByEventType and typesBySubscriber maps store

Eventbus.getdefault ().post(” I am a chestnut “)

Public void post (Object event) {/ / currentPostingThreadState is a ThreadLocal PostingThreadState type / / PostingThreadState class holds the event queue and the information such as thread model (which can be understood as the implementation of the post secondary class) PostingThreadState postingState = currentPostingThreadState. The get (); List<Object> eventQueue = postingState.eventQueue; // Add events to the event queue eventqueue.add (event); // isPosting defaults to false if (! Whether postingState. IsPosting) {/ / give priority to the thread postingState. IsMainThread = isMainThread (); postingState.isPosting = true; if (postingState.canceled) { throw new EventBusException("Internal error. Abort state was not reset"); } try {// traverse the event queue while (! Eventqueue.isempty ()) {// Send a single event // eventQueue.remove(0), Remove postSingleEvent(eventQueue.remove(0), postingState); } } finally { postingState.isPosting = false; postingState.isMainThread = false; }}}Copy the code

The post() method does a queue of events that are sent and then loops out of the queue to the postSingleEvent() method for processing. Let’s go straight to the postSingleEvent() method

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<? > eventClass = event.getClass(); // Get the class object for this event type Boolean subscriptionFound = false; If (eventInheritance) {// Search for the Class of the current event type and save it with the Class object of the current event type into the collection 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

PostSingleEvent () method, according to the eventInheritance properties, decide whether to traverse upward parent types of events, and then use postSingleEventForEventType further processing events () method. Not too well understood We only directly into postSingleEventForEventType () method:

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; / / record events postingState. Subscription = subscription; // Record the subscription Boolean aborted = false; Try {/ / final event handling 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

The register () storage subscriptionsByEventType map is in postSingleEventForEventType () method is used. The Subscription collection for the sent event type is iterated, and the postToSubscription() method is called to handle the event.

private void postToSubscription(Subscription subscription, Object event, Boolean isMainThread) {/ / determine the subscription event method thread mode switch (subscription. SubscriberMethod. ThreadMode) {/ / the default mode of thread, Case POSTING: invokeSubscriber(subscription, event); break; If (isMainThread) {invokeSubscriber(subscription, event); } else {// If the event is sent in the child thread, the event is queued, // mainThreadPoster is not null mainThreadPoster. Enqueue (subscription, event); } break; // Regardless of which thread sends the event, the event is first queued and then switched to the main thread by Handler, which processes the event in turn. // mainThreadPoster not empty case MAIN_ORDERED: if (mainThreadPoster! = null) { mainThreadPoster.enqueue(subscription, event); } else { invokeSubscriber(subscription, event); } break; case BACKGROUND: If (isMainThread) {backgroundPoster. Enqueue (subscription, event); // If (isMainThread) {backgroundPoster. } else {// If an event is sent in a child thread, invokeSubscriber is handled by reflection directly in the sending thread (subscription, event); } break; // Regardless of which thread sends the event, the event is queued and processed by the thread pool. case ASYNC: asyncPoster.enqueue(subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); }}Copy the code

PostToSubscription () internally executes a subscribed event method by reflection, either indirectly or directly, taking the sent event as an argument, depending on the thread pattern of the subscribed event method.

As you can see, the postToSubscription() method determines how an event is handled based on the pattern of the thread that subscribed to the event method and the thread that sent the event. There are two main ways to handle the event: One way is to use reflection to execute subscribed events directly in the corresponding thread through the invokeSubscriber() method, so that the sent event is received by the subscriber and processed accordingly:

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

MainThreadPoster. Enqueue (subscription, event) ¶ mainThreadPoster. Enqueue (mainThreadPoster. MainThreadPoster is an instance of HandlerPoster. See the main implementation of this class

public class HandlerPoster extends Handler implements Poster { private final PendingPostQueue queue; private boolean handlerActive; . public void enqueue(Subscription subscription, PendingPost PendingPost = PendingPost.obtainPendingPost(subscription, event); Synchronized (this) {queue. Enqueue (pendingPost); if (! handlerActive) { handlerActive = true; The handleMessage() method is executed, completing the switch from child thread to main thread. If (! sendMessage(obtainMessage())) { throw new EventBusException("Could not send handler message"); } } } } @Override public void handleMessage(Message msg) { boolean rescheduled = false; try { long started = SystemClock.uptimeMillis(); PendingPost PendingPost = queue.poll(); PendingPost = queue.poll(); . / / further processing pendingPost eventBus. InvokeSubscriber (pendingPost); . } } finally { handlerActive = rescheduled; }}}... void invokeSubscriber(PendingPost pendingPost) { Object event = pendingPost.event; Subscription subscription = pendingPost.subscription; / / release pendingPost reference resources pendingPost. ReleasePendingPost (pendingPost); If (subscription. Active) {invokeSubscriber(subscription, event); }}...Copy the code

Unregister EventBus

EventBus.getDefault().unregister(this);

Public synchronized void unregister(Object subscriber) {List<Class<? >> subscribedTypes = typesBySubscriber.get(subscriber); if (subscribedTypes ! = null) {// Iterate over the set of parameter types, freeing the Subscription for (Class<? > eventType : subscribedTypes) { unsubscribeByEventType(subscriber, eventType); } // Delete the pair of typesbysubscriber.remove (subscriber); } else { logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass()); } } private void unsubscribeByEventType(Object subscriber, Class<? > eventType) {/ / get the current parameter types corresponding collection Subscription List < Subscription > subscriptions = subscriptionsByEventType. Get (eventType); if (subscriptions ! = null) { int size = subscriptions.size(); // Subscription set for (int I = 0; i < size; i++) { Subscription subscription = subscriptions.get(i); // If the current subscription object corresponds to the same registered class object as the unregistered class object, If (subscription == subscriber) {subscription. Active = false; subscriptions.remove(i); i--; size--; }}}}Copy the code

In the unregister() method, the resources cached in typesBySubscriber and subscriptionsByEventType are released.

After reading the simple core code of EventBus, I deepened my use of annotations and reflection, maps and lists to store objects and parameter types.