EventBus Application and Framework Principle of Android Event Bus Development

Posted by Rhiknow on Sun, 07 Jul 2019 19:42:58 +0200

Background of EventBus for [Android] Event Bus

During the development of our Android project, there are often communication requirements between components such as activities, fragments and service s, and between threads. The most widely used broadcast mechanism of Android framework is Android broadcast mechanism. Android broadcast mechanism is based on the Binder mechanism of the system to realize IPC or intra-process communication. Compared with the original mechanism of Linux, Binder IPC mechanism has the characteristics of better performance, higher security and better usability. So many system events in Android system are transmitted by broadcasting, such as boot-up broadcasting, reminder broadcasting with low power, etc. However, in an Android process, if the communication between components and sub-threads uses broadcasting, there will be a kind of cattle knife to kill chickens.

EventBus for Internal Communication of [Android] Process

EventBus is a publish/subscribe event bus component developed by Green Robot. It is also based on the observer model. The difference is that the EventBus framework decouples the sending and subscribing modules of events, and the distribution of event objects is handled by EventBus. The following framework diagram can clearly see this.

Basic usage of [Android]EventBus 3.0

Defining event classes

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

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

    public String getMsg() {
        return msg;
    }

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

II. Registration/Cancellation Response Events

As event subscribers, you need to register the object that responds to the event into EventBus:

EventBus.getDefault().register(obj)

When there is no need to process a certain type of event, deactivate the event:

EventBus.getDefault().unregister(obj)

3. Declare and annotate the subscription method, select the specified thread mode

As a subscriber to events, we need to define the response method of events. The name of the method can be chosen at will. The parameter type of the method must be the same as the event object type monitored.

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

3.1 Four Thread Patterns

Event subscribers can annotate the thread in which the method of handling the event is located:

PostThread: If the event handler specifies the thread model as PostThread, the event publishing and receiving processing will be in the same thread.

BackgroundThread: If the event handler specifies a thread model as BackgroundThread, then if the event is published in a UI thread, the event handler will run in a new sub-thread. If the event publication is originally published in a non-UI thread, then the event handler is direct. Execute in the thread that publishes the event.

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

Async: If the event handler specifies the thread model as Async, the event handler will execute in the newly created subthread regardless of which thread the event is published on.

3.2 Viscous Event

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

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

IV. EventBus 3.0 Source Details

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 its class from the registered object
Class<?> subscriberClass = subscriber.getClass();
//Get a list of subscription methods for the class object of the class
List<SubscriberMethod> subscriberMethods =  subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod :subscriberMethods) {
            //Thread synchronization, traversing the list of subscription methods, registering each subscription method
            subscribe(subscriber, subscriberMethod);
        }
    }
}

The code subscriberMethodFinder. find Subscriber Methods (subscriberClass) gets the list of subscribers as follows:

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    //Find the list of subscription methods corresponding to this class object in the cache
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
      return subscriberMethods;
    }
  //Ignore the MyEventBusIndex class generated by the annotator
  if (ignoreGeneratedIndex) {
        //A list of subscription event methods corresponding to the subscriber class object is obtained by reflection mechanism
        subscriberMethods = findUsingReflection(subscriberClass);
  } else {
        //Get a 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 subscription methods for this class object
      METHOD_CACHE.put(subscriberClass, subscriberMethods);
      return subscriberMethods;
  }
}

Get a list of subscription methods through reflection mechanism:

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

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;
    }
    //Traversing through such methods
    for (Method method : methods) {
        int modifiers = method.getModifiers();
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            //A function with only one parameter
            if (parameterTypes.length == 1) { 
                //Obtain the annotated information object of 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(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");
        }
    }
}

So far, we have got a list of all the subscription functions. Next, we will 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 by listening on event objects
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        //If this object is already in the listener list for this 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++) {
        //Traversing the list of listeners, inserting new listeners into the specified location of the list according to the priority of the subscription method, that is, 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 the corresponding listening event types from the subscriber object
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    //Add event type objects to the list of event objects corresponding to this listener object
    subscribedEvents.add(eventType);
    //Deal with viscous 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();
                //Send sticky events to a specified subscription method for processing
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);
            }
        }
    } else {
        Object stickyEvent = stickyEvents.get(eventType);
        //Send sticky events to a specified subscription method for processing
        checkPostStickyEventToSubscription(newSubscription, stickyEvent);
    }
}

The registration process of events is actually from the two dimensions of listener object and message event. Each party is added to its corresponding list, which can be summarized through 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 the ThreadLocal mechanism
    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 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()) {
            //Traverse this thread message queue to process message events in the message queue
            postSingleEvent(eventQueue.remove(0), postingState);
        }
        } finally {
        postingState.isPosting = false;
        postingState.isMainThread = false;
        }
    }
}

ThreadLocal mechanism can store local data of each thread.
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 class
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            //The event class is used to get the list of the corresponding subscriber objects, and the event objects are distributed to the corresponding subscription functions for processing. Event message transmission is thus realized.
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        //The event class is used to get the list of the corresponding subscriber objects, and the event objects are distributed to the corresponding subscription functions for processing. Event message transmission is thus realized.
        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));
        }
    }
}

Event message object specific distribution function: postToSubscription

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    //The subscription function is executed in different threads according to the thread mode set by the 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);
    }
}

So far, we have completed the distribution process of event message object. The following flow chart summarizes the process of post:

4.3 Cancellation process

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

unsubscribeByEventType function:

/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    //Get the corresponding list of subscriber objects according 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);
            //Traverse the list, and if you find this subscriber object, delete it from the list
            if (subscription.subscriber == subscriber) {
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}

The process of cancellation of registration is summarized as follows:

1. Observer class objects are used to get their corresponding list of event class objects through the MAP table.

2. Traverse the list list and get the corresponding list of Observer class objects in the MAP table through the event class object.

3. Traverse the list of observer objects and determine if there are registered observer objects that need to be cancelled in the list, then delete the observer objects from the list of objects.

4. Delete the mapping item whose key is the unregistered observer object from the MAP object obtained in step 1.

5. Complete the unregister process.

V. Comparison of Similar Functional Frameworks

Components with similar functions as EventBus include Otto launched by Square and LocalBroadCast of Android system. The common point is that these three frameworks are intra-process communication, which facilitates the communication between components and sub-threads in the process.

Ottootto uses annotations to register. Otto uses more scenarios in the main thread, because unlike EventBus, it can choose different thread modes.

LocalBroadCast is provided by Android in Android Support Library. It is used in the communication framework within the process. Its main purpose is to ensure the transmission of broadcasting within the process and the security of communication. Its internal implementation is realized by handler.

Topics: Android Linux Java github