EventBus, as a frequently used project in Android development, has helped us simplify data transfer in many complex scenarios, which is also an essential question in Android junior high school advanced interview.
However, most people don’t use EventBus correctly. Today, I will show you how to use EventBus correctly and how to understand EventBus from the source code level.
EventBus project address: github.com/greenrobot/…
1. Use EventBus
Step 1: Register
/ / register
EventBus.getDefault().register(this)
/ / the registration
EventBus.getDefault().unregister(this)
Copy the code
Registration and unregistration operations need to be used carefully to ensure that the lifecycle is in sync. For example, if you register in onResume, you should unregister in onPause.
Step 2: Set methods and add annotations
@Subscribe(threadMode = ThreadMode.MAIN)
fun onReceiverEventTwoNormal(event: FragmentEvent) {
if (event.isOne()) {
bindView {
tvReceiver.text = "Received contents:${event.eventMsg}"}}}Copy the code
Step 3: Use the POST method event
EventBus.getDefault().post(FragmentEvent.postOne())
Copy the code
I’m sure it’s the same thing you’re using, and most of the projects I’ve seen are set up this way.
But if that were all I could say, trust me, I wouldn’t need to write this article.
Have you ever come across this question in an interview?
Req: What is the difference between EventBus 2.x and EventBus 3.x?
The EventBus2.x implementation uses reflection, while 3.0 uses annotations and is optimized with indexes. Ok, it looks like perferct, but how do you index? And is the above code really better than the 2.0 code?
I don’t know how you would feel if I told you that the above processing is worse than EventBus 2.x. Go straight to the diagram above.
Here first put the correct way to use, after the analysis of the source code I believe we will understand.
Add the following to the build.gradle file under your project or module:
kapt { arguments { arg('eventBusIndex', 'org. Fireking. Event. MyEventBusIndex' dependencies)}} {kapt 'org. Greenrobot: eventbus -- the annotation processor: 3.0.0'... }Copy the code
To rebuild the code, you will be in the build/generated/source/kapt generated under/debug/org. Fireking. Event. MyEventBusIndex file, When we open it up, we see that it’s actually the class we identified with the SUBSCRIBE annotation.
.private static finalMap<Class<? >, SubscriberInfo> SUBSCRIBER_INDEX; static { SUBSCRIBER_INDEX = new HashMap<Class<? >, SubscriberInfo>(); putIndex(new SimpleSubscriberInfo(org.fireking.laboratory.eventbus.EventBusOneFragment.class.true,
new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onReceiverEventTwoNormal", org.fireking.laboratory.eventbus.FragmentEvent.class,
ThreadMode.MAIN),
}));
putIndex(new SimpleSubscriberInfo(org.fireking.laboratory.eventbus.EventBusTwoFragment.class.true,
new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onReceiverEventTwoNormal", org.fireking.laboratory.eventbus.FragmentEvent.class, ThreadMode.MAIN), })); }...Copy the code
You must have thought of it, here is a direct use of Kapt, the previous annotation class and method relationship directly generated file saved in map, future use should be directly from this, no need to reflect the search. As for the specific is not so, the source code will explain.
In Application, add Settings
// Register the methods generated above with the default eventBus configuration item, and you can use them as before.
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
Copy the code
2. Conventional principle analysis
For the analysis of Event implementation, we directly analyzed according to the commonly used method flow.
2-1.EventBus.getDefault().register(Object subscriber)
public void register(Object subscriber) {
// Find the class based on the currently passed objectClass<? > subscriberClass = subscriber.getClass();// Find all subscriberMethods under the current class object
List<SubscriberMethod> subscriberMethods
=subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
// Iterate over all subscription methods
for(SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); }}}Copy the code
The 2-1-1,subscriberMethodFinder#findSubscriberMethods
// It is used to hold the class and all the subscribed method relationships in the class
private static finalMap<Class<? >, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>(); .//SubscriberMethod is saved method information that uses Subscriber annotations
public class SubscriberMethod {
// The method of using Subscriber annotations
final Method method;
// Corresponding to threadMode in the annotation
final ThreadMode threadMode;
EventBus limits only one parameter to be passed by the method, which is the first parameter
finalClass<? > eventType;// Event priority
final int priority;
// Whether it is sticky
finalboolean sticky; .Copy the code
List<SubscriberMethod> findSubscriberMethods(Class<? > subscriberClass) {// Check whether the cache data already exists from method-cachae
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
// If it is already cached, return it directly
if(subscriberMethods ! =null) {
return subscriberMethods;
}
//ignoreGeneratedIndex specifies whether to ignore the index generated by the Annotation Processor
// If ignored, reflection is used by default, otherwise the generated index is used
if (ignoreGeneratedIndex) {
// Use reflection to find
subscriberMethods = findUsingReflection(subscriberClass);
} else {
// Use the index to search, if not found in the index, use reflection to search again
subscriberMethods = findUsingInfo(subscriberClass);
}
Eventbus.getdefault ().register()
// If there is no @subscriber method in the class, an exception will be thrown
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
// Once the retrieval is complete, save the class and @subscriber Method relationship in method-Cacahe
METHOD_CACHE.put(subscriberClass, subscriberMethods);
/ / return subscriberMethods
returnsubscriberMethods; }}Copy the code
private List<SubscriberMethod> findUsingReflection(Class<? > subscriberClass)
Reflection mode
private List<SubscriberMethod> findUsingReflection(Class
subscriberClass) {
// Get the FindState object from the cache pool of size 4
FindState findState = prepareFindState();
// Initialize the FindState object state
findState.initForSubscriber(subscriberClass);
while(findState.clazz ! =null) {
// Use reflection to find
findUsingReflectionInSingleClass(findState);
// search from the parent class
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
Copy the code
FindUsingReflectionInSingleClass core search method
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// Only public methods are found to increase the speed of the search
// This is faster than getMethods, especially when subscribers are fat classes like Activities
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
try {
methods = findState.clazz.getMethods();
} catch (LinkageError error) { // super class of NoClassDefFoundError to be a bit more broad...
String msg = "Could not inspect methods of " + findState.clazz.getName();
if (ignoreGeneratedIndex) {
msg += ". Please consider using EventBus annotation processor to avoid reflection.";
} else {
msg += ". Please make this class visible to EventBus annotation processor to avoid reflection.";
}
throw new EventBusException(msg, error);
}
findState.skipSuperClasses = true;
}
for (Method method : methods) {
Public is 1, private is 2, protected is 4, static is 8, final is 16.
int modifiers = method.getModifiers();
// The modifier is public
if((modifiers & Modifier.PUBLIC) ! =0 && (modifiers & MODIFIERS_IGNORE) == 0) {
// Get the class object type of the parameterClass<? >[] parameterTypes = method.getParameterTypes();// Only one eventBus parameter can be used. If more than one parameter is used, an exception will be thrown
if (parameterTypes.length == 1) {
// Get the annotation of the method
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if(subscribeAnnotation ! =null) { Class<? > eventType = parameterTypes[0];
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
// Save the found method data to findState
findState.subscriberMethods.add(newSubscriberMethod(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"); }}} the clazz method is replaced by the clazz objectwhileLoop ` findUsingReflectionInSingleClass ` and ` moveToSuperclass ` to find one by one. ` ` `java
void moveToSuperclass(a) {
// If the parent class is skipped, null is returned
if (skipSuperClasses) {
clazz = null;
} else {
// Return the parent class
clazz = clazz.getSuperclass();
String clazzName = clazz.getName();
// Skip system classes, this degrades performance.
// Also we might avoid some ClassNotFoundException (see FAQ for background).
// Skip Java and Android classes
if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") ||
clazzName.startsWith("android.") || clazzName.startsWith("androidx.")) {
clazz = null; }}}}Copy the code
Finally, the collection of methods found is returned and the cache pool getMethodsAndRelease is cleared
The 'findState' cache pool needs to be freed when it is used. Java private List<SubscriberMethod> getMethodsAndRelease(FindState FindState) {List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods); // Release the cache pool findstate.recycle () after the search is complete; synchronized (FIND_STATE_POOL) { for (int i = 0; i < POOL_SIZE; i++) { if (FIND_STATE_POOL[i] == null) { FIND_STATE_POOL[i] = findState; break; } } } return subscriberMethods; }Copy the code
Since there are two search methods, namely reflection and APT, THE apt method will be explained in the following part. As for how to use the search method, the following part will also be explained.
private List<SubscriberMethod> findUsingInfo(Class<? > subscriberClass)
Apt mode priority, search will not reflect search
privateList<SubscriberMethod> findUsingInfo(Class<? > subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass);while(findState.clazz ! =null) {
// Search from index information generated by APT
findState.subscriberInfo = getSubscriberInfo(findState);
// It was found in the index
if(findState.subscriberInfo ! =null) {
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if(findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); }}}else {
// If not found in index, use reflection to search
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
Copy the code
getSubscriberInfo
private SubscriberInfo getSubscriberInfo(FindState findState) {
if(findState.subscriberInfo ! =null&& findState.subscriberInfo.getSuperSubscriberInfo() ! =null) {
// Look in the parent class
SubscriberInfo superclassInfo =
findState.subscriberInfo.getSuperSubscriberInfo();
// If found, return directly
if (findState.clazz == superclassInfo.getSubscriberClass()) {
returnsuperclassInfo; }}//SubscriberInfoIndex is an interface from which we inherit classes generated by APT
if(subscriberInfoIndexes ! =null) {
for (SubscriberInfoIndex index : subscriberInfoIndexes) {
SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
// If it is found from the index apt generated, it is returned directly
if(info ! =null) {
returninfo; }}}return null;
}
Copy the code
The 2-1-2,subscribe(subscriber, subscriberMethod)
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// Get the @subscribe method argumentClass<? > eventType = subscriberMethod.eventType;// The Subscription object encapsulates the @subscribe method and the subscribed class into a single class
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
/ / subscriptionsByEventType is a @ the Subscribe method parameter as a key, the subscription method is set as the value of the Map
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
// Check to see if it is already registered
if (subscriptions == null) {
// A thread safe read/write queue is used here
subscriptions = new CopyOnWriteArrayList<>();
// If not registered, all methods of eventType will be collected into a collection
// The eventType is the @subscribe parameter, so it can be interpreted as putting all the registered types in a set,
// When unregistered, it is removed from the collection, so that all registered methods can be notified
subscriptionsByEventType.put(eventType, subscriptions);
} else {
// If already registered, repeat registration to throw an exception
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "+ eventType); }}int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
// Sort by priority, because priority is compared every time,
// So you end up with a higher -> lower priority
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break; }}// a set of subscriptionsByEventType,
// Discovery is only used to assist the isRegistered method in determining whether it has been registeredList<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
// This is for sticky event handling, which will be explained 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
The following is a brief summary of EventBus#register(Object subscriber) workflow
Using subscriberMethodFinder findSubscriberMethods, query the subscriber registration parameters as parameters in the class to all USES the @ the Subscribe method of annotation.
The search logic is as follows:
If found in METHOD_CACHE, it returns directly. If not, it will judge whether apt method is used to generate at compile time. If apt method is not used, reflection method is used to search.
Once found, it is stored in METHOD_CACHE and returned for subsequent use.
After that, the method set of @SUBSCRIBE annotation found is iterated, and the method parameter is taken as the key, and all the method set using the method parameter is taken as the value, which is saved in subscriptionsByEventType.
All @subsciBe annotation methods in the current registered class are counted into a collection using the event object (that is, the parameter) as the key. This facilitates event distribution during subsequent POST. It’s a very typical subscription-spend model, or observer model.
2-2,unregister(Object subscriber)
public synchronized void unregister(Object subscriber) {
// As mentioned above, typesBySubscriber is really just a help in checking whether or not you have registeredList<Class<? >> subscribedTypes = typesBySubscriber.get(subscriber);// If it is already registered
if(subscribedTypes ! =null) {
// Register takes the class as the key, and all @SUBSCRIBE method arguments (events for Hong Kong) in the class as values
for(Class<? > eventType : subscribedTypes) {// Iterate through the collection to remove the saved data from subscriptionsByEventType
unsubscribeByEventType(subscriber, eventType);
}
// Remove the key and value from the map
typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: "+ subscriber.getClass()); }}Copy the code
unsubscribeByEventType(Object subscriber, Class<? > eventType)
The logic here is relatively simple, with the above analysis of the register method, I believe this is easy to understand, without further explanation
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
Here’s a quick summary of the Event#unregister(Object subscriber) method:
Two map collections typesBySubscriber and subscriptionsByEventType are used
typesBySubscriber
The set is essentially savedsubscriber
All of the following@SUBSCRIBE annotation event
Collection, where key is the usage class and value is the event collection.subscriptionsByEventType
It’s all that’s savedEvents for
Image’s set of methods, events and methods areOne-to-many relationship
.
The unRegister operation is to clean up the relationships between classes and event sets and between events and event object methods found in the register method to prevent memory leaks caused by object references.
Posts, sticky messages, etc., will be split into a separate article for space reasons