EventBus 3.0 Source Learning (I)

csdn blog: http://blog.csdn.net/hjjdehao
Before looking at the source code, we need to master the main knowledge points: collection, reflection, annotation.
Framework is basically written with the knowledge of these three aspects, not the best to master, otherwise you will be dizzy when you look.

1. Source Code Resolution of Registration

The flow chart of the registration process (from the network) is as follows:


Here's a picture description.
  When we use EventBus, the first thing we do is to register the event object. Generally speaking, to receive the message, we need to register the information so that the message can be notified to you.
  So how does EventBus register information? How do you receive events?

If we look at the code with questions, the effect will be better.
Registered Subscriber Code:

    //Register the current object to indicate that it receives events
    EventBus.getDefault().register(this);

Registration source code:

    public void register(Object subscriber) {
        //Get the Class type of the subscriber
        Class<?> subscriberClass = subscriber.getClass();
        //Get the subscriber's subscription method based on the subscriber's Class type
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

List< SubscriberMethod > subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
This code indicates that all subscribers'subscription methods are retrieved by the subscriber's Class type. So how does it get all the subscription methods?

Let's look at the source code for the findSubscriberMethods method again.

 List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
 //Find out if there is a subscription method from the cache first
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
 //Ignore the MyEventBusIndex class generated by the annotator
        if (ignoreGeneratedIndex) {
  //Using Reflection to Get Subscription Method Information in Subscription Class      
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
  //Get the subscription method information of 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 {
            METHOD_CACHE.put(subscriberClass, subscriberMethods);
            return subscriberMethods;
        }
    }

The source code above searches for subscription method information in two ways, as follows:

 if (ignoreGeneratedIndex) {
     //Using Reflection to Get Subscription Method Information in Subscription Class      
                subscriberMethods = findUsingReflection(subscriberClass);
            } else {
      //Get the subscription method information of the subscription class from the MyEventBusIndex class generated by the annotator     
                subscriberMethods = findUsingInfo(subscriberClass);
            }

ignoreGeneratedIndex judges whether your build configuration file configures the annotated processor information as follows:


Here's a picture description.
To say that the difference between the two ways is nothing more than performance and speed. Relatively speaking, annotation processing is better than reflection. Look at the figure below.

The general logic of this method is as follows:
First, the subscription method information is retrieved from the cache, and if not, the subscription method information is retrieved by reflection or annotation.

1. Use Annotation Processor
findUsingInfo(Class< ? > subscriberClass)

 private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            findState.subscriberInfo = getSubscriberInfo(findState);
            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 {
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

EventBus provides an EventBus Annotation Processor annotation processor to read and parse @Subscribe() annotations at compile time.
Processing the information contained therein and then generating java classes to hold all subscribers'information about subscriptions is much better than using reflection to get those subscribers at run time.
Information is faster.
EventBus 3.0 uses compile-time annotations, not run-time annotations. It can be seen from the table that the performance of callback method table is greatly improved by indexing.


Here's a picture description.

2. Subscription Method Information Obtained by Reflection

Find UsingReflection (subscriberClass) source code

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
     while (findState.clazz != null) {
          //Obtaining Subscription Method Information by Launching
          findUsingReflectionInSingleClass(findState);
            //Find Subscription Method Information for the Parent Class
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

FindState is an entity class that stores information about subscribers and subscription methods, as well as verifying subscription methods. So much has been written that ultimately findUsingReflectionInSingleClass(findState) is called to get information about the subscription method.
Look at the code:

private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            //Get all subscription methods for subscribers
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        for (Method method : methods) {
            //Get modified permissions for subscription methods
            int modifiers = method.getModifiers();
            //If it's a public-decorated method and it's not static or abstract, it throws an exception
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            //Get the parameter type array of the method
                Class<?>[] parameterTypes = method.getParameterTypes();
                //Method must have at least one parameter or throw an exception
                if (parameterTypes.length == 1) {
                //Getting Annotation Objects
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
                    //Getting parameter type information of event object
                        Class<?> eventType = parameterTypes[0];
                        //Check if a subscription method has been added
                        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");
            }
        }
    }

Start subscribing to event source code:
There are subscribers and subscription methods. Next is subscribing as a parameter.

  // Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    //Get the event type
        Class<?> eventType = subscriberMethod.eventType;
        //Instance of a Subscriber Information
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        //Getting Subscriber Objects Based on Event Types
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);
        } else {
            if (subscriptions.contains(newSubscription)) {
                throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                        + eventType);
            }
        }

        int size = subscriptions.size();
        //Adding Subscriber Objects Based on Priority
        for (int i = 0; i <= size; i++) {
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }
//Getting Subscriber Events Based on Subscriber Objects
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            //Save Subscriber Objects and Subscriber Events
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);

        if (subscriberMethod.sticky) {
            if (eventInheritance) {
                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);
            }
        }
    }

