- Message: If you see this article, I hope you can go through the source code, direct reflection or use index will be used in detail analysis!
Benefits and features of EventBus
- You may have a question: There are a lot of event delivery frameworks on the market. Why did you choose EventBus over Otto? Rxbus, etc., can meet the daily development requirements, but the update of the latter two is basically stalled, while EventBus is still in normal update and maintenance, choose to access the largest number of people, at the same time, the most complete function, if you choose to use this framework, naturally understand the operation of the source code is absolutely helpful for debugging!
- We learn knowledge can not simply be able to use it, to know what it is and know why it can progress, improve themselves ~
advantages
- Simplifies communication between components and decouples event sending and receiving
- Good for communication between activities, fragments, background threads, avoid using multiple Intent transmission,handler communication, etc
- Avoid complex, error-prone dependencies and lifecycle issues, such as callbacks after multiple Activity jumps
- Fast and responsive, especially after 3.0 recompilers add indexes
- Lightweight about 50K JAR package
- Used by a large number of apps and verified in practice
- There are many advanced features, such as thread switching mode, subscription priority, etc
features
- Easy to use annotation API: a simple @subscribe annotation on a subscription method is indexed by a compile-time subscriber, so the app doesn’t need to get a subscription through annotation reflection at runtime
- Supports event Posting to the UI thread, regardless of the thread from which the event was sent
- Support event and subscriber inheritance: If event A is the parent of event B, an event of type B will be sent and the event will also be sent to subscribers who are interested in event A. Similar inheritance relationship exists for subscribers. You can disable this function through eventInheritance(False) and only invoke it in this class without calling back the parent interface
- Eventbusbuilder.builder ()
Public EventBusBuild () {// Create an EventBus object by calling build() return new EventBus(this); }Copy the code
- Note: This method allows you to create multiple EventBus objects, each with different configuration information, but each instance of EventBus is independent, that is, each EventBus Eventbus.geteventbus (type).regirst(this).eventBus.geteventBus (type).regirst(this).eventBus.geteventBus (type).regirst(this This type is returned in EventBus.
The construction of EventBus
- Firstly, the meanings of each attribute of this class are analyzed
private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool(); ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE; // The default thread pool, which is a core 0, Max, no cache queue SynchronousQueue; If a large number of events may cause OOM, you can change the thread pool configuration // executorService build, which is mainly used for asynchronous and background event passing / / / / in the subscription method throws an exception, whether print log, annotations can rewrite onSubscriberExceptionEvent listening in class to Boolean logSubscriberExceptions = true; Boolean logNoSubscriberMessages = true; / / in the subscription SubscriberExceptionEvent event method throws an exception, whether to send SubscriberExceptionEvent event Boolean sendSubscriberExceptionEvent = true; Boolean sendNoSubscriberEvent = true; // No event subscriberevent method found. // Whether to throw EventBusException when an exception is thrown in the unsubscribed SubscriberExceptionEvent event method. The default value is false Boolean throwSubscriberException; boolean eventInheritance = true; Boolean ignoreGeneratedIndex; // Send child event; // Send child event; False Boolean strictMethodVerification; // Whether to use reflection to find subscription methods directly, default is false Boolean strictMethodVerification; // Strict method validation for non-annotation index generation: when the method does not conform to the format (public, non-abstract, EventBusException is thrown. The default value is false List<SubscriberInfoIndex> subscriberInfoIndexes. List<Class<? >> skipMethodVerificationForClasses; // Check for methods that start with onEventCopy the code
- Note: Adding an index to EventBus must precede the first eventBus. regirst, so it is normally added in the onCreate method of the Application
// Improve performance: the first point is to turn off the parent class and the interface lookup distribution event (only the parent class is called, the parent interface is not called); // Add index. The principle of adding index is to load the relevant information of registered class in advance at compile time. EventBus.builder().addIndex(new MyEventBusIndex()).eventInheritance(false).installDefaultEventBus(); // Eventbus.getDefault () is generated by the current installDefaultEventBus method and will not be recreated, but EventBus rebuilt by Builder.build () will be invalid!Copy the code
- Here we mainly use the source code of Event3.0, after all, use the new not the old
Note the Subscribe
- Mark subscription methods with annotations: you can also specify threadMode, which represents the thread on which the subscription method is running, sticky, which represents whether it is a sticky event, and priority, which represents the priority (from highest to last, setting can be changed).
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
boolean sticky() default false;
int priority() default 0;
}
Copy the code
- ThreadMode: The thread in which the subscription method runs
- Threadmode. POSTING — POST thread, subscription methods run on the same thread as the publisher (default)
- Threadmode. MAIN — UI MAIN thread, subscription methods run on the MAIN thread and are sent if the UI thread runs directly, if not via Handler callback to the UI thread
- Threadmode. BACKGROUND — BACKGROUND thread, publisher is the main thread, subscription methods run on the newly opened child thread; The publisher is a child thread, and the subscription method runs on the publisher’s thread. (Create child thread from thread pool)
- Threadmode. ASYNC — Asynchronous threads, subscription methods run on newly opened child threads, regardless of which thread the publisher is on (same thread pool)
- Sticky: Consistent with the concept of sticky in Android broadcast, it means that if the broadcast is not registered, the sticky event will be saved in the memory. When the broadcast is registered, the sticky event will be transmitted to the subscriber. That is, the subscriber can receive the sticky event after sending it.
- Priority: subscription priority. The same priority is transmitted according to the subscription order after the event is sent. (Add from large -> small, call method directly polls from 0 -> size to achieve event transitivity, and the event object is immutable but the attribute value is variable, so the value can be changed)
Registration of the register
- EventBus registers with an Activity or Fragment
Public void register(Object subscriber) {//subscriber: this Class<? > subscriberClass = subscriber.getClass(); / / to get this on behalf of the class List of < SubscriberMethod > subscriberMethods = subscriberMethodFinder. FindSubscriberMethods (subscriberClass); Synchronized (this) {for (SubscriberMethod: subscriberMethods) { subscribe(subscriber, subscriberMethod); Public class SubscriberMethod {final Method Method; // Final ThreadMode ThreadMode; On which thread does the subscription method execute final Class<? > eventType; // Final int priority; // Priority final Boolean sticky; /** Used for efficient comparison */ String methodString;Copy the code
- See subscriberMethodFinder. FindSubscriberMethods method how to find the subscription
// The first thing you must do is create the subscriberMethodFinder: in the constructor of EventBus subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes, builder.strictMethodVerification, builder.ignoreGeneratedIndex); // Call EventBus's first cached map set: The key represents the activity or Fragment of the current registered Class. The value represents the collection of subscribed methods in the current Class. >, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>(); List<SubscriberMethod> findSubscriberMethods(Class<? > subscriberClass) { List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods ! // Return return subscriberMethods if the registered classes are already cached; } //ignoreGeneratedIndex indicates that reflection is used, that is, ignoring the MyEventBusIndex class generated by the annotator compiler. The default is false if (ignoreGeneratedIndex) {subscriberMethods = findUsingReflection(subscriberClass); // By reflection call, at run time} else {// We all add the code logic generated by the compiler. / / / / subscription from annotators generated MyEventBusIndex class class subscription method information} the 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 added to the cache after the call is complete; }}Copy the code
- First we’ll look at how to get the subscription method information in a subscription class through reflection, which is explained later, via index
private List<SubscriberMethod> findUsingReflection(Class<? > subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); Findstate while (findState.clazz! = null) { findUsingReflectionInSingleClass(findState); / / find the class of all incident response method findState. MoveToSuperclass (); } return getMethodsAndRelease(findState);} return getMethodsAndRelease(findState); }Copy the code
- Find all event response methods in a class
private void findUsingReflectionInSingleClass(FindState findState) { Method[] methods; Try {/ / get all the methods in the class, including private methods. The methods = findState clazz. GetDeclaredMethods (); } catch (Throwable th) { // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149 methods = findState.clazz.getMethods(); findState.skipSuperClasses = true; } for (Method method : methods) { int modifiers = method.getModifiers(); Get method attributes,private,static, etc. If ((modifiers & Modifier.PUBLIC)! // If the method is public and not abstract,static,bridge, synthetic compiler generated Class<? >[] parameterTypes = method.getParameterTypes(); If (parametertypes. length == 1) {Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); // Get a method to Subscribe if (subscribeAnnotation! = null) { Class<? > eventType = parameterTypes[0]; / / to get event classes if (findState checkAdd (method, eventType)) {ThreadMode ThreadMode = subscribeAnnotation. ThreadMode (); findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky())); / / will all the Subscribe method in a class to join findstate collection}}} else if (strictMethodVerification && method. The 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"); }}}Copy the code
- Let’s look at the attributes of the FindState class
Static class FindState {final List<SubscriberMethod> subscriberMethods = new ArrayList<>(); Final Map<Class, Object> anyMethodByEventType = new HashMap<>(); / / the event type as the key, the method is a collection of information to value the final Map < String, Class > subscriberClassByMethodKey = new HashMap < > (); // Use methodKey as the key, and the subscriber class as the value collection (methodKey below: Final StringBuilder methodKeyBuilder = new StringBuilder(128); Class<? > subscriberClass; // Subscriber Class<? > clazz; boolean skipSuperClasses; // Whether to skip the parent class SubscriberInfo; void initForSubscriber(Class<? // initialize this.subscriberClass = clazz = subscriberClass; skipSuperClasses = false; subscriberInfo = null; } void recycle() { subscriberMethods.clear(); anyMethodByEventType.clear(); subscriberClassByMethodKey.clear(); methodKeyBuilder.setLength(0); subscriberClass = null; clazz = null; skipSuperClasses = false; subscriberInfo = null; } Boolean checkAdd(Method Method, Class<? < span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 20px; font-size: 14px! Important; white-space: inherit! Important;" 1st level with event type only (fast), 2nd level with complete signature when required. // Usually a subscriber doesn't have methods listening to the same Object Existing = anyMethodByEventType. Put (eventType, method); If (existing == null) {return true; If (existing instanceof Method) {// Existing is the same key corresponding to oldValue if (!) {// Existing is the same key corresponding to oldValue if (! checkAddWithMethodSignature((Method) existing, eventType)) { // Paranoia check throw new IllegalStateException(); } // Put any non-Method object to "consume" the existing Method anyMethodByEventType.put(eventType, this); / / do not match the method signature (multiple methods in a class containing the same event class) as a value added FindState} return checkAddWithMethodSignature (method, eventType); }} / / subscription method signatures were used to detect whether can join the subscribe method information list, subscription method method, eventType event class private Boolean checkAddWithMethodSignature (method method, Class<? > eventType) { methodKeyBuilder.setLength(0); methodKeyBuilder.append(method.getName()); methodKeyBuilder.append('>').append(eventType.getName()); / / the method signature methodkey how to generate the String methodkey = methodKeyBuilder. ToString (); Class<? > methodClass = method.getDeclaringClass(); // Class<? > methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass); If (methodClassOld = = null | | methodClassOld. IsAssignableFrom (methodClass)) / / methodClass methodClassOld subclasses or subinterface { Return true if there is no subscription method signed by the same method, or if the class of the previously saved subscription method is a subclass of the class of the current subscription method to be added. } else {/ / same method signatures, and methodOld is subclass / / subscriberClassByMethodKey preserve only the most lower level of fathers and sons inheritance subclasses, the purpose is to registered in a subclass to monitor the event, if the parent class method have the same incident response, Should call subclasses override a method subscriberClassByMethodKey. Put (methodKey methodClassOld); return false; } } void moveToSuperclass() { if (skipSuperClasses) { clazz = null; } else { clazz = clazz.getSuperclass(); String clazzName = clazz.getName(); /** Skip system classes, this just degrades performance. */ if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.")) { clazz = null; }}}}Copy the code
- The above steps in FindState are:
- Get all the methods of class by reflection
- Filter out is not the public and the abstract, the static, bridge, synthetic method, and the parameter has only one
- Find ways to not subScribe
- Add method and event type to findState
- Add all method subscription methods encapsulated as SubscribeMethod to the collection class in findState
- This is the step to query the subscription method, continue above, continue the registration step!
- Let’s go back to the registration logic
public void register(Object subscriber) { Class<? > subscriberClass = subscriber.getClass(); List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); Synchronized (this) {for (SubscriberMethod: synchronized (this) { subscriberMethods) { subscribe(subscriber, subscriberMethod); }}}Copy the code
- We look at the subscribe(Subscriber, subscriberMethod) parameter: the current subscription class, the subscription method in the subscription class
// Must be called in synchronized block private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<? > eventType = subscriberMethod.eventType; // Subscription newSubscription = newSubscription (subscriber, subscriberMethod); / / create a Subscription class encapsulation, pay attention to the parameter CopyOnWriteArrayList < Subscription > subscriptions = subscriptionsByEventType. Get (eventType); If (Subscriptions == null) {if (Subscriptions = null) {if (subscriptions = null) {if (subscriptions = null) {if (subscriptions = null) {if (subscriptions = null) CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); } else {if (subscriptions. Contains (newSubscription)) {// Throw new EventBusException("Subscriber "+ if it already exists subscriber.getClass() + " already registered to event " + eventType); } } int size = subscriptions.size(); // Add for (int I = 0; i <= size; I++) {if (I = = size | | subscriberMethod. Priority > subscriptions. Get (I). SubscriberMethod. Priority) {/ / priority from big to small order subscriptions.add(i, newSubscription); break; }} //typesBySubscriber caches key: activity/fragment of the current subscription Class, value: event set of the subscription method in the current Class List<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); // Add the current event to the current set of subscribed methods if (subscriberMethod.sticky) {// If (eventInheritance) {// Whether to find the parent class method // 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); StickyEvent = stickyEvents. Get (eventType);}} else {Object stickyEvent = stickyEvents. checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } //checkPostStickyEventToSubscription private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) { if (stickyEvent ! PostToSubscription (newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper()); }} // A Subscription class Subscription attribute for new includes final class Subscription {final Object subscriber; // Final SubscriberMethod SubscriberMethod; /** * Becomes false as soon as {@link EventBus#unregister(Object)} is called, which is checked by queued event delivery * {@link EventBus#invokeSubscriber(PendingPost)} to prevent race conditions. */ volatile boolean active; // Whether it is active. Subscription(Object subscriber, SubscriberMethod SubscriberMethod) {this.subscriber = subscriber; this.subscriberMethod = subscriberMethod; active = true; } @override public Boolean equals(Object other) {if (other instanceof Subscription) {Subscription otherSubscription = (Subscription) other; return subscriber == otherSubscription.subscriber && subscriberMethod.equals(otherSubscription.subscriberMethod); } else { return false; } } @Override public int hashCode() { return subscriber.hashCode() + subscriberMethod.methodString.hashCode(); }}Copy the code
- Summary subscription methods: Since we have summarized above, we continue to register a subscription class with a loop call subscribe after finding all subscription methods in it
- Create a subscription class and determine whether there is a List< subscription class > set with the current event as the key. This is to manage all the subscription classes of the same event so that an event can be sent and only need to traverse the collection to know which methods to call those classes
- Note that the List is sorted by priority when added to the List, and the order of calls is the same
- For subscription classes, the typesBySubscriber cache is used: key for the current subscription class,value for the collection of all event classes in the current subscription class
- Events if it is sticky, and determine whether need to draw on events parent class or interface corresponding subscribe method, if true and cache exists in the event of a post that is executed directly subscribe method, otherwise only subscribe method of execution of the current class (after reaching the first to send, subscriptions, and subscribe to the operation purpose, can not in fragments through intent Reopening the Activity passes information.)
The register unregister
- There is a subscription corresponding to anti-registration, otherwise memory leakage and multiple registration of the same information in the source code error
- Let’s think about it for a moment. There are basically two sets of maps registered at the top,
- YpesBySubscriber: key: indicates the subscription class,value: indicates the collection of all event types in the subscription class.
- SubscriptionsByEventType represents key: event type, value: collection of all subscription methods that register the event
- For example, A and B are friends in the circle of friends. If A wants to close B, then A has A map(A, List of friends) in A, and then B can find his List of friends in B and then delete A
- Similarly, for the above two sets, we also find all the sets of event types in the ypesBySubscriber set through this subscription class during de-registration, and traverse to find the value with the current event as the key in subscriptionsByEventType, the subscriber Delete the subscription method corresponding to this class from the collection
public synchronized void unregister(Object subscriber) { List<Class<? >> subscribedTypes = typesBySubscriber.get(subscriber); // Get all subscribed event classes from the subscribedTypes class. If (subscribedTypes! = null) { for (Class<? > eventType : // Iterate through the subscription events and unbind the data in another collection: delete unsubscribeByEventType(subscriber, eventType) from eventType; } typesBySubscriber.remove(subscriber); Else {log. w(TAG, "Subscriber to unregister was not registered before: "+ subscriber.getclass ()); } } 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) {// if the event is subscribed to in the current class, it is removed from the subscription method collection because the class is about to be over and no longer needs to accept subscription events subscription.active = false; // Tag does not activate subscriptions.remove(I); // Delete I --; size--; }}}}Copy the code
Post Send event
- Now that the event and subscribe methods have been added to the collection by registering and unregistering above, we can send an event to start responding to the method!
- ThreadLocal is threadisolated. Only the same thread can get a ThreadLocal. If a ThreadLocal is accessed by a get, it can be accessed by the same thread, and the same thread can obtain the same object
Public T get() {Thread T = thread.currentThread (); ThreadLocalMap map = getMap(t); // Return the value of map if (map! = null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e ! = null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); Private T setInitialValue() {T value = initialValue();} // Call initialValue to create an object value and store it in map. Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) map.set(this, value); else createMap(t, value); return value; } / / simply analyzes the source of ThreadLocal above, below to start real post operation private final ThreadLocal < PostingThreadState > currentPostingThreadState = new ThreadLocal<PostingThreadState>() { @Override protected PostingThreadState initialValue() { Return new PostingThreadState(); return PostingThreadState(); return PostingThreadState(); }}; Public void post(Object event) {public void post(Object event) {public void post(Object event) currentPostingThreadState.get(); // Different threads correspond to different postingstates. The same threads follow happended-before List<Object> eventQueue = postingState.eventQueue; PostingState eventQueue eventqueue.add (event); // Add events to the queue: each post thread is queued independently //isPosting is false by default. PostingState. IsPosting) {/ / the same thread is follow the rules of happended - before, whether is safe / / is main process postingState. IsMainThread = stars. GetMainLooper () == Looper.myLooper(); postingState.isPosting = true; Canceled: throws new EventBusException("Internal error. Abort ") {// Abort: throws new EventBusException("Internal error state was not reset"); } try { while (! Eventqueue.isempty ()) {postSingleEvent(eventQueue.remove(0), postingState); }} finally {postingstate. isPosting = false; / / finish to restore the default can accept to send information in postingState. IsMainThread = false; Final static class PostingThreadState {// Final List<Object> eventQueue = new ArrayList<Object>(); boolean isPosting; boolean isMainThread; Subscription subscription; Object event; boolean canceled; }Copy the code
- The post process
- ThreadLocal is used to obtain the PostingState of the current thread. Note that different threads have different postingstates
- Enqueue the sent event to the PostingState class
- Loop through the event queue, sending a single event call
- The method that sends a single Event, postSingleEvent
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<? > eventClass = event.getClass(); // Get the event type Boolean subscriptionFound = false; If (eventInheritance) {// if eventInheritance is allowed, by default it is allowed to find all the parent classes and implementation interfaces of eventClass. >> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); for (int h = 0; h < countTypes; h++) { Class<? > clazz = eventTypes.get(h); // If one of the events is successfully sent, return true, Then subscriptionFound to true subscriptionFound | = postSingleEventForEventType (event, postingState clazz); }} else {/ / do not allow the inheritance, then just send the event subscriptionFound = postSingleEventForEventType (event, postingState, eventClass); } if (! SubscriptionFound) {// If no subscribers subscribe to if (logNoSubscriberMessages) {log.d (TAG, "No subscribers registered for event " + eventClass); } // The default allowed if (sendNoSubscriberEvent && eventClass! = NoSubscriberEvent.class && eventClass ! = SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event)); / / send NosubSribeEvent event, to rewrite the subscribe method accepts to do their own processing logic}}} / / postSingleEventForEventType (event, postingState, eventClass) parameters: Event Object inside may have value, the current thread state postingState, event type class private Boolean postSingleEventForEventType (Object event, PostingThreadState postingState, Class<? > eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; synchronized (this) { subscriptions = subscriptionsByEventType.get(eventClass); // Get a collection of subscription classes based on the current event (attributes: subscription class, subscription method, activated or not)} if (Subscriptions! = null && ! subscriptions.isEmpty()) { for (Subscription subscription : Postingstate.event = event; postingState.event = event; PostingState.event = event; postingState.event = Event; postingState.event = Event postingState.subscription = subscription; boolean aborted = false; Try {// Send event, subscription class,event send event, whether it is the main thread postToSubscription(subscription, event, postingState.isMainThread); Aborted = Postingstate. canceled; } finally { postingState.event = null; / / restore the default value postingState. Subscription = null; postingState.canceled = false; } if (aborted) {// If aborted, break loop; } } return true; } return false; } //postToSubscription: IsMainThread specifies whether the sending method is in the main thread. Private void postToSubscription Boolean isMainThread) {switch (subscription. SubscriberMethod. ThreadMode) {/ / accept event subscription method of thread case POSTING: // that thread sends invokeSubscriber on that thread (subscription, event); break; case MAIN: if (isMainThread) { invokeSubscriber(subscription, event); } else {// Other threads send the value of the queue to the main thread and send it to the UI thread once (subscription, invokeSubscriber) PendingPost object pool mainThreadPoster. Enqueue (subscription, event); PendingPost object pool mainThreadPoster. } break; Case BACKGROUND: if (isMainThread) {// backgroundPoster. Enqueue (subscription, event); } else {// Send invokeSubscriber in child thread, receive invokeSubscriber in same thread (subscription, event); } break; Case ASYNC: // A new thread is opened to receive asyncPoster. Enqueue (subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); } // subscription: Void invokeSubscriber(Subscription, Object event) {try {// Reflection call method: Method. The invoke (class, parameter information), in which thread calls will run in which the thread subscription. SubscriberMethod. Method. Invoke (subscription. The subscriber, event); } catch (InvocationTargetException e) { handleSubscriberException(subscription, event, e.getCause()); } catch (IllegalAccessException e) {throw new IllegalStateException("Unexpected Exception ", e); }}Copy the code
- Let’s take a look at what happens between threads.
- Mainthreadposter.equeue () for the main thread
MainThreadPoster = new HandlerPoster(this, looper.getMainLooper (), 10); final class HandlerPoster extends Handler { private final PendingPostQueue queue; / / the event queue private final int maxMillisInsideHandleMessage; private final EventBus eventBus; private boolean handlerActive; HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) { super(looper); this.eventBus = eventBus; this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage; queue = new PendingPostQueue(); // Each thread has its own time queue} void enqueue PendingPost PendingPost = PendingPost = PendingPost = PendingPost = PendingPost = PendingPost = PendingPost = PendingPost = PendingPost.obtainPendingPost(subscription, event); synchronized (this) { queue.enqueue(pendingPost); // join queue if (! handlerActive) { handlerActive = true; if (! SendMessage (obtainMessage())) {throw new EventBusException("Could not send handler message"); } } } } @Override public void handleMessage(Message msg) { boolean rescheduled = false; try { long started = SystemClock.uptimeMillis(); while (true) { PendingPost pendingPost = queue.poll(); if (pendingPost == null) { synchronized (this) { // Check again, this time in synchronized pendingPost = queue.poll(); if (pendingPost == null) { handlerActive = false; return; } } } eventBus.invokeSubscriber(pendingPost); // Call invokeSubscriber long timeInMethod = systemclock. uptimeMillis() - started; if (timeInMethod >= maxMillisInsideHandleMessage) { if (! sendMessage(obtainMessage())) { throw new EventBusException("Could not send handler message"); } rescheduled = true; return; } } } finally { handlerActive = rescheduled; PendingPost (PendingPost) {Object Event = pendingPost.event; Subscription subscription = pendingPost.subscription; PendingPost.releasePendingPost(pendingPost); If (subscription. Active) {// Reflection calls method invokeSubscriber(subscription, event) if currently active; Private final Static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();Copy the code
- The EventBus constructor creates a thread pool called newCacheExecutor(), which can result in OOM. See Backgroundposter.enqueue (subscription, event);
BackgroundPoster = new backgroundPoster (this); backgroundPoster = new backgroundPoster (this); Final class BackgroundPoster implements Runnable {private final PendingPostQueue queue; Private final EventBus EventBus; private volatile boolean executorRunning; BackgroundPoster(EventBus eventBus) { this.eventBus = eventBus; queue = new PendingPostQueue(); } public void enqueue(Subscription subscription, Object event) {/ / through events encapsulated Object pool access Object PendingPost PendingPost = PendingPost. ObtainPendingPost (subscription, event); synchronized (this) { queue.enqueue(pendingPost); // Add one to the queue and execute if (! ExecutorRunning) {// Whether the thread pool is running executorRunning = true; / / call the EventBus created in the thread pool execution run method of the EventBus. GetExecutorService (). The execute (this); } } } @Override public void run() { try { try { while (true) { PendingPost pendingPost = queue.poll(1000); Synchronized (this) {if (pendingPost == null) {synchronized (this) { this time in synchronized pendingPost = queue.poll(); if (pendingPost == null) { executorRunning = false; return; }}} / / a line of thread pool medium-range execution method, with the top of the Ui thread, reflective calling the execution method, is running thread is different, this is the child thread, the above is the Ui thread eventBus. InvokeSubscriber (pendingPost); } } catch (InterruptedException e) { Log.w("Event", Thread.currentThread().getName() + " was interruppted", e); } } finally { executorRunning = false; } } } // asyncPoster.enqueue(subscription, event); Class AsyncPoster implements Runnable {// Class AsyncPoster implements Runnable {private Final PendingPostQueue queue; private final EventBus eventBus; AsyncPoster(EventBus eventBus) { this.eventBus = eventBus; queue = new PendingPostQueue(); } public void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); queue.enqueue(pendingPost); eventBus.getExecutorService().execute(this); } @Override public void run() { PendingPost pendingPost = queue.poll(); If (pendingPost == null) {throw new IllegalStateException("No pending post available"); } eventBus.invokeSubscriber(pendingPost); }}Copy the code
- From the analysis above,equeue addition is added one by one and executed one by one, which is controlled by the executorRunning variable. If the run execution takes too long and the addition is particularly rapid, a large number of threads will be created in the thread pool, which may eventually be OOM
- Posting: Where is the POST thread sent and accepted
- Main: If it is sent in the Main thread, it is received directly, otherwise it is called back to the Main thread using Handler
- BackGround child thread: If the publish event is in the main thread, a child thread in the thread pool is called to execute it, otherwise it is executed directly in the sending thread
- (ASYNC) Asynchronous thread: Publish events, both UI and child threads, are executed using an asynchronous thread!
- Note: All of the above method analysis calls logic inside the Post() method, so the thread switch needs to be aware of whether the thread pool is created or whether the original Post method is in the thread
Configuration index
- In order to improve the efficiency of EventBus3.0 and avoid the need to do much of this work through reflection at runtime, the index configuration generation class is used to generate registration files at the compiler to improve efficiency
- Use: Two ways: using the Android-APT tripartite plugin, or annotationProcessor
/ / 1. Use the android - apt buildscript {dependencies {classpath 'com. Neenbedankt. Gradle. Plugins: android - apt: 1.8'}} the apply plugin: 'com. Neenbedankt. Android - apt' dependencies {the compile 'org. Greenrobot: eventbus: 3.0.0' apt 'org. greenRobot: EventBus - annotation-Processor :3.0.1'} apt {arguments {eventBusIndex "com.monster.android.wild.MyEventBusIndex" } } //2. Using annotationProcessor android {defaultConfig {javaCompileOptions {annotationProcessorOptions {the arguments = [eventBusIndex:'com.monster.android.wild.MyEventBusIndex'] } } } } dependencies { compile 'org. Greenrobot: eventbus: 3.0.0' annotationProcessor 'org. Greenrobot: eventbus -- the annotation processor: 3.0.1'}Copy the code
- Com. Monster. Android. Wild. MyEventBusIndex is what we want to generate the Subscriber Index class
public class MyEventBusIndex implements SubscriberInfoIndex { private static final Map<Class<? >, SubscriberInfo> SUBSCRIBER_INDEX; // Cache MAO :key The current subscribed class, Static {SUBSCRIBER_INDEX = new HashMap<Class<? >, SubscriberInfo>(); // createInfoIndexFile() is not hardcode except for MyEventBusIndex. The compiler automatically generates code that encapsulates all subscription functions and related parameters in the subscriber class into SimpleSubscriberInfo class objects for EventBus to use during registration. Note that SimpleSubscriberInfo class objects are generated at compile time and therefore can be used directly at runtime, saving time and resources to do similar operations via reflection at runtime. putIndex(new SimpleSubscriberInfo(com.monster.android.wild.myeventbusdemo.MainActivity.class, true, new SubscriberMethodInfo[] { new SubscriberMethodInfo("onEvent", com.monster.android.wild.myeventbusdemo.MainActivity.EventBusEvent.class, ThreadMode.MAIN), })); } private static void putIndex(SubscriberInfo info) { SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info); } @override public SubscriberInfo getSubscriberInfo(Class<? > subscriberClass) { SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass); if (info ! = null) { return info; } else { return null; }}}Copy the code
- The first findUsingReflection method is used in the register, and the second findUsingInfo method is used in the register
private List<SubscriberMethod> findUsingInfo(Class<? > subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); While (findState.clazz! Findstate. subscriberInfo = getSubscriberInfo(findState); if (findState.subscriberInfo ! = null) {// If you find the SimpleSubscriberInfo class, go through the subscription method array and add it to the SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods(); . / / call SimpleSubscriberInfo getSubscriberMethods collection method for subscription encapsulated into SubscriberMethod [] for (SubscriberMethod SubscriberMethod: If (findstate.checkadd (subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); }}} else {/ / or otherwise honest go back to the reflection to register the above findUsingReflectionInSingleClass (findState); } findState.moveToSuperclass(); } return getMethodsAndRelease(findState); } //getSubscriberInfo private SubscriberInfo getSubscriberInfo(findState findState) {if (findState.subscriberInfo ! = null && findState.subscriberInfo.getSuperSubscriberInfo() ! = null) {/ / new findState null SubscriberInfo superclassInfo = findState. SubscriberInfo. GetSuperSubscriberInfo (); if (findState.clazz == superclassInfo.getSubscriberClass()) { return superclassInfo; }} //subscriberInfoIndexes are created after the appropriate if (subscriberInfoIndexes! = null) {// Multiple file information may be added to the indexes. Here we just have a for (SubscriberInfoIndex index: SubscriberInfoIndexes) {// Get a unique index class that is: MyEventBusIndex class object SubscriberInfo info = index.getSubscriberInfo(findState.clazz); // Call its getSubscriberInfo(subscription class) to get the SimpleSubscriberInfo if (info! = null) { return info; } } } return null; } // Create the subscriberMethodFinder = new from the EventBus constructor that passes in the build if we use the index and override the build method that passes in the data SubscriberMethodFinder(builder.subscriberInfoIndexes, builder.strictMethodVerification, builder.ignoreGeneratedIndex); EventBus EventBus = eventbus.Builder ().addIndex(new MyEventBusIndex()).build();Copy the code
- At this point, either the registry is reflected directly or the registry is read directly by adding an index at the compiler build file runtime. Welcome to leave a comment below to discuss!