The article directories

  1. What is EventBus? What’s the use of
  2. EventBus is easy to use
  3. EventBus principle analysis
  4. Hand lift EventBus framework
  5. EventBus optimization

1. What is EventBus? What’s the use of

EventBus is a subscription/publish (observer) based event processing mechanism on android platform, which can be used to pass events within and between Android components

In short: the way an event or notification is passed between Android activities (partial, but understandable)

This is EventBus? Since it is a communication method between components, Android has many implementation methods, such as Intent, Broadcast, Handler, so why should I use EventBus? Because there are some pain points in the traditional approach that EventBus solves, it is widely used

Disadvantages of the traditional way:

Intent to pass big data TransactionTooLargeException, Broadcast events cannot be specified after running threads, and the implementation of trival, coupling

EventBus advantages:

Simple to use, you can specify the thread on which the function that receives the Event runs, decoupled, and the Event that passes a large amount of data will not crash

So amazing? Take a look at how EventBus is used

2. Simple use of EventBus

Using EventBus is simple: register the object you want to receive the event in, write a few methods to receive the event, and unbind the registration when the page is destroyed. Once you’re ready, if you want to send an event from there, just post. Easy B

EventBus can be registered with any object and can send any event. Suppose you now send an event when a button is clicked in the Activity, receive the event in the current Activity, and update the UI

Note: EventBus event passing is allowed within and between components, as demonstrated in this example within components

2.1 Ready to receive events, register

Now that we’re ready to receive events in the Activity, we register the EventBus in the Activity’s onCreate

public class XActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        / / register
        EventBus.getDefault().register(this); }}Copy the code

The registration is simple: get the singleton of EventBus by getDefault() and then call Register (Object obj) to complete the registration

2.2 Write a method to receive events

To receive events in an Activity, write a method that receives the event and specifies the running thread as the main thread

public class XActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        / / register
        EventBus.getDefault().register(this);
    }
	

    /** * receives events via annotations that specify the running thread as the main thread **@paramEvent The event object */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void updateUI(String event) {
        / / update the UIbutton.setText(event); }}Copy the code

The function that receives the event uses an annotation to receive an Object of type Object. See what attributes the @SUBSCRIBE annotation supports

@Documented
@Retention(RetentionPolicy.RUNTIME)// Runtime annotations
@Target({ElementType.METHOD})// apply to the method
public @interface Subscribe {
	// Run the thread
    ThreadMode threadMode(a) default ThreadMode.POSTING;

	// Whether the event is sticky
    boolean sticky(a) default false;

	/ / priority
    int priority(a) default 0;
}
Copy the code

As you can see from the code, @subscribe is a runtime annotation that applies to a method and has three basic attributes:

  1. ThreadMode () = the thread on which the function runs
public enum ThreadMode {
	Note: If post is in the main thread, avoid time-consuming operations */	
    POSTING,

	/** * Main thread * If the post is in the main thread, running directly in the main thread will block the publishing thread * if the POST is in the child thread, the event is queued for delivery and called back to the main thread using handler */	
    MAIN,
   
	Unlike MAIN, this mode events are always queued for delivery and do not block the publishing thread */	
    MAIN_ORDERED,

	/** * background thread * If the post is in a child thread, it is executed in the same thread as the post */ if the post is the main thread, it is executed sequentially using a single background thread	
    BACKGROUND,

	/** * asynchronous threads * always execute with asynchronous threads, manage asynchronous threads through the thread pool, do not block the publishing thread and main thread */
    ASYNC
}
Copy the code
  1. Sticky () = Sticky event (EventBus default event is non-sticky, which can be specified by this attribute. Generally, EventBus needs to be registered before receiving events, but sticky events can be registered after sending events. Usually used when XActivity jumps to YActivity and passes data.)
  2. Priority () = Event priority
2.3 Sending Events

With the previous two steps completed, you can now send the event where the business needs it, which simulates sending the event when a button is clicked

public class XActivity extends Activity {

    private Button button;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        / / register
        EventBus.getDefault().register(this);
        setContentView(R.layout.activity_x);
        button = (Button) findViewById(R.id.btn_post);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Send events --> Normal
                EventBus.getDefault().post("normal-event");
                // Send events --> stickiness
                //EventBus.getDefault().postSticky("sticky-event");}}); }/** * receives events via annotations that specify the running thread as the main thread **@paramEvent The event object */
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void updateUI(String event) {
        / / update the UIbutton.setText(event); }}Copy the code

We can see that sending events is also very simple, like getting an EventBus instance and calling POST (Object Event) to send an event of type Object. Call postSticky(Object Event) if sticky events need to be sent

2.4 Unbinding registration

Remember to unbind and register to free resources after page destruction or use is complete

public class XActivity extends Activity {

    @Override
    protected void onDestroy(a) {
        super.onDestroy();
        // Unbind registration to release resources
        EventBus.getDefault().unregister(this); }}Copy the code

Just so so. But as an advanced programmer, how can we be content with knowing how to use it? We want to know how it works, why EventBus doesn’t crash when passing big data, and why it’s so easy to specify threads.

3. Explain the EventBus principle

