EventBus, as a frequently used project in Android development, has helped us simplify data transfer in many complex scenarios, which is also an essential question in Android junior high school advanced interview.

However, most people don’t use EventBus correctly. Today, I will show you how to use EventBus correctly and how to understand EventBus from the source code level.

EventBus project address: github.com/greenrobot/…

1. Use EventBus

Step 1: Register

/ / register
EventBus.getDefault().register(this)
/ / the registration
EventBus.getDefault().unregister(this)
Copy the code

Registration and unregistration operations need to be used carefully to ensure that the lifecycle is in sync. For example, if you register in onResume, you should unregister in onPause.

Step 2: Set methods and add annotations

@Subscribe(threadMode = ThreadMode.MAIN)
fun onReceiverEventTwoNormal(event: FragmentEvent) {
    if (event.isOne()) {
        bindView {
            tvReceiver.text = "Received contents:${event.eventMsg}"}}}Copy the code

Step 3: Use the POST method event

EventBus.getDefault().post(FragmentEvent.postOne())
Copy the code

I’m sure it’s the same thing you’re using, and most of the projects I’ve seen are set up this way.

But if that were all I could say, trust me, I wouldn’t need to write this article.

Have you ever come across this question in an interview?

Req: What is the difference between EventBus 2.x and EventBus 3.x?

The EventBus2.x implementation uses reflection, while 3.0 uses annotations and is optimized with indexes. Ok, it looks like perferct, but how do you index? And is the above code really better than the 2.0 code?

I don’t know how you would feel if I told you that the above processing is worse than EventBus 2.x. Go straight to the diagram above.

Here first put the correct way to use, after the analysis of the source code I believe we will understand.

Add the following to the build.gradle file under your project or module:

kapt { arguments { arg('eventBusIndex', 'org. Fireking. Event. MyEventBusIndex' dependencies)}} {kapt 'org. Greenrobot: eventbus -- the annotation processor: 3.0.0'... }Copy the code

To rebuild the code, you will be in the build/generated/source/kapt generated under/debug/org. Fireking. Event. MyEventBusIndex file, When we open it up, we see that it’s actually the class we identified with the SUBSCRIBE annotation.

.private static finalMap<Class<? >, SubscriberInfo> SUBSCRIBER_INDEX; static { SUBSCRIBER_INDEX = new HashMap<Class<? >, SubscriberInfo>(); putIndex(new SimpleSubscriberInfo(org.fireking.laboratory.eventbus.EventBusOneFragment.class.true,
            new SubscriberMethodInfo[] {
        new SubscriberMethodInfo("onReceiverEventTwoNormal", org.fireking.laboratory.eventbus.FragmentEvent.class,
                ThreadMode.MAIN),
    }));

    putIndex(new SimpleSubscriberInfo(org.fireking.laboratory.eventbus.EventBusTwoFragment.class.true,
            new SubscriberMethodInfo[] {
        new SubscriberMethodInfo("onReceiverEventTwoNormal", org.fireking.laboratory.eventbus.FragmentEvent.class, ThreadMode.MAIN), })); }...Copy the code

You must have thought of it, here is a direct use of Kapt, the previous annotation class and method relationship directly generated file saved in map, future use should be directly from this, no need to reflect the search. As for the specific is not so, the source code will explain.

In Application, add Settings

// Register the methods generated above with the default eventBus configuration item, and you can use them as before.
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
Copy the code

2. Conventional principle analysis

For the analysis of Event implementation, we directly analyzed according to the commonly used method flow.

2-1.EventBus.getDefault().register(Object subscriber)

public void register(Object subscriber) {
	// Find the class based on the currently passed objectClass<? > subscriberClass = subscriber.getClass();// Find all subscriberMethods under the current class object
    List<SubscriberMethod> subscriberMethods
    =subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
    	// Iterate over all subscription methods
        for(SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); }}}Copy the code

The 2-1-1,subscriberMethodFinder#findSubscriberMethods

