EventBus, which is frequently used in Android development, helps us simplify data transmission in many complex scenarios. Of course, it is also an essential question in Android junior high school senior interview.
However, for the use of EventBus, most people may not use it correctly. Today, let me take you to use it correctly and really understand EventBus from the source code level.
Eventbus project address: https://github.com/greenrobot/EventBus
1. EventBus usage
Step 1: Register
//register EventBus.getDefault().register(this) //De registration EventBus.getDefault().unregister(this)
For registration and de registration operations, you should pay attention to the use to ensure that the life cycle is consistent. For example, if you register in onResume, you should de register in onPause.
Step 2: set the method and add comments
@Subscribe(threadMode = ThreadMode.MAIN) fun onReceiverEventTwoNormal(event: FragmentEvent) { if (event.isOne()) { bindView { tvReceiver.text = "Received content: ${event.eventMsg}" } } }
Step 3: use the post method event
EventBus.getDefault().post(FragmentEvent.postOne())
Here, I believe it is consistent with what you use, and most of the projects I have seen are set like this.
But if I only say these words, I believe I don't need to write this article.
Have you ever encountered such a problem in your interview?
req: what is the difference between EventBus 2.x and eventbus 3.x?
I believe many people who have memorized the interview questions can blurt it out. EventBus2.x uses reflection, while 3.0 uses annotation method and adds index for optimization. OK ~, it looks like perfect, but how do you implement the index? And is the above code really more optimized than the 2.0 code?
If I tell you that the performance of the above processing method is worse than EventBus 2.x, I don't know what you think. Directly above.
Here is the correct way to use it. After analyzing the source code, I believe you will understand it.
Add in the build.gradle file under the project or module:
kapt { arguments { arg('eventBusIndex', 'org.fireking.event.MyEventBusIndex') } } dependencies{ kapt 'org.greenrobot:eventbus-annotation-processor:3.0.0' ... }
rebuild the code, and the org.firefighting.event.myeventbusindex file will be generated under build/generated/source/kapt/debug /. After opening, it is found that it is actually the class identified by the subscribe annotation.
... private static final Map<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), })); } ...
Smart, you must have thought that kapt is used directly here. The previously annotated class and method relationships are directly generated and saved in the map. In the future, it should be taken directly from here, and there is no need for reflection search. As for whether this is the case, the source code will explain later.
In application, add a new setting
//Register the method generated above into the default eventBus configuration item, and you can use it directly as before. EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
2. Conventional principle analysis
For the analysis of Event implementation, we directly analyze it according to the common methods and processes.
2-1,EventBus.getDefault().register(Object subscriber)
public void register(Object subscriber) { //Find the class based on the currently passed in object Class<?> subscriberClass = subscriber.getClass(); //Find all subscription methods subscribermethods under the current class object List<SubscriberMethod> subscriberMethods =subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { //Traverse all subscription methods for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } }
2-1-1,subscriberMethodFinder#findSubscriberMethods
//It is used to save the class and the collection of all subscription method relationships in the class private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>(); ... //Subscriber method is the saved method information using subscriber annotation public class SubscriberMethod { //Method of using Subscriber annotation final Method method; //Corresponding to threadMode in annotation final ThreadMode threadMode; //Corresponding to the parameters passed by the method, EventBus is limited to only one parameter, which corresponds to the first parameter final Class<?> eventType; //Event priority final int priority; //Is it sticky final boolean sticky; ...
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { //Find out whether cached data already exists from method cachee List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); //If it is found that it has been cached, it is returned directly if (subscriberMethods != null) { return subscriberMethods; } //ignoreGeneratedIndex is used to distinguish whether to ignore the index generated by the Annotation Processor //If omitted, the reflection method will be used by default, otherwise the generated index method will be used for searching if (ignoreGeneratedIndex) { //Find using reflection subscriberMethods = findUsingReflection(subscriberClass); } else { //Use the index method to find. If it cannot be found in the index, use reflection to find again subscriberMethods = findUsingInfo(subscriberClass); } //If EventBus.getDefault().register() is set, //However, 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 { //After retrieval, save the relationship between the class and @ Subscriber method in method cache METHOD_CACHE.put(subscriberClass, subscriberMethods); //Return subscriberMethods return subscriberMethods; } }
- Private list < subscribermethod > findusingreflection (class <? > subscriberclass) reflection method
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) { //Get FindState object from cache pool of size 4 FindState findState = prepareFindState(); //Initializes the FindState object state findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { //Find using reflection findUsingReflectionInSingleClass(findState); //Find from parent class findState.moveToSuperclass(); } return getMethodsAndRelease(findState); }
Core lookup method findUsingReflectionInSingleClass
private void findUsingReflectionInSingleClass(FindState findState) { Method[] methods; try { //Find only public methods to increase the search speed // 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) { //Get the method modifier without anything. It is 0, public is 1, private is 2, protected is 4, static is 8 and final is 16. int modifiers = method.getModifiers(); //The modifier is public if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { //Gets the class object type of the parameter Class<?>[] parameterTypes = method.getParameterTypes(); //Only one parameter of eventBus can be used. If more than one parameter is used, an exception will be thrown if (parameterTypes.length == 1) { //Gets 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(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"); } } } For parent class search, you can see the previous code, which will be clazz Method is replaced with the parent object, continue in while Use in circulation`findUsingReflectionInSingleClass`and`moveToSuperclass`Perform a layer by layer lookup. ```java void moveToSuperclass() { //Returns null if you skip finding the parent class if (skipSuperClasses) { clazz = null; } else { //Return 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; } } } }
Finally, return the found method collection and clear the cache pool getMethodsAndRelease
`findState`When the cache pool is in use, it needs to be released for other operations ```java private List<SubscriberMethod> getMethodsAndRelease(FindState findState) { List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods); //Free cache pool after discovery findState.recycle(); 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; }
Because there are two search methods, reflection method and apt method, apt method will be explained next. As for the use of search at that time, it will also be explained below.
- Private list < subscribermethod > findusinginfo (class <? > subscriberclass) takes precedence over apt method. If it is not found, the search will be reflected
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { //Search from the index information generated by apt findState.subscriberInfo = getSubscriberInfo(findState); //Found from 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 it is not found in the index, it is found through reflection findUsingReflectionInSingleClass(findState); } findState.moveToSuperclass(); } return getMethodsAndRelease(findState); }
- getSubscriberInfo
private SubscriberInfo getSubscriberInfo(FindState findState) { if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) { //Find in parent class SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo(); //If found, return directly if (findState.clazz == superclassInfo.getSubscriberClass()) { return superclassInfo; } } //SubscriberInfoIndex is an interface. The class generated by apt inherits from it if (subscriberInfoIndexes != null) { for (SubscriberInfoIndex index : subscriberInfoIndexes) { SubscriberInfo info = index.getSubscriberInfo(findState.clazz); //If it is found in the index generated by apt, it is returned directly if (info != null) { return info; } } } return null; }
2-1-2,subscribe(subscriber, subscriberMethod)
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { //Get the method parameters of @ Subscribe Class<?> eventType = subscriberMethod.eventType; //Here, a Subscription object is constructed, which encapsulates the Subscription class and the @ Subscribe method in the class, and encapsulates the two into a class Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //subscriptionsByEventType is a Map with @ Subscribe method parameter as key and subscription method collection as value CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); //Check whether it has been registered, and throw an exception for repeated registration if (subscriptions == null) { //A thread safe read / write queue is used here subscriptions = new CopyOnWriteArrayList<>(); //If it has not been registered, all methods of eventType will be changed and counted into a collection //The eventType here is the parameter of @ Subscribe, so it can be understood as putting all registered changed types into one collection, // When unregistering, it will be removed from the collection, so you can realize the function of notifying all registered methods subscriptionsByEventType.put(eventType, subscriptions); } else { //If you have already registered, repeat the registration and 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 according to priority, because priority is compared every time, // Therefore, a priority ranking from high to low is finally formed if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) { subscriptions.add(i, newSubscription); break; } } //In fact, it is a collection of subscriptionsByEventType, // Discovery is only used to assist the isRegistered method to determine whether it has been registered List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); //Here is the sticky event processing, which will be described 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); } } }
The following is a brief summary of the workflow of EventBus#register(Object subscriber)
Using subscriberMethodFinder.findSubscriberMethods and the registered parameter subscriber as the parameter, the query is all the methods in this class that use the @ Subscribe annotation.
The search logic is as follows:
If in method_ If it is found in cache, it will be returned directly. If it cannot be found, judge whether to use apt mode to generate at compile time. If not, use reflection mode to find.
After it is found, it is saved in METHOD_CACHE and return to subsequent use.
After that, traverse the found @ Subscribe annotated method set, take the method parameter as the key, and save all method sets using the method parameter as the value in subscriptionsByEventType.
In fact, the core idea is to take the event object (i.e. parameter) as the key for all @ subscribe annotation methods in the currently registered class, and count the @ subscribe annotation methods using the event object in all found classes in the code into a collection, so as to facilitate the distribution of events in subsequent post s. In fact, it is a very typical subscription consumption model, or observer model.
2-2,unregister(Object subscriber)
public synchronized void unregister(Object subscriber) { //As mentioned above, typesBySubscriber is actually used to help judge whether it has been registered List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber); //If it is already registered if (subscribedTypes != null) { //In register, the class will be used as the key, and all @ Subscribe annotation method parameters (event pairs) in the class will be used as the value for (Class<?> eventType : subscribedTypes) { //Traverse the collection to remove the saved data from the subscriptionsByEventType unsubscribeByEventType(subscriber, eventType); } //Remove the object data key and value from the map typesBySubscriber.remove(subscriber); } else { logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass()); } }
- unsubscribeByEventType(Object subscriber, Class<?> eventType)
The logic here is relatively simple. With the analysis in the register method above, I believe this is easy to understand, so I won't explain it much
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--; } } } }
The following is a brief summary of the Event#unregister(Object subscriber) method:
Two map sets, typesBySubscriber and subscriptionsByEventType, are used here
- The typesBySubscriber collection is actually an event collection that holds all @ Subscribe annotations under the subscriber, where key is the usage class and value is the event collection.
- subscriptionsByEventType is the method collection of all event objects saved. Events and methods have a one to many relationship.
The unRegister operation is to clean up the class and event collection relationship, event and event object method relationship found in the register method to prevent memory leakage caused by object reference.
Due to space reasons, post, sticky messages and other contents will be split into another article