Here suddenly want to help the big factory to clean up, people say that the big factory interview to build aircraft carrier, entry screw, in the interview always like to ask the underlying principle, frame structure and so on. In fact, it is necessary to examine the basic knowledge of a candidate, like this is going to analyze the EventBus principle, to see what the officer can think of at this moment. How do you think it will work?

Some students may be a face meng force, feel very hanging a frame, should be very difficult, what have no clue;

EventBus is implemented in observer mode and receives events via annotations.

So we guess: the first must be related to annotations, the knowledge of annotations you have to understand it! And then the use of annotations. How do annotations work? General is tie-in reflex ah, this reflex you have to meet! Advanced and APT, this APT you know? See this is why big factory want to ask you frame principle, all is to inspect your basic knowledge, this all don’t understand how you see frame source code? How do you get a high salary in BAT?

Let’s take a look at the source code for EventBus and see how it works

First we use the steps to look at the source code analysis:

3.1 EventBus. GetDefault (). The register (the Object the subscriber);

   /** * Register object */			
  public void register(Object subscriber) { Class<? > subscriberClass = subscriber.getClass();// Get the class of the current object
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);// Get the list of methods in the current class that use the @SUBSCRIBE annotation ->> Analysis point 1
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);// Traverse the annotation method, subscribe to the current object ->> Analysis point 2}}}Copy the code

