EventBus for Android Development, I

Posted by webmazter on Fri, 21 Jan 2022 04:53:48 +0100

1. Introduction to EventBus

EventBus is an event publishing-subscription bus for Android, developed by GreenRobot. It simplifies the complexity of communication between components within an application, especially between fragments, and avoids the inconvenience of using broadcast communication.
EventBus can replace Android's traditional Intent,Handler,Broadcast, or interface functions to transfer data and execute methods between Fragment, Activity, and Service threads.
This article will not cover the basic use of EventBus, but will focus on the source code and the implementation of EventBus.

2. Source code parsing?

    void test() {
        EventBus.getDefault().register(this);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onGetMessage(MessageBean message) {
        //Processing messages
    }

    class MessageBean {
        int messageId;
    }

2.1 register

Let's first look at how EventBus was created, EventBus.getDefault()

    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }

getDefault returns an EventBus object for a global singleton. Getting this object then calls the register method to register an object, which can be viewed as an observer.

public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
//Get all the Subscribe annotated methods inside the subscriber object
    List<SubscriberMethod> subscriberMethods = 	
subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
//Traverse, adding methods to the list
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

findSubscriberMethods Get Subscriber Methods by Reflecting or Compiling Note Information
This concept is temporarily understood as a way to get all Subscribe annotations through reflection, and how compile-time annotations are presented in the next article.

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
   //Getting it from the cache first improves efficiency
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
//
    if (ignoreGeneratedIndex) {
//Using reflection to get
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
//Compile-time annotation generated class acquisition.
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the 
@Subscribe annotation");
    } else {
// Save last. That is, a class is only registered for the first time
//Get the subscriber method and use the cache when registering again.
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

Here is a brief description of the difference between findUsingReflection and findUsingInfo.
FindUsingReflection mainly reflects back all the methods annotated by Subscribe. Since Java does not provide an api to get all the methods that have this annotation in an object based on the type of annotation, we have to go through all the methods in the object and decide whether or not this method has this annotation individually. This process is time consuming. To solve this problem, EventBus introduces compile-time annotations. If a method is annotated by Subscribe, some corresponding code will be generated during compilation, such as the name of the method, the parameter type, etc. Then all methods annotated by Subscribe can be obtained directly from this information during registration, avoiding the process of reflection and traversal. Save time and improve efficiency, and the compile-time notes on EventBus are freed up for the next article, where only findUsingReflection is analyzed.

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
  //findState is reusable and has a reuse pool.
  //Think of it as a tool class
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
//Annotate Get Reflection
        findUsingReflectionInSingleClass(findState);,
//Switch to the parent class to find the method annotated by Subscribe within it
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}

This is mainly a while loop in which the object itself begins to iterate up its ancestor classes to get the method annotated by Subscribe.

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        // This is faster than getMethods, especially when subscribers are 
fat classes like Activities
//Get all methods, including private methods
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        // Workaround for java.lang.NoClassDefFoundError, see 
        methods = findState.clazz.getMethods();
        findState.skipSuperClasses = true;
    }
    for (Method method : methods) {
        int modifiers = method.getModifiers();
// Subscriber method, must be of type public and cannot be abstract
//static method
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & 
MODIFIERS_IGNORE) == 0) {
		//Get the formal parameters of the method
            Class<?>[] parameterTypes = method.getParameterTypes();
  //There can only be one parameter
            if (parameterTypes.length == 1) {
 //Annotations must be used. Previous versions required method names, but now there is no requirement
                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)) {
// Too many parameters, errors will be made in strict mode, and ignored in non-strict mode.
//You can choose what you want
                 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)) {
// Static method, abstract method using Subscribe annotation
           	 String methodName = method.getDeclaringClass().getName() + 
"." + method.getName();
            throw new EventBusException(methodName +
                    " is a illegal @Subscribe method: must be public,
 non-static, and non-abstract");
        }
    }
}

This is to iterate through all the methods of the class to determine if they have Subscribe annotations, and to check for this method, the first method annotated by Subscribe must be of type public, not abstract or static. On the other hand, the number of formal parameters of the method is checked, where only one parameter is required. If EventBus is running in strict mode and the above conditions do not meet, an EventBusException exception will be thrown.
If everything matches, a SubscriberMethod object is eventually created that contains information about the annotation connection method.

After while traversal, getMethodsAndRelease is called

private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
    List<SubscriberMethod> subscriberMethods = new ArrayList<>
(findState.subscriberMethods);
//Recycling means emptying the map inside, etc.
    findState.recycle();
