“This is the 9th day of my participation in the November Gwen Challenge.The final text challenge in 2021”


preface

This paper mainly analyzes the implementation principle of EventBus from the perspective of source code. EventBus is one of the commonly used message delivery methods. Other common message delivery methods also include Handler, BroadcastReceiver and Listener. In this tutorial, you will learn how to implement EventBus. This can be clearly seen in the following frame diagram.

Define the event class

As an event publisher, you need to define the class of the published event:

public class MessageEvent {
	private String msg;
	public MessageEvent(String msg) {
    this.msg = msg;
	}

	public String getMsg(a) {
    	return msg;
	}

	public void setMsg(String msg) {
    	this.msg = msg; }}Copy the code

Register/unregister response events

As an event subscriber, you need to register the object that responds to the event with EventBus: eventbus.getDefault ().register(obj)

Unlisten for a type of event when it is not needed: eventbus.getDefault ().unregister(obj)

Declare and comment subscription methods, select the specified thread mode

As the subscriber of the event, we need to define the response method of the event. The name of the method can be arbitrary, and the parameter type of the method must be the same as the type of the listening event object:

@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
	Toast.makeText(this, event.getMsg (),
	Toast.LENGTH_SHORT).show();
}
Copy the code

3.1 Four threading modes

Event subscribers can annotate the thread in which the event method is handled:

  • PostThread: If the event handler specifies the thread model as PostThread, the event is published and received in the same thread.

  • BackgroundThread: If the event handler specifies the threading model for BackgroundThread, so if the event is posted in the UI thread, then the event handler will be in the new child thread running, if the event publishing is originally released in the UI thread, the event handler in the thread of publishing events directly executed.

  • MainThread: If the event handler specifies the thread model as MainThread, the event handler will be executed in the UI thread regardless of the thread in which the event object is published.

  • Async: If the event handler specifies the thread model Async, the event handler will be executed in the newly created child thread regardless of which thread the event is published in.

3.2 Viscosity events

If sticky is set to true with an annotation, the event handler can handle the last event object:

@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
Copy the code

EventBus 3.0

4.1 Registration Process