Analysis Point 1: List of ways to get the @SUBSCRIBE annotation by reflection

 private static finalMap<Class<? >, List<SubscriberMethod>> METHOD_CACHE =new ConcurrentHashMap<>();// Cache Map with @SUBSCRIBE in the current object (e.g. XActivity), key= current object, value= list of annotated methods
 List<SubscriberMethod> findSubscriberMethods(Class
        subscriberClass) {
		// Check to see if there is a list of annotation methods for the current object in the cache, if so, return directly
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if(subscriberMethods ! =null) {
            return subscriberMethods;
        }

        if (ignoreGeneratedIndex) {
            subscriberMethods = findUsingReflection(subscriberClass);/ / ignore irrelevant information eventually call findUsingReflectionInSingleClass ()
        } else {
            subscriberMethods = findUsingInfo(subscriberClass);/ / ignore irrelevant information eventually call findUsingReflectionInSingleClass ()
        }
        if (subscriberMethods.isEmpty()) {// If the annotation method is not found, throw an exception. This is why trying register in a class that does not use @subscribe results in an error
            throw new EventBusException("Subscriber " + subscriberClass
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            METHOD_CACHE.put(subscriberClass, subscriberMethods);// Cache Map, key= current object, value= list of annotation methods
            returnsubscriberMethods; }}/ / ignore irrelevant information eventually call findUsingReflectionInSingleClass ()
    private List<SubscriberMethod> findUsingReflection(Class
        subscriberClass) {
		// Ignore the code
        findUsingReflectionInSingleClass(findState);
    }

	/ / ignore irrelevant information eventually call findUsingReflectionInSingleClass ()
 	private List<SubscriberMethod> findUsingInfo(Class
        subscriberClass) {
		// Ignore the code
        findUsingReflectionInSingleClass(findState);
    }
   
Copy the code

Finally get a list of methods for the @SUBSCRIBE annotation

  // Use reflection lookup in a single class
  private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            methods = findState.clazz.getDeclaredMethods();/ / reflection for all methods (public, private, protected, the default type)
        } catch (Throwable th) {
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
		// We iterate through all the methods in this class, then we just get the method we want (via @SUBSCRIBE) and parse the information about that method (method name, method modifiers, parameters, and annotation information)
        for (Method method : methods) {
            int modifiers = method.getModifiers();// Method modifiers
            if((modifiers & Modifier.PUBLIC) ! =0 && (modifiers & MODIFIERS_IGNORE) == 0) {// The method modifier type must be public= common && simultaneously (cannot be abstract, cannot be static, cannot be bridged, Not SYNTHETIC) (MODIFIERS_IGNORE = = Modifier. The ABSTRACT | Modifier. The STATIC | BRIDGE | SYNTHETIC)Class<? >[] parameterTypes = method.getParameterTypes();// Array of method parameter types (parameter types)
                if (parameterTypes.length == 1) {// a method that takes only one parameter
					// Get information about the annotation @subscribe
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if(subscribeAnnotation ! =null) {// The annotation is not empty, indicating that this method is the @SUBSCRIBE annotation method, which is the target method we are looking forClass<? > eventType = parameterTypes[0];// Get the type of the first parameter (e.g., event.class, string.class)
                        if (findState.checkAdd(method, eventType)) {// check whether the parameter meets the condition
                            ThreadMode threadMode = subscribeAnnotation.threadMode();Subscribe(threadMode = threadmode.main)
							// Get additional annotation information (priority= priority, sticky= sticky event) and then build a SubscriberMethod object to save to the list
                            findState.subscriberMethods.add(newSubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky())); }}}else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {// If strict validation is enabled, an exception will be thrown if the @SUBSCRIBE method argument exceeds 1.
                    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)) {// If strict validation is enabled, an error will be reported if @Subscribe uses non-public, static, or abstract modifiers
                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

We can see that this step is to use reflection and annotation knowledge to get the @SUBSCRIBE annotation Method in the registered object, and then add the annotation information (running thread, stickiness, event priority), Method, What do we get at this point when the event type is encapsulated as a SubscriberMethod and stored in the subscriberMethods List

?

public class XActivity extends Activity {

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void updateUI(String event) {}@Subscribe(threadMode = ThreadMode.ASYNC)
    public void updateUI2(int event) {}@Subscribe(threadMode = ThreadMode.POSTING)
    public void updateUI3(Object event) {}}Copy the code

In simple terms, we get all the @SUBSCRIBE method information from the registered object (XActivity) and encapsulate it in a list. Then we iterate through the method information and bind it to the registered object (XActivity)

Analysis of point 2

	for (SubscriberMethod subscriberMethod : subscriberMethods) {
		subscribe(subscriber, subscriberMethod);// Traverse the annotation method and subscribe to the current object
	}

   // Bind SubscriberMethod to the current object
   private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {

		/** * 1. Store all enclosing objects of annotation methods whose parameters are event (including the annotation method's class) * for example: So Activity1 has annotation methods method1(String MSG),method2(String MSG) so Activity1 and method1 will be packaged as one object, Activity1 and method2 will package an object to save * * map.put(" string.class ", List(Subscription (Activity1& M1),Subscription (Activity1& M2),Subscription (Activity2& M1)); * map.put(" int. class", List(Subscription (Activity1&m1),Subscription (Activity1&m2))); * Note: The registered objects here are actually annotation methods and */	
		/** * cache set key = event object type (event.class), value = all parameters of the event annotation method encapsulation object (including the annotation method of the class) * can be interpreted as String. There are multiple Activity1, Activity2... There is a method for receiving (can be combined with broadcast understanding, one action, can be received by multiple broadcast receivers) */	
		private finalMap<Class<? >, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType; Class<? > eventType = subscriberMethod.eventType;// Get the event object type, which is the parameter type of the annotation method
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);// Build a new registration object (understand as encapsulated activity&Method object)
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);// Get the list of registered objects in the cache based on the event object type Key
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<>();
            subscriptionsByEventType.put(eventType, subscriptions);// If empty, add to map
        } else {
            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++) {
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);// Insert the current registered object into the appropriate position according to the event priority
                break; }}/** * Step 1 Summarize **@Subscribe(threadMode = threadmode.main) * public void updateUI(String event) {*} * All wrapped objects with method arguments of String are held as keys according to String.class List(Subscription (Activity1& M1),Subscription (Activity1&m2),Subscription (Activity2& M1)) * After post(String event), we can find all registered objects based on the string. class as the key, and then call each of these objects one by one@SubscribeMethods * /	


		/** * 2. Save all event types based on the current Activity as the key. Put ("Activity1",List(string.class, int.class..) ); * map.put("Activity2",List(String.class,Int.class..) ); * /	
		/** * key = current registered object, value = list of event object types * can be interpreted as multiple annotation methods in Activity1, their event types are string.class, int.class */	
 		private finalMap<Object, List<Class<? >>> typesBySubscriber; List<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);// Add the event type


		So far, the event types (also method parameter types) of all annotated methods in this class are stored according to the current registered object Activity as the key. * 1. Used to query whether they have been registered the current Object isRegistered (Object the subscriber) {return typesBySubscriber. Either containsKey (the subscriber); } * 2. When untying and registering unregister(Object Subscriber), all event types can be found (combined with subscriptionsByEventType), then the list of registered objects can be found according to the event, and the currently registered Object */ can be removed	



		/** * 3. Determine whether the annotation method in the current registered object is a sticky event. If it is a sticky event, fetch the sticky event related information */ from the sticky event cache	
		/** * Sticky events are stored separately in a Map. This is why you can send sticky events first and then receive them in the register. Because they are stored in the cache Map, as long as they are not cleared, It's always * map.put(" string. class", Object sticky event 1); * map.put(" string. class", Object sticky event 2); * map.put("Int. Class ", Object sticky event 1); * map.put("Int. Class ", Object sticky event 2); * /	
 		private finalMap<Class<? >, Object> stickyEvents;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);// Find sticky events in the sticky map based on the event type
                checkPostStickyEventToSubscription(newSubscription, stickyEvent);// Call the post event, call the corresponding annotation method, so that the annotation method can receive the previously sent sticky event}}}Copy the code

According to analysis point 2, we can know that in the registration process, the list of annotation methods obtained is traversed one by one, and then two important maps are constructed. Data is saved in the map and retrieved when needed.

Analysis point 1 and analysis point 2 constitute the register(Object Subscriber) method. The main content of this method is 1. Get the @subscribe method from the registered object, stored in List 2. Iterate through the list of annotation methods, binding them to the current object one by one. 2.1 Build a map based on the annotation method event object type

class Activity1{
	@Subscribe(threadMode = ThreadMode.MAIN)
    public void updateUI(String event) {}@Subscribe(threadMode = ThreadMode.ASYNC)
    public void updateUI2(int event) {}}class Activity2{
	@Subscribe(threadMode = ThreadMode.MAIN)
    public void updateUI(String event) {}@Subscribe(threadMode = ThreadMode.ASYNC)
    public void updateUI2(int event) {
    }
}

map.put("Stirng.class", List(Subscription(Activity1&updateUI),Subscription(Activity2&updateUI)));
map.put("Int.class",	List(Subscription(Activity1&updateUI2),Subscription(Activity2&updateUI2)));
Copy the code

Activity1 and Activity2 use the @SUBSCRIBE annotation to receive events of type String and int. map.put(“Stirng.class”, List(Subscription(Activity1&updateUI),Subscription(Activity2&updateUI))); Indicates that multiple activities are bound to events of type String. What does building this map do? Because subsequent post is sent to all annotated methods (updateUI(String event)) in activities that have registered the event type based on the event type.

2.2 According to the registered object (Activity), save the event types existing in the current object and build the Map

class Activity1{
	@Subscribe(threadMode = ThreadMode.MAIN)
    public void updateUI(String event) {}@Subscribe(threadMode = ThreadMode.ASYNC)
    public void updateUI2(int event) {
    }
}

map.put("Activity1", List(String.class,Int.class..) );Copy the code

Put (“Activity1”, List(string.class, int.class..)) ); Indicates that multiple types of events exist in an Acticity1 object. What does it do? 1. Used to query whether the current object has been registered. 2. Used to support unbinding registration and releasing resources.

2.3 If the event received by the current annotation method is a sticky event, the sticky event information will be obtained from the sticky event map cache (postSticky event) according to the parameter type of the current method (that is, the event type). If the sticky event information exists, the current annotation method will be executed (the principle of delayed response event implementation).

As you can see, register() is an important part of the process, storing data for subsequent post events. Then look at the source code of the POST method

3.2 EventBus. GetDefault (). The post (Object event);

Sends events, which can be of any type

	// Send events
    public void post(Object event) {
        PostingThreadState postingState = currentPostingThreadState.get();// Save objects for different threads
        List<Object> eventQueue = postingState.eventQueue;// Event list
        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()) { postSingleEvent(eventQueue.remove(0), postingState);// loop to send a single event}}finally {
                postingState.isPosting = false;
                postingState.isMainThread = false; }}}// Send single event
    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<? > eventClass = event.getClass();// Event object class
        boolean subscriptionFound = false;// Whether the subscription object is found
        if(eventInheritance) { 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);// Send events}}else {
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);// Send events
        }
        if(! subscriptionFound) {// No subscription object was found
            if (logNoSubscriberMessages) {
                logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
            }
            if(sendNoSubscriberEvent && eventClass ! = NoSubscriberEvent.class && eventClass ! = SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event));// No subscription object found, send NoSubscriberEvent}}}// Find the registered objects according to the event type send, and send the events one by one
   private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class
        eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            subscriptions = subscriptionsByEventType.get(eventClass);// Get the registration object added at registration time, that is, look for the collection of activities bound to the eventClass, as you can see here depending on the data prepared by the registration method
        }
        if(subscriptions ! =null && !subscriptions.isEmpty()) {// Loop through all registered objects (e.g. Activity1,activity2..)
            for (Subscription subscription : subscriptions) {
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false;
                try {
                    postToSubscription(subscription, event, postingState.isMainThread);// Send events to registered objects (an activity)
                    aborted = postingState.canceled;
                } finally {
                    postingState.event = null;
                    postingState.subscription = null;
                    postingState.canceled = false;
                }
                if (aborted) {
                    break; }}return true;
        }
        return false;
    }

	// Send event thread processing, queue processing
   private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:If post is in the main thread, avoid time-consuming operations
                invokeSubscriber(subscription, event);// Direct reflection calls the method
                break;
            case MAIN:/ / main thread
                if (isMainThread) {// If post is in the main thread, running directly in the main thread blocks the publishing thread
                    invokeSubscriber(subscription, event);
                } else {// If the POST is in a child thread, the event is queued for delivery and called back to the main thread using handler
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED:// main thread - queue
				Unlike MAIN, this mode is always queued for delivery and does not block the publishing thread. However, the official source code notes that the implementation is not technically correct and does not follow the ThreadMode definition, so we will leave it at that */	
                if(mainThreadPoster ! =null) {
                    mainThreadPoster.enqueue(subscription, event);// Events are queued for delivery and do not block the publishing thread
                } else {
 					// temporary: technically not correct as poster not decoupled from subscriber
					// Temporary: technically incorrect, not separated from the POST thread
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:// background threads
                if (isMainThread) {// If post is the main thread, a single background thread is used to execute it sequentially
                    backgroundPoster.enqueue(subscription, event);
                } else {// If the post is in a child thread, it is executed in the same thread as the post
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:// Asynchronous thread, always execute with asynchronous thread, manage asynchronous thread through thread pool, do not block publish thread and main thread
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: "+ subscription.subscriberMethod.threadMode); }}// Finally execute the method by calling method.invoke(method object, method parameter) via reflection
   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); }}Copy the code