//Save it, somewhat like Message, and it can be reused.
    synchronized (FIND_STATE_POOL) {
        for (int i = 0; i < POOL_SIZE; i++) {
            if (FIND_STATE_POOL[i] == null) {
                FIND_STATE_POOL[i] = findState;
                break;
            }
        }
    }
    //
    return subscriberMethods;
}

The main purpose here is to recycle the findState object and register all the methods annotated by Subscribe within the class.

Let's go back to the register method

findSubscriberMethods traverses the List after returning

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// Get parameter type, message type received
    Class<?> eventType = subscriberMethod.eventType;
    Subscription newSubscription = new Subscription(subscriber, 
subscriberMethod);
   //Determine if this message type has been registered before.
    CopyOnWriteArrayList<Subscription> subscriptions = 		
subscriptionsByEventType.get(eventType);
 
    if (subscriptions == null) {
    //If this is the first time you have registered this message type, create a list for this message
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
		//Not going here,
    }

// Insert into the list by priority,
//The larger the priority, the more behind it; If the values are the same, sort them in the order they were added
    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;
        }
    }
//Get all message types that subscriber can receive
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
//Save message type.
    subscribedEvents.add(eventType);

//Sticky message
    if (subscriberMethod.sticky) {
//eventInheritance defaults to true
        if (eventInheritance) {
//stickyEvents, saves historical messages, key is message type, value is last
//Messages distributed.
            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();
  //Distribute sticky messages.
                    checkPostStickyEventToSubscription(newSubscription, 
stickyEvent);
                }
            }
        } else {
 //Whether eventInheritance receives historical messages of subtypes or not,
//The default is true, and if false, the value receives messages of this type.
//For example, charsequence when eventInheritance =true 
//Can receive messages of type String, false can only receive messages of type charsequence
//Subtypes cannot be received.
            Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

Two important objects of the table are involved here
1 subscriptionsByEventType:
The type of subscriptionsByEventType is Map<Class<?>, CopyOnWriteArrayList>, used to hold all message types, the key is the class of the message, the value is a list, this list holds callbacks to receive this message type.

 EventBus.getDefault().post(new MessageBean());

When you distribute a message, you can quickly get all the recipients of this message type through subscriptionsByEventType, and then pass the message to the appropriate method, which is described later.

1 typesBySubscriber:
The type of typesBySubscriber is Map<Object, List<Class<?>>, This saves all the types that the registered class can receive.

 EventBus.getDefault().unregister(this);

When you unregister, you can quickly get all the message types that the this object can receive, and then delete the corresponding information. Unregister comes later.

Finally, the sticky message is processed, and if it is received, the cached message is passed to the corresponding method.

Last

    @Subscribe(threadMode = ThreadMode.MAIN,priority =10 )
    public void onGetMessage(MessageBean message) {
        //Processing messages
    }

If there are two ways to receive the message Bean, to whom will EventBus give priority when a message arrives? From the source above, you can see who gets the bigger priority first, because the bigger priority is always at the front of the list.

2.1 post

After the registration is complete, you can wait for the message to arrive. Let's see below EventBus How messages are distributed to individual observers.
   EventBus.getDefault().post(new MessageBean());
/** Posts the given event to the event bus. */
public void post(Object event) {
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
//Queue messages first
    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()) {
  //Start distributing messages
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

This shows queuing messages. Then call postSingleEvent to distribute the message, a little confused about why.

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    if (eventInheritance) {
      //lookupAllEventTypes queries for subclasses of eventClass.
      //For example, post a MainActivity object, the data type received by the recipient's small A registration is Activity.
      //Since MainActivity inherits Activity, Little A can also receive this message at this time.
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            //Distribute message
            subscriptionFound |= postSingleEventForEventType(event, 
  postingState, clazz);
        }
    } else {
	   //Subtypes of messages are not allowed.
        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) {
//Unreceived messages are converted to NoSubscriberEvent-type messages
//We can register to listen for messages that nobody receives
            post(new NoSubscriberEvent(this, event));
        }
    }
}
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
    //subscriptionsByEventType see previous explanation
    //Get all methods that receive this message
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            try {
//Real Start Distributing Messages
                postToSubscription(subscription, event, 	
postingState.isMainThread);
//The user canceled the message
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}

This is mainly to deal with parent-child type issues, such as small A registration receiving a type of message. When a type two message is thrown, inherited or implemented as one, whether small A can receive messages of a subtype at this time depends on the eventInheritance parameter, which can be set through EventBusBuilder's eventInheritance. The actual distributed message is the invoked postSingleEventForEventType.

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING:
//Distribute messages within the same thread
            invokeSubscriber(subscription, event);
            break;
        case MAIN:
//Non-android Main Thread
            if (isMainThread) {
                invokeSubscriber(subscription, event);
            } else {
                mainThreadPoster.enqueue(subscription, event);
            }
            break;
        case MAIN_ORDERED:
//This is the main thread of android.
//mainThreadPoster is a handler
            if (mainThreadPoster != null) {
                mainThreadPoster.enqueue(subscription, event);
            } else {
                // temporary: technically not correct as poster not decoupled from subscriber
                invokeSubscriber(subscription, event);
            }
            break;
        case BACKGROUND:
//Execute on a non-primary thread
            if (isMainThread) {
                backgroundPoster.enqueue(subscription, event);
            } else {
                invokeSubscriber(subscription, event);
            }
            break;
        case ASYNC:
//Opens a subthread execution with a thread pool inside.
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: " + 		
subscription.subscriberMethod.threadMode);
    }
}

Distribute messages based on ThreadMode.
EventBus3.0 has the following four ThreadMode s:

POSTING (default): If the thread model is specified as POSTING using an event handler, then in which thread the event was published, the event handler runs in this thread, that is, the publishing and receiving events are on the same thread. Avoid time-consuming operations in event handlers where the threading model is POSTING, as it can block the delivery of events and possibly even cause ANR s.

MAIN: Processing of events is performed in the UI thread. Event processing time cannot be too long, it will be ANR long.

BACKGROUND: If an event is published in a UI thread, the event handler will run in a new thread. If the event was originally published in a child thread, the event handler will execute directly in the thread that published the event. UI update operations are prohibited in this event handler.
ASYNC: The event handler executes in a new child thread regardless of which thread the event is published. Similarly, UI updates are prohibited in this event handler.

We will only analyze the implementation of MAIN here

       case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
    void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
        	//Message Join Queue
            queue.enqueue(pendingPost);
            if (!handlerActive) {
                handlerActive = true;
                //Handle handles the message 
                if (!sendMessage(obtainMessage())) {
                    throw new EventBusException("Could not send handler message");
                }
            }
        }
    }

Let's see how Hnadler handles this message

@Override
public void handleMessage(Message msg) {
    boolean rescheduled = false;
    try {
        long started = SystemClock.uptimeMillis();
        while (true) {
//Get out the message
            PendingPost pendingPost = queue.poll();
            if (pendingPost == null) {
                synchronized (this) {
                    // Check again, this time in synchronized
                    pendingPost = queue.poll();
                    if (pendingPost == null) {
//No more messages, handlerActive set false
                        handlerActive = false;
//No message exited handleMessage
                        return;
                    }
                }
            }
//Call Subscriber's Method
            eventBus.invokeSubscriber(pendingPost);
            long timeInMethod = SystemClock.uptimeMillis() - started;
 //Processing time has expired, at which point there may be messages that have not been distributed.
//This will exit while and wait for the schedule to be rescheduled.
            if (timeInMethod >= maxMillisInsideHandleMessage) {
//Send a message waiting to be scheduled again
                if (!sendMessage(obtainMessage())) {
                    throw new EventBusException
("Could not send handler message");
                }
                rescheduled = true;
 //Timed out while waiting to be scheduled again
                return;
            }
        }
    } finally {
        handlerActive = rescheduled;
    }
}

It simply takes out the message and calls the subscriber.

More interesting is the processing of message processing timeouts, which has never been used before and has never been introduced in other people's articles.

2.3 Anti-registration

To avoid memory leaks, we usually unregister pages when they are destroyed

  EventBus.getDefault().unregister(this);

See the source code for the anti-registration below

    public synchronized void unregister(Object subscriber) {
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            for (Class<?> eventType : subscribedTypes) {
                unsubscribeByEventType(subscriber, eventType);
            }
            //TypeesBySubscriber is described above
            typesBySubscriber.remove(subscriber);
        } else {
            Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

typesBySubscriber saves all message types subscriber subscribes get these message types and call unsubscribeByEventType to unsubscribe

    private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    //By type, find all subscribers to this message, that is, the way to receive it
        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);
                //Subscriber's class is subscriber
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    //Remove Subscriber
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }

The unsubscribeByEventType is the method of finding all received eventType messages within a subscriber and deleting the corresponding subscription. The Subscription class is a wrapper for the method of receiving messages, as described earlier.

All Subscription s are eventually removed through the for loop inside unregister.

summary

EventBus source analysis is now complete, and one of the remaining issues is the compile-time annotations mentioned in this article. EventBus avoids reflection time by compile-time annotations and improves the efficiency of EventBus execution. In the next article, we'll cover compile-time annotations, followed by EventBus's compile-time annotations and their dynamically generated code.

Topics: Android