For Android Developer, many open source libraries are essential knowledge points for development, from the use of ways to implementation principles to source code parsing, which require us to have a certain degree of understanding and application ability. So I’m going to write a series of articles about source code analysis and practice of open source libraries, the initial target is EventBus, ARouter, LeakCanary, Retrofit, Glide, OkHttp, Coil and other seven well-known open source libraries, hope to help you 😇😇
Official account: byte array
Article Series Navigation:
- Tripartite library source notes (1) -EventBus source detailed explanation
- Tripartite library source notes (2) -EventBus itself to implement one
- Three party library source notes (3) -ARouter source detailed explanation
- Third party library source notes (4) -ARouter own implementation
- Three database source notes (5) -LeakCanary source detailed explanation
- Tripartite Library source note (6) -LeakCanary Read on
- Tripartite library source notes (7) -Retrofit source detailed explanation
- Tripartite library source notes (8) -Retrofit in combination with LiveData
- Three party library source notes (9) -Glide source detailed explanation
- Tripartite library source notes (10) -Glide you may not know the knowledge point
- Three party library source notes (11) -OkHttp source details
- Tripartite library source notes (12) -OkHttp/Retrofit development debugger
- Third party library source notes (13) – may be the first network Coil source analysis article
We know that EventBus will call all listening methods of the message type directly after sending the message. The callback operation is implemented by reflecting method.invoke, so it must get all listening methods in the application before calling back. There are two ways for EventBus to obtain the listener method:
- No annotation handler is configured. All listening methods of subscriber are obtained by reflection when subscriber registers, which is implemented at run time
- Configure the annotation handler. The method signature information of all listening methods is stored in auxiliary files in advance, and all the parsing results can be obtained directly at run time without relying on reflection. This method is implemented at compile time, which is much better than the first method
The first approach is described here, which simply requires importing the following dependencies
implementation "Org. Greenrobot: eventbus: 3.2.0."
Copy the code
One, register
An EventBus is registered through the eventBus.register (Object) method. This method will parse the subscriber, Save the signature information of all the @SUBSCRIBE annotated methods contained in subscriber to memory by the FindSubscriberMethodMethods method of SubscriberMethodFinder, When a message is posted, the target method can be directly found in memory
public void register(Object subscriber) { Class<? > subscriberClass = subscriber.getClass(); List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);synchronized (this) {
for(SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); }}}Copy the code
As can be seen from all parameters included by SubscriberMethod, it contains the parameter information of @SUBSCRIBE and the corresponding method signature information
public class SubscriberMethod {
final Method method;
final ThreadMode threadMode;
// Message type
finalClass<? > eventType;// Message priority
final int priority;
// Whether it is a sticky message
final boolean sticky;
/** Used for efficient comparison */String methodString; ...}Copy the code
SubscriberMethodFinder will cache the results of each lookup to METHOD_CACHE, which is useful for some subscribers that undergo multiple registration and dereregistration operations, because each lookup may depend on multiple cyclic traversal and reflection operations. There is a slight performance cost, but caching also takes up some memory space
private static finalMap<Class<? >, List<SubscriberMethod>> METHOD_CACHE =new ConcurrentHashMap<>();
List<SubscriberMethod> findSubscriberMethods(Class
subscriberClass) {
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if(subscriberMethods ! =null) {
return subscriberMethods;
}
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
// If it is null, the @subscribe method is not found, then the register operation is meaningless and the exception is thrown
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
METHOD_CACHE.put(subscriberClass, subscriberMethods);
returnsubscriberMethods; }}Copy the code
Because ignoreGeneratedIndex defaults to false, look directly at the findUsingInfo(subscriberClass) method
The main logic is:
- through
prepareFindState()
Method from the object poolFIND_STATE_POOL
Gets the free FindState object in the. Initializes a new one if none exists and passes after usegetMethodsAndRelease
Method returns the object to the object pool. Avoid unrestricted creation by pooling objectsFindState
Object, which counts as an optimization point. FindState is used to hold various intermediate state values during reflection traversal - In the absence of an annotation handler
findState.subscriberInfo
和subscriberInfoIndexes
The default is null, so let’s seefindUsingReflectionInSingleClass
The method name indicates that the method is parsed by the reflection operation, and the parsing result is stored in thefindState
In the - Because the parent class declares the listening method will be inherited by the subclass, and the resolution process will be from the subclass to the parent class, so after the resolution of the subclass needs to pass
findState.moveToSuperclass()
Method points the next class object to the superclass
private static final int POOL_SIZE = 4;
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];
private List<SubscriberMethod> findUsingInfo(Class
subscriberClass) {
/ / step 1
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while(findState.clazz ! =null) {
findState.subscriberInfo = getSubscriberInfo(findState);
if(findState.subscriberInfo ! =null) {
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if(findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); }}}else {
/ / step 2
findUsingReflectionInSingleClass(findState);
}
/ / step 3
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
findState.recycle();
synchronized (FIND_STATE_POOL) {
// Reclaim findState and try to save it to the object pool
for (int i = 0; i < POOL_SIZE; i++) {
if (FIND_STATE_POOL[i] == null) {
FIND_STATE_POOL[i] = findState;
break; }}}return subscriberMethods;
}
// If there are available objects in the object pool, take them out and use them, otherwise build a new one
private FindState prepareFindState(a) {
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
FindState state = FIND_STATE_POOL[i];
if(state ! =null) {
FIND_STATE_POOL[i] = null;
returnstate; }}}return new FindState();
}
Copy the code
Here mainly to see findUsingReflectionInSingleClass method is how to perform the reflex action. If the parsed method signature does not meet the requirements, an exception is directly thrown when strict checking is enabled. If the method signature meets the requirements, the method signature is saved to subscriberMethods
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
// Get all methods included in clazz, excluding inherited methods
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
try {
// Retrieve all public methods of clazz and its parent class
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);
}
// Since getDeclaredMethods() has thrown an exception, the loop is not going to continue, so specify that the superclass will be ignored the next time
findState.skipSuperClasses = true;
}
for (Method method : methods) {
int modifiers = method.getModifiers();
if((modifiers & Modifier.PUBLIC) ! =0 && (modifiers & MODIFIERS_IGNORE) == 0) {
// SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETIC SYNTHETICClass<? >[] parameterTypes = method.getParameterTypes();if (parameterTypes.length == 1) { // The method contains one parameter
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if(subscribeAnnotation ! =null) { // The method signature contains the Subscribe annotationClass<? > eventType = parameterTypes[0];
if (findState.checkAdd(method, eventType)) {
// After verification, save the configuration information of Subscribe annotation and method signature
ThreadMode threadMode = subscribeAnnotation.threadMode();
findState.subscriberMethods.add(newSubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky())); }}}else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
// Since EventBus only supports annotated functions with one incoming parameter, an exception will be thrown if strict method validation is enabled
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)) {
// Throw an exception if the method signature is invalid and strict method validation is enabled
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
FindUsingReflectionInSingleClass method is a key is findState checkAdd method. If we think in a simple way, we just need to save the method of each subscriber that declares the Subscribe annotation, but we also need to consider some special cases:
- Classes in Java are inherited. If a parent class declares a Subscribe method, then the parent class also has the Subscribe method. After registering, the parent class needs to get all Subscribe methods from the parent class
- If a subclass inherits and overwrites a parent’s Subscribe method, then the subclass registers its own Subscribe method and ignores the parent’s corresponding method
The checkAdd method is used to do this
// Use eventType as key, method or FindState as value
final Map<Class, Object> anyMethodByEventType = new HashMap<>();
// Use methodKey as key and methodClass as value
final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
boolean checkAdd(Method method, Class
eventType) {
// 2 level check: 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 event type.
Object existing = anyMethodByEventType.put(eventType, method);
if (existing == null) {
Existing = null; existing = null; existing = null
// Since most of the time listeners do not declare multiple methods that listen for the same event, it is more efficient to do this first
return true;
} else { Existing not equal to NULL indicates that there is already a method that listens for this event
if (existing instanceof Method) {
if(! checkAddWithMethodSignature((Method) existing, eventType)) {// Paranoia check
throw new IllegalStateException();
}
// Put any non-Method object to "consume" the existing Method
// eventType = this; // eventType = this
/ / to avoid check for many times, let it go directly to perform checkAddWithMethodSignature method
anyMethodByEventType.put(eventType, this);
}
returncheckAddWithMethodSignature(method, eventType); }}private boolean checkAddWithMethodSignature(Method method, Class
eventType) {
methodKeyBuilder.setLength(0);
methodKeyBuilder.append(method.getName());
methodKeyBuilder.append('>').append(eventType.getName());
// Use methodName>eventTypeName string as key
// Use this key to determine whether a subclass overrides a superclass method
String methodKey = methodKeyBuilder.toString();
// Get the class object for which method is declaredClass<? > methodClass = method.getDeclaringClass(); Class<? > methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);//1. If methodClassOld == null is true, the method is parsed for the first time and can be added
/ / 2. If methodClassOld. IsAssignableFrom (methodClass) is true
//2.1, specify that methodClassOld is the parent class of methodClass
// This should not actually be the case, because EventBus starts from the subclass and iterates through the superclass
//2.2, specify a methodClass that listens to the same event as a methodClass
if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
// Only add if not already found in a sub class
return true;
} else {
// Revert the put, old class is further down the class hierarchy
// Because EventBus is parsed from subclass to superclass
// If a subclass overwrites a method of the parent class, the method of the same key has been resolved
// In this case, we need to override method, so we reset methodClassOld back
subscriberClassByMethodKey.put(methodKey, methodClassOld);
return false; }}Copy the code
After you do the above, all the listening methods contained in Subscriber are found, and they are saved to List
. After getting all the methods, the register method needs to classify the subscriber and all the listening methods
The purpose of categorization is both to facilitate subsequent operations and to improve efficiency. Because there may be multiple subscribers that declare multiple listening methods for the same type of message, it is necessary to correspond each message type with all the current listening methods to improve the message sending efficiency. In addition, when the subscriber is unregistered, all listening methods contained in the subscriber need to be removed, so it also needs to be classified in advance. Listening methods can also set their own priority order for message processing, so they need to be sorted in advance
public void register(Object subscriber) { Class<? > subscriberClass = subscriber.getClass(); List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);synchronized (this) {
for(SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); }}}private finalMap<Class<? >, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;private finalMap<Object, List<Class<? >>> typesBySubscriber;// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<? > eventType = subscriberMethod.eventType; Subscription newSubscription =new Subscription(subscriber, subscriberMethod);
/ / subscriptionsByEventType to message type eventType as a key value store all of the eventType subscriber, improve the efficiency of the follow-up when sending a message
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
// A Subscriber is registered twice
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "+ eventType); }}// Order subscribers by message priority
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);
break; }}//typesBySubscriber takes subscriber subscriber as key, and value stores all eventtypes to which it is subscribed
// It is used to provide the function of whether a class is registered, and facilitate the removal of all listening methods under subscriber when unregisterList<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
// The following is the processing of sticky events, 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); }}}Copy the code
Second, the message
1. Message execution policy
Before introducing the message sending steps, let’s take a look at some of the different message execution strategies in EventBus. The execution policy is defined by the enumeration ThreadMode and declared in the @subscribe annotation. The execution policy determines which thread received the message by the message receiver
ThreadMode | The thread of execution | |
---|---|---|
POSTING | Executes in the thread that sent the event | Call the message receiver directly |
MAIN | Execute in the main thread | If the event is sent on the main thread, the message recipient is invoked directly, otherwise it is handled through mainThreadPoster |
MAIN_ORDERED | Execute sequentially in the main thread | Processing is done through mainThreadPoster to ensure orderly message processing |
BACKGROUND | Execute sequentially in the background thread | If the event is sent on the main thread, it is submitted to the backgroundPoster for processing, otherwise the message receiver is invoked directly |
ASYNC | Submit to an idle background thread for execution | Submit the message to the asyncPoster for processing |
The detailed breakdown logic for executing the policy is done in the postToSubscription method of the EventBus class
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
if(mainThreadPoster ! =null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(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); }}Copy the code
For AsyncPoster, for example, its each receives a message, will be directly in the enqueue method (Runnable) submitted to the thread pool for processing, and use the default thread pool is Executors newCachedThreadPool (), Each time a task is received by the thread pool, it is immediately handed over to the thread for processing, so The AsyncPoster does not guarantee ordering of message processing, but it does have a high timeliness of message processing, and each message submitted to the AsyncPoster may be processed by a different thread
class AsyncPoster implements Runnable.Poster {
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(a) {
PendingPost pendingPost = queue.poll();
if(pendingPost == null) {
throw new IllegalStateException("No pending post available"); } eventBus.invokeSubscriber(pendingPost); }}Copy the code
The BackgroundPoster will cache the tasks to the PendingPostQueue in turn, fetching one task at a time for the thread pool to execute, so the BackgroundPoster will ensure that the message queue is processed in order. However, the timeliness of message processing is lower than that of AsyncPoster
final class BackgroundPoster implements Runnable.Poster {
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) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if(! executorRunning) { executorRunning =true;
eventBus.getExecutorService().execute(this); }}} ···}Copy the code
No matter what message processing strategy is used, the end result is that the call to the listener method is reflected by calling the following 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 {
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
2. Send non-sticky messages
The eventbus.getDefault ().post(Any) method is used to send non-sticky messages. EventBus maintains a PostingThreadState object via ThreadLocal for each thread sending messages, which is used to maintain a message queue and other auxiliary parameters for each thread
/** * For ThreadLocal, much faster to set (and get multiple values). */
final static class PostingThreadState {
final List<Object> eventQueue = new ArrayList<>();
boolean isPosting;
boolean isMainThread;
Subscription subscription;
Object event;
boolean canceled;
}
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue(a) {
return newPostingThreadState(); }};/** * Posts the given event to the event bus. */
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
// Add the message to the message queue
eventQueue.add(event);
if(! postingState.isPosting) {// Whether the message is sent on the main thread
postingState.isMainThread = isMainThread();
// Indicates that the message is currently being sent
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); }}finally {
postingState.isPosting = false;
postingState.isMainThread = false; }}}Copy the code
Each incoming message is stored in the Message queue and processed through the while loop using the postSingleEvent method
The main logic is:
- Assuming that EventA inherits from EventB, then if the message type is EventA, you need to consider whether the listening method of EventB can receive EventA, that is, whether the message type has inheritance relationship
- Has inheritance relationship. In this case, you need to get all the parent types of EventA, and then send messages based on all the listening methods associated with EventA and its parent type
- There is no inheritance relationship. All you need to do is send the message to EventA’s listening method
- If the message is sent and no receiver is found, and
sendNoSubscriberEvent
True, then a NoSubscriberEvent event is actively sent to notify the outside that no receivers have been found for the message - You can set the priority of message processing between listening methods, and higher-priority methods can be called
cancelEventDelivery
Method to intercept the event without further sending down. But only inPOSTING
Only in this mode can events be intercepted, because only in this mode can the listening methods be guaranteed to be executed in strict order
Ultimately, sent messages are completed through the postToSubscription method, depending on the processing policy of the recipient’s method
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<? > eventClass = event.getClass();// Used to mark whether the recipient of the message has been found
boolean subscriptionFound = false;
if (eventInheritance) {
/ / step 2List<Class<? >> eventTypes = lookupAllEventTypes(eventClass);int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
/ / step 3
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
if(! subscriptionFound) {if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if(sendNoSubscriberEvent && eventClass ! = NoSubscriberEvent.class && eventClass ! = SubscriberExceptionEvent.class) {/ / step 4
post(new NoSubscriberEvent(this, event)); }}}private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class
eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
// Find all listeners
subscriptions = subscriptionsByEventType.get(eventClass);
}
if(subscriptions ! =null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted;
try {
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
/ / step 5
if (aborted) {
break; }}return true;
}
return false;
}
Copy the code
3. Sending sticky messages
Sticky message means that after the message is sent, even the subscriber registering later can receive the previous message. In this case, the sticky attribute of @subscribe annotation needs to be set to true, indicating that the message receiver wants to receive sticky messages
The eventbus.getDefault ().poststicky (Any) method is used to send sticky messages. StickyEvents are saved in a Map called stickyEvents, where key is the Class object of the event and value is the event itself, which means that for sticky messages of the same type, only the last message will be saved
private finalMap<Class<? >, Object> stickyEvents;/**
* Posts the given event to the event bus and holds on to the event (because it is sticky). The most recent sticky
* event of an event's type is kept in memory for future access by subscribers using {@link Subscribe#sticky()}.
*/
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
// Should be posted after it is putted, in case the subscriber wants to remove immediately
post(event);
}
Copy the code
For a sticky message, there are two different times to be received by subscriber
- When the postSticky method is called, it is received directly by its existing subscriber by calling the POST method in the postSticky method
- When the Register method is called, the newly added subscriber determines whether there is an associated Event in stickyEvents that needs to be distributed
I’m going to focus on the second case here. The register operation completes the distribution of sticky events in the subscribe method. As with post operations, the event inheritance relationship needs to be considered when sending sticky events
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {...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>).
// Event types need to consider their inheritance
// Therefore, we need to determine whether each stickyEvent parent has a listener, and if so, we need to call backSet<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 {
// Event types do not need to consider their inheritanceObject stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); }}}private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if(stickyEvent ! =null) {
// If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
// --> Strange corner case, which we don't take care of here.postToSubscription(newSubscription, stickyEvent, isMainThread()); }}Copy the code
4. Remove sticky events
Removal of the specified stickyEvents can be accomplished by the following methods, both of which are used to remove the specified event from stickyEvents
/**
* Remove and gets the recent sticky event for the given event type.
*
* @see #postSticky(Object)
*/
public <T> T removeStickyEvent(Class<T> eventType) {
synchronized (stickyEvents) {
returneventType.cast(stickyEvents.remove(eventType)); }}/**
* Removes the sticky event if it equals to the given event.
*
* @return true if the events matched and the sticky event was removed.
*/
public boolean removeStickyEvent(Object event) {
synchronized(stickyEvents) { Class<? > eventType = event.getClass(); Object existingEvent = stickyEvents.get(eventType);if (event.equals(existingEvent)) {
stickyEvents.remove(eventType);
return true;
} else {
return false; }}}Copy the code
3. Cancellation of registration
The purpose of deregistration is to avoid memory leak. EventBus uses singleton mode. If you do not take the action of deregistration, EventBus will always hold subscriber. The unregister method is implemented through the unregister method. The logic of this method is relatively simple, which just removes subscriber and all method objects associated with it from the collection
Although all the information about subscriber will be removed here, the static member variable METHOD_CACHE in the SubscriberMethodFinder still caches the information of subscriber that has been registered. This is also for the purpose of information reuse when some subscriber registers EventBus multiple times to avoid multiple cyclic reflection
/** * Unregisters the given subscriber from all event classes. */
public synchronized void unregister(Object subscriber) { List<Class<? >> subscribedTypes = typesBySubscriber.get(subscriber);if(subscribedTypes ! =null) {
for(Class<? > eventType : subscribedTypes) { unsubscribeByEventType(subscriber, eventType); } typesBySubscriber.remove(subscriber); }else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: "+ subscriber.getClass()); }}/** * Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
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--; }}}}Copy the code
Iv. Annotation processor
The Annotation Processing Tool can avoid repeated cyclic reflection operations during subscriber registration, which greatly improves the running efficiency of EventBus. An annotation handler is an annotation processing tool that scans and processes annotations at compile time to generate Java files from annotations. That is, using annotations as a bridge to automatically generate Java files through predefined code generation rules. Representatives of such annotation frameworks are ButterKnife, Dragger2, EventBus, and so on
The Java API already provides a framework for scanning source code and parsing annotations, and developers can implement their own annotation parsing logic by inheriting AbstractProcessor classes. The principle of APT is that after some code elements (such as fields, functions, classes, etc.) are annotated, the compiler will check subclasses of AbstractProcessor at compile time, and automatically call its process() method, and then pass all code elements with specified annotations to the method as parameters. The developer then outputs Java code at compile time based on the annotation elements
For more information on how APT works and how it works, see this article: APT For Android
The way to introduce an annotation handler into the Kotlin environment is as follows:
apply plugin: 'kotlin-kapt'
kapt {
arguments {
arg('eventBusIndex'.'github.leavesc.demo.MyEventBusIndex')
}
}
dependencies {
implementation "Org. Greenrobot: eventbus: 3.2.0."
kapt "Org. Greenrobot: eventbus -- the annotation processor: 3.2.0"
}
Copy the code
, MyEventBusIndex is ancillary file will be generated at compile phase, dead simple. Leavesc. Demo. MyEventBusIndex package name of the path is generated by the auxiliary files, can be defined by ourselves
Original file:
/ * * * the author: leavesC * time: 2020/10/01 12:17 * description: * GitHub:https://github.com/leavesC * /
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
@Subscribe
fun fun1(msg: String){}@Subscribe(threadMode = ThreadMode.MAIN, priority = 100)
fun fun2(msg: String){}}Copy the code
The auxiliary files generated after compilation are shown below. As you can see, the MyEventBusIndex file encapsulates the subscriber and all of its listening methods’ signature information, so we don’t need to parse it at run time, but directly generate it at compile time
/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
private static finalMap<Class<? >, SubscriberInfo> SUBSCRIBER_INDEX;static {
SUBSCRIBER_INDEX = newHashMap<Class<? >, SubscriberInfo>(); putIndex(new SimpleSubscriberInfo(MainActivity.class, true.new SubscriberMethodInfo[] {
new SubscriberMethodInfo("fun1", String.class),
new SubscriberMethodInfo("fun2", String.class, ThreadMode.MAIN, 100.false),})); }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
Note that these class files are needed to initialize EventBus after the auxiliary files are generated
EventBus.builder().addIndex(MyEventBusIndex()).installDefaultEventBus()
Copy the code
The injected auxiliary file is saved to the subscriberInfoIndexes member variable of the SubscriberMethodFinder class, and the findUsingInfo method first tries to get the SubscriberMethod from the auxiliary file. Only when it is not available is it done through a low performance reflection operation
private List<SubscriberMethod> findUsingInfo(Class
subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while(findState.clazz ! =null) {
// In the absence of an annotation handler, the default value of findState.SubscriberInfo and subscriberInfoIndexes is null, so getSubscriberInfo returns NULL
/ / at this point you need to get reflected by findUsingReflectionInSingleClass method
// When an annotation handler is used, the subscriberInfoIndexes store an automatically generated auxiliary file, and getSubscriberInfo gets the target information from the auxiliary file
// This avoids the reflection operation
findState.subscriberInfo = getSubscriberInfo(findState);
if(findState.subscriberInfo ! =null) {
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if(findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); }}}else {
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
private SubscriberInfo getSubscriberInfo(FindState findState) {
if(findState.subscriberInfo ! =null&& findState.subscriberInfo.getSuperSubscriberInfo() ! =null) {
SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
if (findState.clazz == superclassInfo.getSubscriberClass()) {
returnsuperclassInfo; }}if(subscriberInfoIndexes ! =null) {
for (SubscriberInfoIndex index : subscriberInfoIndexes) {
SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
if(info ! =null) {
returninfo; }}}return null;
}
Copy the code
There are drawbacks to using an annotation processor. Since MyEventBusIndex holds all method signature information in a static constant Map, when initializing EventBus, the Map is initialized at the same time, which is equivalent to a full load at the beginning. However, we may not use some subscriber, which will cause a waste of memory. If it is obtained by reflection, it is equivalent to loading on demand. The listening method of subscriber will be cached only after the subscriber has registered
Five, some pits
1. Weird inheritance
As described above, a subclass can inherit the Subscribe method of its parent class. There is a strange twist: an IllegalStateException is thrown if a subclass overrides multiple Subscribe methods of its parent class. For example, in the example below. The parent class BaseActivity declares two Subscribe methods, and the subclass MainActivity overwrites these methods. When run, IllegalStateException is thrown. MainActivity, on the other hand, works fine if it doesn’t override a method or overrides only one
/ * * * the author: leavesC * time: 2020/10/01 description: "* * GitHub:https://github.com/leavesC * /
open class BaseActivity : AppCompatActivity(a){
@Subscribe
open fun fun1(msg: String) {}@Subscribe
open fun fun2(msg: String) {}}class MainActivity : BaseActivity(a){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
EventBus.getDefault().register(this)}override fun onDestroy(a) {
super.onDestroy()
EventBus.getDefault().unregister(this)}@Subscribe
override fun fun1(msg: String) {}@Subscribe
override fun fun2(msg: String) {}}Copy the code
If a subclass overrides a parent class and one Subscribe method works, then two Subscribe methods should work as well. However, the example above makes EventBus seem buggy. By locating the stack information, you can see that it was the checkAdd method in FindState that threw the exception
This is how it throws an exception:
- The parse direction of the Subscribe method of EventBus is carried out from the subclass to the parent class, and the Subscribe method under the same class is parsed according to the declaration order
- when
checkAdd
Method begins parsingBaseActivity
的fun2
Method,existing
Object isBaseActivity.fun1
, operation 1 will be executed, since the subclass has been overriddenfun1
Method, at this pointcheckAddWithMethodSignature
The method returns false, resulting in an exception being thrown
boolean checkAdd(Method method, Class
eventType) {
// 2 level check: 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 event type.
Object existing = anyMethodByEventType.put(eventType, method);
if (existing == null) {
return true;
} else {
if (existing instanceof Method) {
//操作1
if(! checkAddWithMethodSignature((Method) existing, eventType)) {// Paranoia check
throw new IllegalStateException();
}
// Put any non-Method object to "consume" the existing Method
anyMethodByEventType.put(eventType, this);
}
returncheckAddWithMethodSignature(method, eventType); }}Copy the code
The author of EeventBus only responded by saying that the method listens only on subclasses: Issues, which existed in 2018
2. Remove sticky messages
The removeStickyEvent method has one misleading aspect: For sticky messages sent through eventbus.getDefault ().poststicky (XXX), the removeStickyEvent method cannot be used to cause existing listeners to intercept the event
For example, if both of the following methods are already registered, the fun2 method will receive the message even if the sticky message is removed from the FUN1 method after postSticky. This is because the postSticky method ultimately calls the POST method, which is not affected by stickyEvents, to complete message sending
@Subscribe(sticky = true)
fun fun1(msg: String) {
EventBus.getDefault().removeStickyEvent(msg)
}
@Subscribe(sticky = true)
fun fun2(msg: String) {}Copy the code
If sticky events are already stored in EventBus, the fun1 method can intercept the message so that fun2 method cannot receive the message when the two methods are first registered. This is because the register method iterates over methods in a for loop, and if the previous method has removed sticky messages, then the subsequent method has no sticky messages to process
public void register(Object subscriber) { Class<? > subscriberClass = subscriber.getClass(); List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);synchronized (this) {
// Iterates over the method in the for loop
for(SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); }}}Copy the code
Six, summarized
That’s the end of source parsing for EventBus, and you should have covered most of the content in this article. Here is a summary of the EventBus implementation process
- EventBus contains two methods, register and unregister, which are used to mark whether the current subscriber needs to receive messages. The operations of adding and removing elements to CopyOnWriteArrayList correspond to them
- Every time an event is posted, we need to find all the methods that declare @subscribe annotation and listen for this type of message according to the eventClass object. These methods are all in the subscriber register, Obtained from subscriber
- There are two ways to obtain all listening methods from subscriber. The first is obtained by reflection at run time, and corresponds to the case where no annotation handler is configured. The second is in the case of a configured annotation processor, which eliminates inefficient reflection operations during register by globally scanning @SUBSCRIBE annotations and generating auxiliary files during compilation. No matter how to obtain all methods, the methods will be categorized according to the message type eventType for the convenience of subsequent traversal
- Whenever a message is sent, the listener method is found based on the Class object corresponding to the event, and the method is called back and forth by reflection. When initializing an EventBus, an external user can choose whether to consider the inheritance of an event, that is, whether a method that listens on the parent type of an event needs to be called back when the event is posted
The hard part is to implement EventBus in a stable and efficient way, with only two releases between 2018 and 2020 (perhaps because the author is too lazy to update?). EventBus 😇😇