There is only one code for registration, but a lot of work has been done to go deep into it.
In fact, so much work has been done to store the relevant information of subscribers (subscribers, subscription methods, event objects, etc.) and pave the way for the distribution of events in the future.

Subscription logic:

    1. First, register () is called to register the subscription object.
    2. Get all the subscription methods of the subscription object according to the subscription object.
    3. subscriptions ByEventType. put (eventType, subscriptions), according to the event type of all subscriptions of the subscriber, the subscribers are stored in each map collection with event type as key and subscriptions as values.
    4. TypeesBySubscriber. put (subscriber, subscribed Events), and then add subscription events to the map collection with subscribers as key s and subscribers as values.

II. Analysis of Event Distribution

Event Distribution Flow Diagram (Source Network)


Here's a picture description.
  Registered subscribers are distributed for subsequent events so as to know who to send the event to, and event reception is the subscriber's subscription method.

Event distribution code:

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

Look at the source code of the post method:
A parameter is the event object to be sent

 public void post(Object event) {
     //Gets the status of the current thread
        PostingThreadState postingState = currentPostingThreadState.get();
        //Get the event queue for the current thread
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);
        //Determine whether the current thread is in the send state
        if (!postingState.isPosting) {
            //Set it as the main thread
            postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            //Continuously looping events out of the queue
            try {
                while (!eventQueue.isEmpty()) {
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

PostingThreadState is the static internal class of EventBust, which stores the event queue and thread state of the current thread.

final static class PostingThreadState {
        final List<Object> eventQueue = new ArrayList<Object>();//Event queue for current thread
         boolean isPosting;//Is there an event being distributed?
         boolean isMainThread;//Is the post thread the main thread?
         Subscription subscription;//Subscriber
         Object event;//Subscription events
         boolean canceled;//Whether to cancel
     }

Parametric 1: Event object
Parametric 2: Send status
PosSingleEvent (eventQueue. remove (0), postingState) executes source code

  private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
      //Get the Class type of the event object
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        //Determine whether event objects have parent objects
        if (eventInheritance) {
        //Find all event types of event objects and their parent types
            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) {
                Log.d(TAG, "No subscribers registered for event " + eventClass);
            }
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                post(new NoSubscriberEvent(this, event));
            }
        }
    }

postSingleEventForEventType(event, postingState, eventClass)
Looking at so many sources, I found that this method is the culprit. The method of ultimately launching events.
Then look at his source code:

  private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
        //Obtain all subscribers by event object type
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        //
        if (subscriptions != null && !subscriptions.isEmpty()) {
        //Travel through all subscribers
            for (Subscription subscription : subscriptions) {
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false;
                try {
                    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;
    }

postToSubscription(subscription, event, postingState.isMainThread);
Start processing subscriber information

 private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        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);
        }
    }

invokeSubscriber(subscription, event)
Parametric 1: Subscribers
Parametric 2: Event object
Finally, it is implemented through reflection calls.

 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);
        }
    }

Event distribution logic:

1. Get the event queue.
2. Loop out the events in the queue.
3. Get the subscriber set, then traverse, and finally invoke the subscriber's subscription method through reflection.

III. Resolution of Cancellation of Registration

 public synchronized void unregister(Object subscriber) {
     //Obtaining all subscriptions of a subscriber based on the event type of the subscriber
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
            for (Class<?> eventType : subscribedTypes) {
                unsubscribeByEventType(subscriber, eventType);
            }
            typesBySubscriber.remove(subscriber);
        } else {
            Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }
 private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
 //Get all subscribers of event type based on 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);
                if (subscription.subscriber == subscriber) {
                    subscription.active = false;
                    subscriptions.remove(i);
                    i--;
                    size--;
                }
            }
        }
    }

Cancellation logic:

1. First, all subscription events are retrieved according to the subscriber.
2. Traversing the subscription event set.
3. Get the subscriber set that subscribes to the event based on the subscription event.
4. Traverse the subscriber set, and then compare the incoming subscribers to determine whether they are the same subscriber. If so, remove it, and vice versa.

Reference resources: http://www.cnblogs.com/all88/archive/2016/03/30/5338412.html

Keywords: network Java

Added by sweetmaster on Sun, 16 Jun 2019 23:34:22 +0300