// It is used to hold the class and all the subscribed method relationships in the class
private static finalMap<Class<? >, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>(); .//SubscriberMethod is saved method information that uses Subscriber annotations
public class SubscriberMethod {
    // The method of using Subscriber annotations
    final Method method;
    // Corresponding to threadMode in the annotation
    final ThreadMode threadMode;
    EventBus limits only one parameter to be passed by the method, which is the first parameter
    finalClass<? > eventType;// Event priority
    final int priority;
    // Whether it is sticky
    finalboolean sticky; .Copy the code
List<SubscriberMethod> findSubscriberMethods(Class<? > subscriberClass) {// Check whether the cache data already exists from method-cachae
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
		// If it is already cached, return it directly
        if(subscriberMethods ! =null) {
            return subscriberMethods;
        }
		//ignoreGeneratedIndex specifies whether to ignore the index generated by the Annotation Processor
		// If ignored, reflection is used by default, otherwise the generated index is used
        if (ignoreGeneratedIndex) {
        	// Use reflection to find
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
        	// Use the index to search, if not found in the index, use reflection to search again
            subscriberMethods = findUsingInfo(subscriberClass);
        }
        Eventbus.getdefault ().register()
        // If there is no @subscriber method in the class, an exception will be thrown
        if (subscriberMethods.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
        	// Once the retrieval is complete, save the class and @subscriber Method relationship in method-Cacahe
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            / / return subscriberMethods
            returnsubscriberMethods; }}Copy the code
  • private List<SubscriberMethod> findUsingReflection(Class<? > subscriberClass)Reflection mode
private List<SubscriberMethod> findUsingReflection(Class
        subscriberClass) {
    // Get the FindState object from the cache pool of size 4
    FindState findState = prepareFindState();
    // Initialize the FindState object state
    findState.initForSubscriber(subscriberClass);
    while(findState.clazz ! =null) {
        // Use reflection to find
        findUsingReflectionInSingleClass(findState);
        // search from the parent class
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}
Copy the code

FindUsingReflectionInSingleClass core search method

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    try {
        // Only public methods are found to increase the speed of the search
        // 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) {
        Public is 1, private is 2, protected is 4, static is 8, final is 16.
        int modifiers = method.getModifiers();
        // The modifier is public
        if((modifiers & Modifier.PUBLIC) ! =0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            // Get the class object type of the parameterClass<? >[] parameterTypes = method.getParameterTypes();// Only one eventBus parameter can be used. If more than one parameter is used, an exception will be thrown
            if (parameterTypes.length == 1) {
                // Get the annotation of the method
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if(subscribeAnnotation ! =null) { Class<? > eventType = parameterTypes[0];
                    if (findState.checkAdd(method, eventType)) {
                        ThreadMode threadMode = subscribeAnnotation.threadMode();
                        // Save the found method data to findState
                        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"); }}} the clazz method is replaced by the clazz objectwhileLoop ` findUsingReflectionInSingleClass ` and ` moveToSuperclass ` to find one by one. ` ` `java
 void moveToSuperclass(a) {
     // If the parent class is skipped, null is returned
     if (skipSuperClasses) {
         clazz = null;
     } else {
         // Return the parent class
         clazz = clazz.getSuperclass();
         String clazzName = clazz.getName();
         // Skip system classes, this degrades performance.
         // Also we might avoid some ClassNotFoundException (see FAQ for background).
         // Skip Java and Android classes
         if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") ||
                 clazzName.startsWith("android.") || clazzName.startsWith("androidx.")) {
             clazz = null; }}}}Copy the code

Finally, the collection of methods found is returned and the cache pool getMethodsAndRelease is cleared

The 'findState' cache pool needs to be freed when it is used. Java private List<SubscriberMethod> getMethodsAndRelease(FindState FindState) {List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods); // Release the cache pool findstate.recycle () after the search is complete; 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; }Copy the code

Since there are two search methods, namely reflection and APT, THE apt method will be explained in the following part. As for how to use the search method, the following part will also be explained.

  • private List<SubscriberMethod> findUsingInfo(Class<? > subscriberClass)Apt mode priority, search will not reflect search
privateList<SubscriberMethod> findUsingInfo(Class<? > subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass);while(findState.clazz ! =null) {
    	// Search from index information generated by APT
        findState.subscriberInfo = getSubscriberInfo(findState);
        // It was found in the index
        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 {
          	// If not found in index, use reflection to search
            findUsingReflectionInSingleClass(findState);
        }
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}
Copy the code
  • getSubscriberInfo
private SubscriberInfo getSubscriberInfo(FindState findState) {
    if(findState.subscriberInfo ! =null&& findState.subscriberInfo.getSuperSubscriberInfo() ! =null) {
    	// Look in the parent class
        SubscriberInfo superclassInfo = 
        findState.subscriberInfo.getSuperSubscriberInfo();
        // If found, return directly
        if (findState.clazz == superclassInfo.getSubscriberClass()) {
            returnsuperclassInfo; }}//SubscriberInfoIndex is an interface from which we inherit classes generated by APT
    if(subscriberInfoIndexes ! =null) {
        for (SubscriberInfoIndex index : subscriberInfoIndexes) {
            SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
            // If it is found from the index apt generated, it is returned directly
            if(info ! =null) {
                returninfo; }}}return null;
}
Copy the code

The 2-1-2,subscribe(subscriber, subscriberMethod)

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
     // Get the @subscribe method argumentClass<? > eventType = subscriberMethod.eventType;// The Subscription object encapsulates the @subscribe method and the subscribed class into a single class
     Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
     / / subscriptionsByEventType is a @ the Subscribe method parameter as a key, the subscription method is set as the value of the Map
     CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
     // Check to see if it is already registered
     if (subscriptions == null) {
         // A thread safe read/write queue is used here
         subscriptions = new CopyOnWriteArrayList<>();
         // If not registered, all methods of eventType will be collected into a collection
         // The eventType is the @subscribe parameter, so it can be interpreted as putting all the registered types in a set,
         // When unregistered, it is removed from the collection, so that all registered methods can be notified
         subscriptionsByEventType.put(eventType, subscriptions);
     } else {
         // If already registered, repeat registration to throw an exception
         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++) {
         // Sort by priority, because priority is compared every time,
         // So you end up with a higher -> lower priority
         if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
             subscriptions.add(i, newSubscription);
             break; }}// a set of subscriptionsByEventType,
     // Discovery is only used to assist the isRegistered method in determining whether it has been registeredList<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {
         subscribedEvents = new ArrayList<>();
         typesBySubscriber.put(subscriber, subscribedEvents);
     }
     subscribedEvents.add(eventType);

	// This is for sticky event handling, which will be explained later
     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

The following is a brief summary of EventBus#register(Object subscriber) workflow

Using subscriberMethodFinder findSubscriberMethods, query the subscriber registration parameters as parameters in the class to all USES the @ the Subscribe method of annotation.

The search logic is as follows:

If found in METHOD_CACHE, it returns directly. If not, it will judge whether apt method is used to generate at compile time. If apt method is not used, reflection method is used to search.

Once found, it is stored in METHOD_CACHE and returned for subsequent use.

After that, the method set of @SUBSCRIBE annotation found is iterated, and the method parameter is taken as the key, and all the method set using the method parameter is taken as the value, which is saved in subscriptionsByEventType.

All @subsciBe annotation methods in the current registered class are counted into a collection using the event object (that is, the parameter) as the key. This facilitates event distribution during subsequent POST. It’s a very typical subscription-spend model, or observer model.

2-2,unregister(Object subscriber)

public synchronized void unregister(Object subscriber) {
    // As mentioned above, typesBySubscriber is really just a help in checking whether or not you have registeredList<Class<? >> subscribedTypes = typesBySubscriber.get(subscriber);// If it is already registered
    if(subscribedTypes ! =null) {
        // Register takes the class as the key, and all @SUBSCRIBE method arguments (events for Hong Kong) in the class as values
        for(Class<? > eventType : subscribedTypes) {// Iterate through the collection to remove the saved data from subscriptionsByEventType
            unsubscribeByEventType(subscriber, eventType);
        }
        // Remove the key and value from the map
        typesBySubscriber.remove(subscriber);
    } else {
        logger.log(Level.WARNING, "Subscriber to unregister was not registered before: "+ subscriber.getClass()); }}Copy the code
  • unsubscribeByEventType(Object subscriber, Class<? > eventType)

The logic here is relatively simple, with the above analysis of the register method, I believe this is easy to understand, without further explanation

private void unsubscribeByEventType(Object subscriber, Class
        eventType) {
    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);
            if (subscription.subscriber == subscriber) {
                subscription.active = false; subscriptions.remove(i); i--; size--; }}}}Copy the code

Here’s a quick summary of the Event#unregister(Object subscriber) method:

Two map collections typesBySubscriber and subscriptionsByEventType are used

  • typesBySubscriberThe set is essentially savedsubscriberAll of the following@SUBSCRIBE annotation eventCollection, where key is the usage class and value is the event collection.
  • subscriptionsByEventTypeIt’s all that’s savedEvents forImage’s set of methods, events and methods areOne-to-many relationship.

The unRegister operation is to clean up the relationships between classes and event sets and between events and event object methods found in the register method to prevent memory leaks caused by object references.

Posts, sticky messages, etc., will be split into a separate article for space reasons