/**
* Registers the given subscriber to receive events. 	Subscribers must call {@link #unregister(Object)} once they
* are no longer interested in receiving events.
* <p/>
* Subscribers have event handling methods that must be 	annotated by {@link Subscribe}.
* The {@link Subscribe} annotation also allows configuration 	like {@link
* ThreadMode} and priority.
*/
public void register(Object subscriber) {
// Get the class object of the registered objectClass<? > subscriberClass = subscriber.getClass();// Get the list of subscribed methods for this object from the class object
List<SubscriberMethod> subscriberMethods = 	subscriberMethodFinder.findSubscriberMethods(subscriberClass);
	synchronized (this) {
    	for (SubscriberMethod subscriberMethod :subscriberMethods) {
        	// Thread synchronization, traversing the list of subscription methods, registering each subscription methodsubscribe(subscriber, subscriberMethod); }}}Copy the code

Code subscriberMethodFinder. FindSubscriberMethods (subscriberClass) access to subscribe to the list of methods specific as follows:

List<SubscriberMethod> findSubscriberMethods(Class
        subscriberClass) {
	// Look up the list of subscribed methods for this class object in the cache
  	List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
  	if(subscriberMethods ! =null) {
      return subscriberMethods;
  	}
  // Whether to ignore the MyEventBusIndex class generated by the annotator
  if (ignoreGeneratedIndex) {
    	// Use reflection to get the list of subscription event methods corresponding to the subscriber class object
    	subscriberMethods = findUsingReflection(subscriberClass);
  } else {
  		// Get the list of subscription methods for the subscription class from the MyEventBusIndex class generated by the annotator
    	subscriberMethods = findUsingInfo(subscriberClass);
	}
  if (subscriberMethods.isEmpty()) {
      throw new EventBusException("Subscriber " + subscriberClass
              + " and its super classes have no public methods with the @Subscribe annotation");
  } else {
      // Cache the list of subscribed methods for this class object
      METHOD_CACHE.put(subscriberClass, subscriberMethods);
      returnsubscriberMethods; }}Copy the code

Get the list of subscription methods via reflection:

private List<SubscriberMethod> findUsingReflection(Class
        subscriberClass) {
  FindState findState = prepareFindState();
  findState.initForSubscriber(subscriberClass);
  while(findState.clazz ! =null) {
      // Iterate over the subscription methods in the current class object and its parent
      findUsingReflectionInSingleClass(findState);
      findState.moveToSuperclass();
  }
  return getMethodsAndRelease(findState);
}
Copy the code

FindUsingReflectionInSingleClass method:

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;
	}
	// A method that iterates through this class
	for (Method method : methods) {
    	int modifiers = method.getModifiers();
    	if((modifiers & Modifier.PUBLIC) ! =0 && (modifiers & MODIFIERS_IGNORE) == 0) { Class<? >[] parameterTypes = method.getParameterTypes();// a function that takes only one parameter
        	if (parameterTypes.length == 1) { 
        		// Get the annotation information object for this function
        		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(newSubscriberMethod(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

At this point, we have a list of all subscription functions. Next, we register each subscription function:

// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<? > eventType = subscriberMethod.eventType; Subscription newSubscription =new Subscription(subscriber, subscriberMethod);
	// Get a list of listener objects from the listener event object
	CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
	if (subscriptions == null) {
    	subscriptions = new CopyOnWriteArrayList<>();
    	subscriptionsByEventType.put(eventType, subscriptions);
	} else {
    	// If the object is already in the listener list of the event object, an exception is thrown
    	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++) {
    	// Iterate through the listener list and insert the new listener object at the specified location in the list according to the priority of the subscription method, i.e. the registration of the subscription method
    	if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
        	subscriptions.add(i, newSubscription);
        	break; }}// Get a list of listener event types from the subscriber objectList<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {
    	subscribedEvents = new ArrayList<>();
    	typesBySubscriber.put(subscriber, subscribedEvents);
	}
	// Add an event type object to the list of event objects corresponding to this listener
	subscribedEvents.add(eventType);
	// Handle sticky events here
	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();
                // Sends sticky events to the specified subscription method for processingcheckPostStickyEventToSubscription(newSubscription, stickyEvent); }}}else {
        Object stickyEvent = stickyEvents.get(eventType);
        // Sends sticky events to the specified subscription method for processingcheckPostStickyEventToSubscription(newSubscription, stickyEvent); }}Copy the code

The event registration process is actually from the two dimensions of listener object and message event, adding the other party to the corresponding list respectively, which can be summarized by the following flow chart:

4.2 Release Process

/** Posts the given event to the event bus. */
public void post(Object event) {
	// Get the postingState object of the current thread through ThreadLocal
	PostingThreadState postingState = currentPostingThreadState.get();
	List<Object> eventQueue = postingState.eventQueue;
	// Add this event object to the thread's eventQueue
	eventQueue.add(event);
	if(! postingState.isPosting) {// Determine whether the current thread is a UI thread
    	postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
    	postingState.isPosting = true;
    	if (postingState.canceled) {
        	throw new EventBusException("Internal error. Abort state was not reset");
    	}
    	try {
        while(! eventQueue.isEmpty()) {// Traverses the thread message queue, processing message events in the message queue
            postSingleEvent(eventQueue.remove(0), postingState); }}finally {
        postingState.isPosting = false;
        postingState.isMainThread = false; }}}Copy the code

ThreadLocal stores local data for individual threads. The postSingleEvent function handles message events in this thread message queue:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<? > eventClass = event.getClass();boolean subscriptionFound = false;
	if (eventInheritance) {
    	// Get all the parent classes and interfaces of this event classList<Class<? >> eventTypes = lookupAllEventTypes(eventClass);int countTypes = eventTypes.size();
    	for (int h = 0; h < countTypes; h++) { Class<? > clazz = eventTypes.get(h);// The subscriber object list is obtained by the event class, and the event object is distributed to the corresponding subscription function for processingsubscriptionFound |= postSingleEventForEventType(event, postingState, clazz); }}else {
    	// The subscriber object list is obtained by the event class, and the event object is distributed to the corresponding subscription function for processing
    	subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
   }
	if(! subscriptionFound) {if (logNoSubscriberMessages) {
        Log.d(TAG, "No subscribers registered for event " + eventClass);
    	}
    	if(sendNoSubscriberEvent && eventClass ! = NoSubscriberEvent.class && eventClass ! = SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event)); }}}Copy the code

Specific distribution functions for event message objects: postToSubscription

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
	// Execute the subscription function in different threads according to the thread mode set in annotation mode
	switch (subscription.subscriberMethod.threadMode) {
    	case POSTING:
        	invokeSubscriber(subscription, event);
        	break;
    	case MAIN:
        	if (isMainThread) {
            	invokeSubscriber(subscription, event);
        	} else {
            	mainThreadPoster.enqueue(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

Now that we have completed the process of distributing the event message object, the following flow chart summarizes the POST process:

4.3 Registration Cancellation Process

/** Unregisters the given subscriber from all event classes. */
public synchronized void unregister(Object subscriber) {
	// Get a list of corresponding event types based on the subscriber objectList<Class<? >> subscribedTypes = typesBySubscriber.get(subscriber);if(subscribedTypes ! =null) {
    	for(Class<? > eventType : subscribedTypes) {// Iterate over the list of event types to get a list of subscriber objects corresponding to each event type, iterate over the list, if it is the observer object, remove it from the list
        	unsubscribeByEventType(subscriber, eventType);
    	}
    	// Delete the subscriber object in typesBySubscriber
    	typesBySubscriber.remove(subscriber);
	} else {
    	Log.w(TAG, "Subscriber to unregister was not registered before: "+ subscriber.getClass()); }}Copy the code

UnsubscribeByEventType function:

/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
private void unsubscribeByEventType(Object subscriber, Class
        eventType) {
	// Get the list of subscriber objects corresponding to the event type
	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);
        	// Iterate through the list and remove the subscriber object from the list if found
        	if (subscription.subscriber == subscriber) {
            	subscription.active = false; subscriptions.remove(i); i--; size--; }}}}Copy the code

The process for canceling registration is summarized as follows:

  1. Through the observer class object through the MAP table to get its corresponding event class object list.

  2. The list is iterated, and the list of observer class objects in the MAP table is obtained through the event class object.

  3. Iterate over the list of observer objects and determine that if there are registered observer objects in the list that need to be unregistered, delete this observer object from the list of objects.

  4. Remove the mapping item with the unregistered observer object as key from the MAP object obtained in Step 1.

  5. Complete the unregister process.