Analysis of EventBus principle
1, Overview
As we all know, EventBus is used for data communication within app, which can achieve good decoupling effect. The traditional broadcast or callback mode has the characteristics of high code coupling, messy code and not suitable for maintenance. EventBus uses the subscriber / publisher mode, with concise code and high decoupling. This is the background reason of this framework product
2, Overall architecture
This is the diagram of the official framework. The structure described in this diagram is very clear
Subscriber: This is the subscriber who subscribes to a certain type of message. For example, onMessage(String src) subscribes to the message accepting src. Of course, the @ subscriber annotation should be written in the code
Publisher: This is the publisher. After the published message is processed by EventBus, it will call back to execute the subscriber's method
3, Use steps
1. Registration and de registration
EventBus.getDefault().register(this); //The registration method is very simple. The code is written in the class that needs to be subscribed, such as Activity
EventBus.getDefault().unregister(this); //This is de registration. This should be written in the destruction method of the class. This and register appear in pairs
2. Subscription method
@Subscribe(threadMode = ThreadMode.MAIN) //This is the subscription mode. This method will be executed in the UI thread of android //Each subscription method can only have one parameter. When sending a message, the corresponding method will be found according to the parameter type public void onEventBusMessage(BMessage msg) { Log.i("eventbus",Thread.currentThread().getId()+":"+msg.toString()); } @Subscribe(threadMode = ThreadMode.ASYNC) //This method will be executed in the child thread of android public void onEventBusMessageSubThread(String msg) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { Log.i("eventbus", "isCurrentThread:"+Looper.getMainLooper().isCurrentThread()); Log.i("eventbus", "mainthread.getId:"+Looper.getMainLooper().getThread().getId()); } Log.i("eventbus",Thread.currentThread().getId()+":"+msg); }
3. Send message
//A message of type String was sent EventBus.getDefault().post("subthread"); GameTestActivity.BMessage message=new GameTestActivity.BMessage(); message.code=2; message.msg="ss"; //A message of type BMessage was sent EventBus.getDefault().post(message);
4. Execution results
We can see from the log that the execution is accurate
4, Principle analysis
1. Overall execution process
1. Register
Extract the subscription method in the registered target class through reflection and save it in EventBus
2. Send message
According to the parameter type of the sent message, find the corresponding subscriber information in EventBus. If found, execute the corresponding method
The overall principle is relatively simple
2. Detailed explanation process
1. Register
1,
public void register(Object subscriber) { Class<?> subscriberClass = subscriber.getClass(); List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } } //The above is to find the subscribed method information, and then execute the subscription process for each method information //SubscriberMethodFinder is a class used to find registered methods //Let's look at what findSubscriberMethods does next
2,
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) { //Call the data in the cache first, and return the result directly if there is any List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } //Whether to register the method by ignoring the annotation index. This is false by default if (ignoreGeneratedIndex) { //Find by reflection subscriberMethods = findUsingReflection(subscriberClass); } else { //apt mode //This method registers the method by annotation index processor, EventBus annotation processor, //This is done during compilation, which is much faster than the traditional reflection method, //This also needs to be configured in EventBusBuild and the annotation processor of EventBus subscriberMethods = findUsingInfo(subscriberClass); } //Throw an exception if the registration method is not found if (subscriberMethods.isEmpty()) { throw new EventBusException("Subscriber " + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation"); } else { //Cache it when you find it METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; } }
Let's look at the findUsingInfo method, which is implemented by annotation processor
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) { //The reuse mechanism will be used in the preparation of FindState. There is a pool FindState findState = prepareFindState(); //Set the initialization of subscriberClass in findState, skipsubsclasses = false, subscriberinfo = null findState.initForSubscriber(subscriberClass); //The following while is to find the corresponding registration information in its own class and the class of its parent class through a loop //findState.moveToSuperclass() at the end of while is equivalent to moving the pointer to the parent class until there is no //Of course, this can be masked by skippsuperclasses = true in Builder while (findState.clazz != null) { findState.subscriberInfo = getSubscriberInfo(findState); //subscriberInfo is used in conjunction with annotation processing. It is null by default 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 { //The actual method of extracting registration information, let's enter and see findUsingReflectionInSingleClass(findState); } findState.moveToSuperclass(); } return getMethodsAndRelease(findState); }
findUsingReflectionInSingleClass(findState);
This is the actual extraction method. Continue to see
private void findUsingReflectionInSingleClass(FindState findState) { Method[] methods; try { // This is faster than getMethods, especially when subscribers are fat classes like Activities //Get its own methods array through reflection methods = findState.clazz.getDeclaredMethods(); } catch (Throwable th) { // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149 //If there is an exception, find the method of its own subclass methods = findState.clazz.getMethods(); findState.skipSuperClasses = true; } for (Method method : methods) { int modifiers = method.getModifiers(); //Meet the following conditions: 1. It is a PUBLIC modifier; 2. It cannot have the following modifiers //Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC //In the Modifier, values are declared in hexadecimal and adjacent to 1 to facilitate bit operation if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { //Get the parameters of the method, Class<?>[] parameterTypes = method.getParameterTypes(); //Only one parameter is allowed, so EventBus does not support multi parameter method registration if (parameterTypes.length == 1) { //Get the annotation of Subscribe.class, which makes a judgment and filters out the annotation method of @ Subscribe Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class ); if (subscribeAnnotation != null) { Class<?> eventType = parameterTypes[0]; if (findState.checkAdd(method, eventType)) { //Get the thread mode, which is also written in the annotation ThreadMode threadMode = subscribeAnnotation.threadMode(); //Setting: priority sticky event sticky (it can also be received after post registration through caching) // Add the new SubscriberMethod to the findState collection 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"); } } }
There is an intermediate class for FindState to deal with reflection mode and apt mode. It deals with the same logic class, which contains various collection data
So far, we have obtained the List result set
Then traverse the collection
Then let's continue to look at the subscribe method of EventBus
//This method is mainly used to save data for later queries private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<?> eventType = subscriberMethod.eventType; //new a Subscription, which is the last stored result class Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //subscriptionsByEventType is a collection of EventBus storage and registration methods CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); //Add an empty CopyOnWriteArrayList subscriptionsByEventType.put(eventType, subscriptions); } else { if (subscriptions.contains(newSubscription)) { throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } int size = subscriptions.size(); for (int i = 0; i <= size; i++) { if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) { //Add a newSubscription subscriptions.add(i, newSubscription); break; } } List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } //The Map collection that stores the eventType subscribedEvents.add(eventType); //If this is a sticky event, you will go to stickyEvents to find it and then execute the registration method //This can be achieved by sending messages first and then registering 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(); //Finally, it will invoke to the registration method checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } }
The whole registration logic above is finished. To sum up, it is to find the registered method and save it
2. Send message
1,post(object)
public void post(Object event) { //currentPostingThreadState saves the current processing state //currentPostingThreadState is a ThreadLocal type, //This ensures that each thread has its own state and will not conflict PostingThreadState postingState = currentPostingThreadState.get(); List<Object> eventQueue = postingState.eventQueue; eventQueue.add(event); //No, it can only be sent when it is sent if (!postingState.isPosting) { //Sets whether the state is currently operating on the main thread postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper(); //Sending message postingState.isPosting = true; //Throw an exception if it has been cancelled if (postingState.canceled) { throw new EventBusException("Internal error. Abort state was not reset"); } try { //If there are always events in the queue, one will be sent until the next is sent while (!eventQueue.isEmpty()) { postSingleEvent(eventQueue.remove(0), postingState); } } finally { postingState.isPosting = false; postingState.isMainThread = false; } } }
Then let's take a look at postSingleEvent
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<?> eventClass = event.getClass(); //Whether this registration method finds the flag of boolean subscriptionFound = false; //Whether the corresponding registration method will be found from their parent class. The default is true if (eventInheritance) { //This method is to find the method information including the method and interface of the parent class and the corresponding type List<Class<?>> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); for (int h = 0; h < countTypes; h++) { Class<?> clazz = eventTypes.get(h); // |=Or so on, as long as one finds that there is, the final result is there subscriptionFound |= postSingleEventForEventType(event, postingState, clazz); } } else { subscriptionFound = postSingleEventForEventType(event, postingState, eventClass); } if (!subscriptionFound) { if (logNoSubscriberMessages) { Log.d(TAG, "No subscribers registered for event " + eventClass); } if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) { //If NoSubscriberEvent is configured and no registration method is found, NoSubscriberEvent event is sent post(new NoSubscriberEvent(this, event)); } } }
Let's continue with postSingleEventForEventType
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; synchronized (this) { //Find the corresponding result value in the map according to the corresponding parameter type. This result is saved from the previous registration //This is an array. The CopyOnWriteArrayList class is characterized by high read performance during concurrency and does not worry about synchronization //Because reading does not cause concurrency problems subscriptions = subscriptionsByEventType.get(eventClass); } //Here is the information to facilitate all registration, and then execute if (subscriptions != null && !subscriptions.isEmpty()) { for (Subscription subscription : subscriptions) { postingState.event = event; postingState.subscription = subscription; boolean aborted = false; try { //Jump to the execution method and continue the analysis below postToSubscription(subscription, event, postingState.isMainThread); aborted = postingState.canceled; } finally { postingState.event = null; postingState.subscription = null; postingState.canceled = false; } if (aborted) { break; } } return true; } return false; }
//This method is executed differently according to the threadModel thread pattern private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) { case POSTING: invokeSubscriber(subscription, event); break; case MAIN: //This isMainThread represents whether the message is sent in the main thread. If so, it can be executed directly, if (isMainThread) { invokeSubscriber(subscription, event); } else { //If not, execute mainThreadPoster mainThreadPoster.enqueue(subscription, event); } break; case BACKGROUND: if (isMainThread) { backgroundPoster.enqueue(subscription, event); } else { invokeSubscriber(subscription, event); } break; case ASYNC: asyncPoster.enqueue(subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); } }
There are two important classes
1,HandlerPoster
Switch to the HandlerPoster of the main thread, which is actually a Handler. During initialization, the parameter in EventBus is Looper.getMainLooper(), which can be used to send the operations of sub threads to the main thread for execution
There is also a queue in this class, which is completed in the order of first in first processing
As follows:
final class HandlerPoster extends Handler { private final PendingPostQueue 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(); } void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); synchronized (this) { queue.enqueue(pendingPost); if (!handlerActive) { handlerActive = true; //Send an empty message, start handleMessage, and then get the message data from the queue 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(); //Keep getting messages while (true) { PendingPost pendingPost = queue.poll(); if (pendingPost == null) { synchronized (this) { //Two checks were done here // Check again, this time in synchronized pendingPost = queue.poll(); if (pendingPost == null) { handlerActive = false; //Return when there is no message return; } } } //Finally, the registered method will be executed, and now it is a ui thread eventBus.invokeSubscriber(pendingPost); 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; } } }
2,AsyncPoster
There is asynchronous queue execution processing. Finally, the thread pool getExecutorService().execute(this) is called to execute the thread
Finally, the invokeSubscriber method is executed in the run method
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); } }
Let's take a look at eventBus.invokeSubscriber, which is the last place to execute the reflection registration method
void invokeSubscriber(PendingPost pendingPost) { Object event = pendingPost.event; Subscription subscription = pendingPost.subscription; PendingPost.releasePendingPost(pendingPost); if (subscription.active) { invokeSubscriber(subscription, event); } } void invokeSubscriber(Subscription subscription, Object event) { try { //Finally, the registration method is executed to achieve the purpose of callback subscription.subscriberMethod.method.invoke(subscription.subscriber, event); } catch (InvocationTargetException e) { //Handle various exceptions and throw them handleSubscriberException(subscription, event, e.getCause()); } catch (IllegalAccessException e) { throw new IllegalStateException("Unexpected exception", e); } }
5, Summary
The overall logic of EventBus is not responsible, and the logic is very clear
1. Register, extract registration method storage
2. Send a message, find the corresponding registration method, and then invoke to execute
While studying other people's excellent frameworks, I learned excellent design and technology and broadened my thinking. When android technology development encounters a bottleneck, learning more good frameworks and source code is a very good means and strategy
The level is limited. If there is any error, please advise