Post method code is very simple, follow the comments along the main line, first through the event type, find the registered list, (registration list added at the time of each registration), find the registration list after sending the current event, how to send the current event? Find the method to which the object is registered, use the thread specified by method, do some thread switching as needed, and invoke Method.invoke (method object, method parameter) via reflection. That’s it.

What about sending sticky events

  // Send sticky events
  public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);// Save sticky events to the cache map
        }
        // Call the post method
        post(event);
    }

Copy the code

So easy. Compared with ordinary events, there is one more step to save sticky events in the cache map. The original principle of sticky events is so simple.

3.3 EventBus. GetDefault (). The unregister (Object the subscriber);

    // Unbind registration, release resources, etc
    public synchronized void unregister(Object subscriber) { List<Class<? >> subscribedTypes = typesBySubscriber.get(subscriber);// Find the list of event types based on the current object
        if(subscribedTypes ! =null) {
            for(Class<? > eventType : subscribedTypes) { unsubscribeByEventType(subscriber, eventType);// Unbind registered objects by iterating through each event type
            }
            typesBySubscriber.remove(subscriber);
        } else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: "+ subscriber.getClass()); }}// Unbind registered objects according to the event type
 	private void unsubscribeByEventType(Object subscriber, Class
        eventType) {
        List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);// Get the list of registered objects based on the event type
        if(subscriptions ! =null) {
            int size = subscriptions.size();
            for (int i = 0; i < size; i++) {// Iterate over the registered list
                Subscription subscription = subscriptions.get(i);
                if (subscription.subscriber == subscriber) {// The registered list is equal to the current object
                    subscription.active = false;
                    subscriptions.remove(i);// Remove the current object from the listi--; size--; }}}}Copy the code

Unbinding registration is also simple. First, find all event types in the class by the current object, and then find a list of objects that have registered the event based on an event type (e.g., Activity1, Activity2..). , and then compares whether the current object exists in the list. If so, it is removed from the list to release resources

The above is the source code analysis of EventBus, combined with the annotation, I believe I can understand its principle and thought. Because the analysis needs to grasp the main line not to spread out, ignoring some code, it is best to open the source code according to the article to have a look, deepen the impression. The article is based on EventBus 3.1.1

1. Why does EventBus not crash when transferring big data? The underlying parcel object actually has a “Binder Transaction buffe” buffer between different activities, and the size of the buffer is limited to 1M. EventBus does not exist to put data in any buffer, ordinary events directly to the event object, find the need to call the method directly call, simple method parameters pass; Sticky events, they cache data in memory, they store it in a map, unless it’s a memory explosion, there’s no size limit. So EventBus can deliver big data

2. How does EventBus specify the thread that receives the event method? Analysis: EventBus uses annotations to specify the thread that receives the event, stores the annotation information during registration, gets the annotation information saved during registration when the event is posted (the thread), and performs different processing through different threadModes, such as enabling the worker thread to execute the method that receives the event. Alternatively, events can be run on the main thread by means of a handler that specifies the thread to run the method

Well, you feel good, you know how to use it, you know how to do source code analysis, and you know how to answer common interview questions. If an interviewer suddenly asks you: Can you write an EventBus framework yourself?

What the X ? After analyzing the source code, you still want to give you a framework? That’s right, an EventBus framework! Don’t virtual, source code analysis out, the idea has been known, hand lifting framework but so, to follow the rascal brother stem it!

4. Hand lift EventBus framework

Of course, the EventBus framework is relatively complete. The hand lift framework mentioned here is a simple version to achieve the main functions. By doing it yourself, you can deeply understand the framework ideas and deepen your understanding.

Let’s implement the main core logic based on the various interfaces provided by EventBus, starting with REventBus

/** * Easy version EventBus */
public class REventBus {

    static volatile REventBus mInstance;
   
    private REventBus(a) {}/** * double check **@return* /
    public static REventBus getDefault(a) {
        if (mInstance == null) {
            synchronized (REventBus.class) {
                if (mInstance == null) {
                    mInstance = newREventBus(); }}}return mInstance;
    }

    /** * Register object **@paramSubscriber Subscription object */
    public void register(Object subscriber) {}/** * Unbind registration to free resources **@paramSubscriber Subscription object */
    public void unregister(Object subscriber) {}/** * Send event **@paramThe event event * /
    public void post(Object event) {}/** ** sends sticky events **@paramThe event event * /
    public void postSticky(Object event) {}/** * Whether an object is subscribed to **@paramSubscriber Subscription object *@return* /
    public boolean isRegistered(Object subscriber) {
        return false; }}Copy the code

Custom annotations (I owe an article on annotations for now, and write a supplement later)

@Retention(RetentionPolicy.RUNTIME)// Run the annotations
@Target({ElementType.METHOD})// apply to the method
public @interface Subscribe {
    /** * events run thread */
    ThreadMode threadMode(a) default ThreadMode.POSTING;

    /** * whether the event is sticky */
    boolean sticky(a) default false;
}

Copy the code

ThreadMode

public enum ThreadMode {
    /** * call thread */
    POSTING,

    /** ** main thread */
    MAIN
}
Copy the code

These are the basic classes for EventBus, and the basic API has been set up in REventBus, so let’s take it step by step

4.1 Manual EventBus framework –> Register (Object Subscriber)

Recall the EventBus ->> Register () principle

  1. Gets information about the subscription method (annotation method) in the subscription object and encapsulates it as an objectSubscriberMethod
  // The object encapsulated by the subscription method
  class SubscriberMethod {
        Object subscriber;/ / subscriber
        Method method;// Subscribe method
        ThreadMode threadMode;// The subscription method runs the threadClass<? > eventType;// Event type class
        boolean sticky;// Whether it is sticky

        public SubscriberMethod(Object subscriber, Method method, Class<? > eventType, ThreadMode threadMode,boolean sticky) {
            this.subscriber = subscriber;
            this.method = method;
            this.eventType = eventType;
            this.threadMode = threadMode;
            this.sticky = sticky; }}Copy the code

Returns a list of annotation methods from the subscription object.

   /** * Gets information about annotation methods in the current object **@param subscriber
     * @return* /
    private List<SubscriberMethod> getSubscriberMethod(Object subscriber) {
        List<SubscriberMethod> list = newArrayList<>(); Class<? > subscriberClass = subscriber.getClass();// Register the object's class
        Method[] methods = subscriberClass.getDeclaredMethods();// Get methods for all the modifier types
        for (Method method : methods) {
            Subscribe subscribe = method.getAnnotation(Subscribe.class);// Get method annotation information
            if (subscribe == null) {// If empty, this method is not an annotation method
                continue; } Class<? >[] types = method.getParameterTypes();Int -> int. Class
            if(types.length ! =1) {// A method that takes only one parameter (see EventBus)
                throw new RuntimeException("REventBus accepts only one parameter!");
            }

            ThreadMode threadMode = subscribe.threadMode();// get the annotation information -> ThreadMode
            boolean sticky = subscribe.sticky();// Get annotation information -> sticky

            // Build the SubscriberMethod that encapsulates the annotation information, method parameters, and method objects
            SubscriberMethod subscriberMethod = new SubscriberMethod(subscriber, method, types[0], threadMode, sticky);
            list.add(subscriberMethod);// Add to the list
        }
        return list;
    }
Copy the code
  1. Bind the subscription method to the subscription object. Special processing when the subscription mode is sticky, get the event from the sticky event set and execute the corresponding method
    // A list of subscribers to an event object
    privateMap<Class<? >, List<SubscriberMethod>> subscriberMethodMap;// A subscriber's list of events
    privateMap<Object, List<Class<? >>> subscriberEventMap;// Sticky event set key = event.class
    privateMap<Class<? >, Object> stickyEventMap;/** * bind the subscription method to the subscription object **@paramSubscriber Subscription object *@paramSubscriberMethod Subscribes method */
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<? > eventType = subscriberMethod.eventType; List<SubscriberMethod> subscriberMethods = subscriberMethodMap.get(eventType);// Gets a list of subscribers to an event object
        if (subscriberMethods == null) {
            subscriberMethods = newArrayList<>(); subscriberMethodMap.put(eventType, subscriberMethods); } subscriberMethods.add(subscriberMethod); List<Class<? >> subscribedEvents = subscriberEventMap.get(subscriber);// Get a subscriber's list of events
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            subscriberEventMap.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);

        if (subscriberMethod.sticky) {// Sticky event method
            Object eventSticky = stickyEventMap.get(eventType);// Sticky event objects
            if(eventSticky ! =null) {// There is a sticky event, call current methodpostEvent(subscriberMethod, eventSticky); }}}Copy the code

In summary, the register method is:

	/** * Register object **@paramSubscriber Subscription object */
    public void register(Object subscriber) {
        List<SubscriberMethod> subscriberMethodList = getSubscriberMethod(subscriber);// Get the annotation method in the subscriber
        if (subscriberMethodList == null || subscriberMethodList.isEmpty()) return;/ / fault-tolerant
        for (SubscriberMethod subscriberMethod : subscriberMethodList) {
            subscribe(subscriber, subscriberMethod);// Bind subscription objects and subscription methods}}Copy the code

EventBus –> POST (Object Event)

Recall post principle:

Get all the subscribed methods as events, and then send the events one by one. The principle of sending the events is to implement the method call through reflection, while doing thread switching according to the specified thread mode

    /** * Send event **@paramThe event event * /
    public void post(Object event) {
        List<SubscriberMethod> subscriberMethodList = subscriberMethodMap.get(event.getClass());// Get all subscribers to an event
        if (subscriberMethodList == null && subscriberMethodList.isEmpty()) return;/ / fault-tolerant
        for (SubscriberMethod subscriberMethod : subscriberMethodList) {
            postEvent(subscriberMethod, event);// Send events}}/** * Send event **@paramSubscriberMethod Object encapsulated by the subscription method *@paramEvent Event (method parameter) */
    private void postEvent(SubscriberMethod subscriberMethod, Object event) {
        Object subscriber = subscriberMethod.subscriber;/ / subscriber
        ThreadMode threadMode = subscriberMethod.threadMode;// The thread of execution for the subscription method
        Method method = subscriberMethod.method;// Subscribe method
        switch (threadMode) {
            case POSTING:/ / post thread
                invokeMethod(subscriber, method, event);
                break;
            case MAIN:/ / main thread
                // Implement the main thread call method through handler
                Object[] objects = new Object[]{subscriber, method, event};
                mainHandler.sendMessage(mainHandler.obtainMessage(1, objects));
                break; }}/** * executes the method ** via reflection@paramSubscriber method object *@paramMethod Method object *@paramThe event method argument */
    private void invokeMethod(Object subscriber, Method method, Object event) {
        try {
            method.invoke(subscriber, event);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch(InvocationTargetException e) { e.printStackTrace(); }}/** * Handler */
    class MainHandler extends Handler {

        public MainHandler(a) {
            super(Looper.getMainLooper());
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Object[] obj = (Object[]) msg.obj;
            Object subscriber = obj[0];/ / class object
            Method method = (Method) obj[1];/ / method
            Object event = obj[2];// Method parameters
            // reflection execution methodinvokeMethod(subscriber, method, event); }}Copy the code

4.3 Manual EventBus –>unregister(Object Subscriber)

Recall how untying registration works:

Get the subscriber’s event list from the current object, find the current subscription object in the subscriber list, and remove it. Then clear the list of events for the current subscriber

   /** * Unbind registration to free resources **@paramSubscriber Subscription object */
    public void unregister(Object subscriber) { List<Class<? >> eventClass = subscriberEventMap.get(subscriber);// Get a subscriber's list of events
        if (eventClass == null || eventClass.isEmpty()) return;/ / fault-tolerant
        for(Class<? > clazz : eventClass) { List<SubscriberMethod> subscriberMethodList = subscriberMethodMap.get(clazz);// Gets a list of subscribers to an event object
            for (int i = 0; i < subscriberMethodList.size(); i++) {
                if (subscriberMethodList.get(i).subscriber == subscriber) {// Find the current subscription object in the subscriber list
                    subscriberMethodList.remove(i);/ / remove it
                }
            }
        }
        subscriberEventMap.remove(subscriber);// Removes the current subscriber's event list
    }
Copy the code

REventBus view

/** * Easy version EventBus */
public class REventBus {

    static volatile REventBus mInstance;
    // Main thread Handler
    private MainHandler mainHandler;
    // A list of subscribers to an event object
    privateMap<Class<? >, List<SubscriberMethod>> subscriberMethodMap;// A subscriber's list of events
    privateMap<Object, List<Class<? >>> subscriberEventMap;// Sticky event set key = event.class
    privateMap<Class<? >, Object> stickyEventMap;private REventBus(a) {
        subscriberMethodMap = new HashMap<>();
        subscriberEventMap = new HashMap<>();
        stickyEventMap = new HashMap<>();
        mainHandler = new MainHandler();
    }

    /** * double check **@return* /
    public static REventBus getDefault(a) {
        if (mInstance == null) {
            synchronized (REventBus.class) {
                if (mInstance == null) {
                    mInstance = newREventBus(); }}}return mInstance;
    }

    /** * Register object **@paramSubscriber Subscription object */
    public void register(Object subscriber) {
        List<SubscriberMethod> subscriberMethodList = getSubscriberMethod(subscriber);// Get the annotation method in the subscriber
        if (subscriberMethodList == null || subscriberMethodList.isEmpty()) return;/ / fault-tolerant
        for (SubscriberMethod subscriberMethod : subscriberMethodList) {
            subscribe(subscriber, subscriberMethod);// Bind subscription objects and subscription methods}}/** * Gets information about annotation methods in the current object **@param subscriber
     * @return* /
    private List<SubscriberMethod> getSubscriberMethod(Object subscriber) {
        List<SubscriberMethod> list = newArrayList<>(); Class<? > subscriberClass = subscriber.getClass();// Register the object's class
        Method[] methods = subscriberClass.getDeclaredMethods();// Get methods for all the modifier types
        for (Method method : methods) {
            Subscribe subscribe = method.getAnnotation(Subscribe.class);// Get method annotation information
            if (subscribe == null) {// If empty, this method is not an annotation method
                continue; } Class<? >[] types = method.getParameterTypes();Int -> int. Class
            if(types.length ! =1) {// A method that takes only one parameter (see EventBus)
                throw new RuntimeException("REventBus accepts only one parameter!");
            }

            ThreadMode threadMode = subscribe.threadMode();// get the annotation information -> ThreadMode
            boolean sticky = subscribe.sticky();// Get annotation information -> sticky

            // Build the SubscriberMethod that encapsulates the annotation information, method parameters, and method objects
            SubscriberMethod subscriberMethod = new SubscriberMethod(subscriber, method, types[0], threadMode, sticky);
            list.add(subscriberMethod);// Add to the list
        }
        return list;
    }

    /** * bind the subscription method to the subscription object **@paramSubscriber Subscription object *@paramSubscriberMethod Subscribes method */
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<? > eventType = subscriberMethod.eventType; List<SubscriberMethod> subscriberMethods = subscriberMethodMap.get(eventType);// Gets a list of subscribers to an event object
        if (subscriberMethods == null) {
            subscriberMethods = newArrayList<>(); subscriberMethodMap.put(eventType, subscriberMethods); } subscriberMethods.add(subscriberMethod); List<Class<? >> subscribedEvents = subscriberEventMap.get(subscriber);// Get a subscriber's list of events
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            subscriberEventMap.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);

        if (subscriberMethod.sticky) {// Sticky event method
            Object eventSticky = stickyEventMap.get(eventType);// Sticky event objects
            if(eventSticky ! =null) {// There is a sticky event, call current methodpostEvent(subscriberMethod, eventSticky); }}}/** * Unbind registration to free resources **@paramSubscriber Subscription object */
    public void unregister(Object subscriber) { List<Class<? >> eventClass = subscriberEventMap.get(subscriber);// Get a subscriber's list of events
        if (eventClass == null || eventClass.isEmpty()) return;/ / fault-tolerant
        for(Class<? > clazz : eventClass) { List<SubscriberMethod> subscriberMethodList = subscriberMethodMap.get(clazz);// Gets a list of subscribers to an event object
            for (int i = 0; i < subscriberMethodList.size(); i++) {
                if (subscriberMethodList.get(i).subscriber == subscriber) {// Find the current subscription object in the subscriber list
                    subscriberMethodList.remove(i);/ / remove it
                }
            }
        }
        subscriberEventMap.remove(subscriber);// Removes the current subscriber's event list
    }

    /** * Send event **@paramThe event event * /
    public void post(Object event) {
        List<SubscriberMethod> subscriberMethodList = subscriberMethodMap.get(event.getClass());// Get all subscribers to an event
        if (subscriberMethodList == null && subscriberMethodList.isEmpty()) return;/ / fault-tolerant
        for (SubscriberMethod subscriberMethod : subscriberMethodList) {
            postEvent(subscriberMethod, event);// Send events}}/** ** sends sticky events **@paramThe event event * /
    public void postSticky(Object event) {
        // Save sticky events
        stickyEventMap.put(event.getClass(), event);
        / / call the post
        post(event);
    }

    /** * Send event **@paramSubscriberMethod Object encapsulated by the subscription method *@paramEvent Event (method parameter) */
    private void postEvent(SubscriberMethod subscriberMethod, Object event) {
        Object subscriber = subscriberMethod.subscriber;/ / subscriber
        ThreadMode threadMode = subscriberMethod.threadMode;// The thread of execution for the subscription method
        Method method = subscriberMethod.method;// Subscribe method
        switch (threadMode) {
            case POSTING:/ / post thread
                invokeMethod(subscriber, method, event);
                break;
            case MAIN:/ / main thread
                // Implement the main thread call method through handler
                Object[] objects = new Object[]{subscriber, method, event};
                mainHandler.sendMessage(mainHandler.obtainMessage(1, objects));
                break; }}/** * executes the method ** via reflection@paramSubscriber method object *@paramMethod Method object *@paramThe event method argument */
    private void invokeMethod(Object subscriber, Method method, Object event) {
        try {
            method.invoke(subscriber, event);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch(InvocationTargetException e) { e.printStackTrace(); }}/** * Whether an object is subscribed to **@paramSubscriber Subscription object *@return* /
    public boolean isRegistered(Object subscriber) {
        return subscriberEventMap.containsKey(subscriber);
    }

    /** * encapsulates the subscription method */
    class SubscriberMethod {
        Object subscriber;/ / subscriber
        Method method;// Subscribe method
        ThreadMode threadMode;// The subscription method runs the threadClass<? > eventType;// Event type class
        boolean sticky;// Whether it is sticky

        public SubscriberMethod(Object subscriber, Method method, Class<? > eventType, ThreadMode threadMode,boolean sticky) {
            this.subscriber = subscriber;
            this.method = method;
            this.eventType = eventType;
            this.threadMode = threadMode;
            this.sticky = sticky; }}/** * Handler */
    class MainHandler extends Handler {

        public MainHandler(a) {
            super(Looper.getMainLooper());
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Object[] obj = (Object[]) msg.obj;
            Object subscriber = obj[0];/ / class object
            Method method = (Method) obj[1];/ / method
            Object event = obj[2];// Method parameters
            // reflection execution methodinvokeMethod(subscriber, method, event); }}}Copy the code

This class is designed to analyze and understand the framework of EventBus. Although it works, it should not be applied directly to projects. After all, it is a simple version and there are many improvements and fault tolerance to be made

5. EventBus optimization

After we had confidently whipped through the EventBus framework, the interviewer asked us another question: Did EventBus have performance issues? It is implemented using reflection, can it be further optimized?

Ah ~ Xiba! I don’t want this offer, this is not forced death! Don’t offer? How is that possible? I not only took the offer, I also want to make a price

First of all, how much worse is a reflection call than a direct call? A lot of people hear reflection on the performance is not good, a stick killed, but how much difference? What numbers are there to talk about? I owe another article here, and then find time to analyze and write an article.

Reflection is certainly a performance problem, how much difference will not be discussed, if you do not use reflection can be improved? If we could avoid the performance problems of reflection calling methods by calling them directly, we could improve performance.

If we know what the method is going to look like, we can solve this problem by writing it ahead of time instead of reflecting it at runtime. There is also a way to implement it ahead of time, that is apt! Create a “method copy” at compile time to provide code calls.

Due to space reasons, here is another article about the use of APT!

Read down an article, even if you do not fully grasp, at least know that there are several knowledge points to learn:

  1. Java Basics – Reflection
  2. annotations
  3. Design Pattern -> singleton
  4. Apt technology
  5. ThreadLocal omitted from the source code

Reflection related usage directly wrote a blog, the rest of the content of a few temporary owe, the follow-up to each write an article to fill!