preface
Recently, I had a talk with a senior about learning methods. I talked a lot and gained a lot. In the process of communication, my senior gave me a saying: “Apply what you have learned to your knowledge.” After listening to them, I realized that there were big problems with the previous learning method. In the future, I need to combine more with practice to achieve the purpose of learning. Do you have a better way to learn? Welcome to leave a message.
Introduction to the
In this article, WE will talk about EventBus, which is familiar to everyone and used in daily development. Let’s take a look at the official introduction of EventBus. EventBus is an open source library for Android and Java that is loosely coupled using the publisher/subscriber model. EventBus simplifies code, eliminates dependencies, and speeds application development by enabling central communication to decouple classes with just a few lines of code.
EventBus
Simplify communication between components 2 separate event sender and receiver 3 Avoid problems caused by complex and error-prone dependencies and lifecycle 4.
EventBus features:
Simple but powerful: EventBus is a tiny library with an easy-to-learn API. However, by decoupling components, your software architecture can benefit greatly: subscribers do not know the sender when using events. Battle-tested: EventBus is one of the most commonly used Android libraries: Thousands of applications use EventBus, including very popular ones. More than a billion installed apps say it all. 3. High performance: Especially on Android, performance is critical. The EventBus was summarized and optimized. Could make it the fastest solution in its class. Convenient annotation-based APIS (without sacrificing performance) : Simply put the @SUBSCRIBE annotation into your subscriber method. Because the annotation creation time is indexed, EventBus does not need to reflect annotations while the application is running, which is very slow on Android. 5. Android mainthread delivery: When interacting with the UI, EventBus can deliver events in the main thread, regardless of how the event is published. Background thread delivery: EventBus can also use background threads to avoid UI blocking if your subscribers are running tasks. Event and Subscriber Inheritance: In EventBus, the object-oriented paradigm applies to the event and subscriber classes. Suppose event class A is A superclass of B. Published events of type B will also be published to subscribers interested in A. Similarly, consider inheritance of a subscriber class. Zero configuration: You can start using the default EventBus instance immediately, which is available anywhere in the code. Configurable: To adjust EventBus to your requirements, you can use builder mode to adjust its behavior.
EventBus is an EventBus. If you don’t use it, you’re Out.
use
This article uses the latest official version 3.1.1. Let’s write a small Demo according to the official usage steps.
Step 1: Define events
public class SayHelloEvent {
private String message;
public void sayHellow(String message) {
this.message = message; }}Copy the code
Here the event is defined as a normal class, inside which a field is created and a method is created.
Step 2: Prepare subscribers
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(SayHelloEvent event) { String message = event.getMessage(); . }@Override
public void onStart(a) {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop(a) {
EventBus.getDefault().unregister(this);
super.onStop();
}
Copy the code
Call eventbus.getDefault ().register(this) and unregister eventbus.getDefault ().unregister(this) where appropriate. Create a method that receives the event, onMessageEvent, annotated with @SUBSCRIBE, and the thread that receives the life event is the master thread.
Step 3: Send events
EventBus.getDefault().post(new SayHelloEvent("Hello EventBus!!"));
Copy the code
Call the eventbus.getDefault ().post method where you want to send the event, passing in the event object created in the first step. This is the end of how to use EventBus, which seems pretty simple, but there are a lot of caveats, which will be covered in code exploration.
Knowledge preparation
Before we start our source code analysis, we need to know three things about EventBus: role assignment, thread model, and event type.
Role assignment
Event
Event, which can be of any type, and EventBus sends globally based on the type of event.
Subscriber
Event subscribers, also known as event receivers. Prior to EventBus3.0, you used several receive methods that must begin with OnEvent to receive events. These methods are as follows: OnEvent, onEventMainThread, onEventBackgroundThread, and onEventAsync, but after 3.0, the processing method can be customized, but it is marked with the annotation @SUBSCRIBE and specifies the thread model. The threading model defaults to POSTING.
Publisher
Event publisher, which can publish events in any thread. When used, the eventBus.getDefault () method is usually called to get an EventBus object, followed by a chained programming call to the POST () method to send the event.
Threading model
The event model refers to the relationship between the thread of the event subscriber and the thread of the event publisher.
POSTING
Events are subscribed to and events are published on the same thread. This is the default setting. Event passing means minimal overhead because it completely avoids thread switching. Therefore, this is the recommended pattern for simple tasks that are known to be completed in a very short time and do not require a main thread. Event handlers using this pattern must return quickly to avoid blocking the publishing thread that might be the main thread.
MAIN
On Android, the user will be called in the Android main thread (UI thread). If the publishing thread is the main thread, the subscribing method is called directly, blocking the publishing thread. Otherwise, events are queued for delivery (non-blocking). Subscribers using this pattern must return quickly to avoid blocking the main thread. If not on Android, the behavior is the same as POSTING.
MAIN_ORDERED
On Android, the user will be called in the Android main thread (UI thread). Unlike {@link#MAIN}, events are always queued for delivery. This ensures that the POST call is non-blocking.
BACKGROUND
On Android, the user will be called in a background thread. If the publishing thread is not the main thread, the subscription method is called directly in the publishing thread. If the publishing thread is the main thread, EventBus uses a single background thread that passes all its events in sequence. Subscribers using this pattern should try to return quickly to avoid blocking background threads. If not on Android, always use background threads.
ASYNC
The subscriber will be called in a separate thread which is always separate from the publishing thread and the main thread. Publish events never wait for subscriber methods that use this pattern. Use this pattern if the execution of the subscriber method is likely to take some time (for network access, for example). Limit the number of concurrent threads by avoiding triggering a large number of long-running asynchronous subscriber methods at the same time. EventBus uses thread pools to effectively reuse threads notified from completed asynchronous subscribers.
The event type
Ordinary events
Normal event means that existing event subscribers can receive events sent by event senders, and registered event receivers cannot receive events after the event is sent. Normal events can be sent by calling the eventbus.getDefault ().post() method.
Viscous event
Sticky events are events that can be received either by event receivers registered before the event is sent or by event receivers registered after the event is sent. The difference here is that the event receiver needs to define the event receiver type, which can be declared by @SUBSCRIBE (threadMode = XXX, sticky = true); The eventBus.getDefault ().poststicky () method is called to send an event. The event type is normal by default.
Event priority
Subscriber priority to influence the order in which events are delivered. In the same delivery thread ThreadMode, a higher-priority subscriber receives events before other lower-priority subscribers. The default priority is 0. Note: Priority does not affect the delivery order between subscribers with different ThreadModes!
Source code analysis
Eventbus.getdefault () creates the EventBus object
This method is used for registering, unregistering, and sending events when used by EventBus, so let’s first look at what it does.
public class EventBus {
static volatile EventBus defaultInstance;
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
private finalMap<Class<? >, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;private finalMap<Object, List<Class<? >>> typesBySubscriber;private finalMap<Class<? >, Object> stickyEvents; .// 1. Singleton design returns EventBus
public static EventBus getDefault(a) {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
// call the EventBus constructor
defaultInstance = newEventBus(); }}}returndefaultInstance; }...public EventBus(a) {
// call the parameter constructor and pass in an EventBusBuilder object
this(DEFAULT_BUILDER);
}
EventBus(EventBusBuilder builder) {
/ / log
logger = builder.getLogger();
// This collection can get subscribers based on the event type
//key: event type, value: set of subscribers subscribing to the event
subscriptionsByEventType = new HashMap<>();
// The set of events to which the subscriber subscribes
//key: subscriber, value: set of events to which the subscriber subscribes
typesBySubscriber = new HashMap<>();
// Set of sticky events
//key: event Class object, value: event object
stickyEvents = new ConcurrentHashMap<>();
// The Android main thread handles eventsmainThreadSupport = builder.getMainThreadSupport(); mainThreadPoster = mainThreadSupport ! =null ? mainThreadSupport.createPoster(this) : null;
//Background event sender
backgroundPoster = new BackgroundPoster(this);
// Asynchronous event sender
asyncPoster = new AsyncPoster(this); indexCount = builder.subscriberInfoIndexes ! =null ? builder.subscriberInfoIndexes.size() : 0;
// Subscribers subscribe to event lookups
subscriberMethodFinder = newSubscriberMethodFinder(builder.subscriberInfoIndexes, builder.strictMethodVerification, builder.ignoreGeneratedIndex); logSubscriberExceptions = builder.logSubscriberExceptions; logNoSubscriberMessages = builder.logNoSubscriberMessages; sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent; sendNoSubscriberEvent = builder.sendNoSubscriberEvent; throwSubscriberException = builder.throwSubscriberException; eventInheritance = builder.eventInheritance; executorService = builder.executorService; }}Copy the code
Internally, the method first creates an EventBus object through the singleton pattern, and eventually calls its parameter constructor when creating an EventBus, passing in an EventBus.Builder object. Inside the parameterized constructor we initialize the properties, which have several important properties: The roles of the subscriptionsByEventType, typesBySubscriber, stickyEvents, and subscriberMethodFinder attributes are given in the comments.
register(Object subscriber)
After creating the EventBus object, you need to register event subscribers with EventBus.
public class EventBus {
/**
* Registers the given subscriber to receive events. Subscribers must call {@link #unregister(Object)} once they
* are no longer interested in receiving events.
* <p/>
* Subscribers have event handling methods that must be annotated by {@link Subscribe}.
* The {@link Subscribe} annotation also allows configuration like {@link
* ThreadMode} and priority.
*/
public void register(Object subscriber) {
// Get the subscriber's Class object by reflectionClass<? > subscriberClass = subscriber.getClass();// 2. Gets the collection of events subscribed to by the subscriber through the subscriberMethodFinder object
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
// 3
for(SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); }}}private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// get the event typeClass<? > eventType = subscriberMethod.eventType;// 5, encapsulate the Subscription object
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
// Get the set of subscribers for the event by event type
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
If no subscriber subscribed to the event
if (subscriptions == null) {
// Create a collection and store it in the subscriptionsByEventType collection
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else { If any subscriber has subscribed to the event
// Determine if any of these subscribers have repeated subscriptions
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "+ eventType); }}int size = subscriptions.size();
// all subscribers of the event are traversed
for (int i = 0; i <= size; i++) {
// Insert according to the priority, if the lowest priority, insert to the end of the set
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break; }}// get the collection of all events subscribed to by the event subscriberList<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber);if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
// add the event to the collection
subscribedEvents.add(eventType);
// 12. Determine whether the event is a sticky event
if (subscriberMethod.sticky) {
if (eventInheritance) { // 13, determine the inheritance of the event, default is not inheritance
// get all sticky events and iterate through them to determine the inheritance relationshipSet<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();
/ / 15, call checkPostStickyEventToSubscription methodcheckPostStickyEventToSubscription(newSubscription, stickyEvent); }}}else{ Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); }}}private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if(stickyEvent ! =null) {
// if the sticky event is not emptypostToSubscription(newSubscription, stickyEvent, isMainThread()); }}private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
Depending on the type of threadMode, choose whether to reflect the calling method directly or to queue the event
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
// call by reflection
invokeSubscriber(subscription, event);
} else {
// Add sticky events to the queue
/ / in the end will invoke the EventBus. InvokeSubscriber (PendingPost PendingPost) method.
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); }}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); }}}public class SubscriberMethod {
final Method method; // The Method object that handles the event
final ThreadMode threadMode; // Thread model
finalClass<? > eventType;// Event type
final int priority; // Event priority
final boolean sticky; // Is it a sticky event
String methodString;
}
final class Subscription {
final Object subscriber;
final SubscriberMethod subscriberMethod;
}
Copy the code
Remember the code above 2 place by calling subscriberMethodFinder. FindSubscriberMethods (subscriberClass) method, access to all subscribers subscribe to events collection.
class SubscriberMethodFinder {
private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
private static finalMap<Class<? >, List<SubscriberMethod>> METHOD_CACHE =new ConcurrentHashMap<>();
private static final int POOL_SIZE = 4;
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];
List<SubscriberMethod> findSubscriberMethods(Class
subscriberClass) {
// select * from the previous cache
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if(subscriberMethods ! =null) {
// if the cache was cached before, return it directly
return subscriberMethods;
}
if (ignoreGeneratedIndex) { //ignoreGeneratedIndex is generally false
subscriberMethods = findUsingReflection(subscriberClass);
} else {
// get all subscription methods
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
// add to the cache set
METHOD_CACHE.put(subscriberClass, subscriberMethods);
returnsubscriberMethods; }}private List<SubscriberMethod> findUsingInfo(Class
subscriberClass) {
Get the FindState object from the array
If there is a direct return, if a new FindState object is not created
FindState findState = prepareFindState();
// Initialize findState based on event subscribers
findState.initForSubscriber(subscriberClass);
while(findState.clazz ! =null) {
// get the subscriberInfo and initialize it to 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 {
// Get Method from subscriber by reflection
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
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();
}
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// all declared methods in the subscriber are placed in an array
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// get the public method declared in the subscriber and set the parent class to be skipped
methods = findState.clazz.getMethods();
findState.skipSuperClasses = true;
}
// Iterate over these methods
for (Method method : methods) {
Get method modifiers :public, private, etc
int modifiers = method.getModifiers();
// 12、订阅方法为public同时不是abstract、static
if((modifiers & Modifier.PUBLIC) ! =0 && (modifiers & MODIFIERS_IGNORE) == 0) {
// 13, method parameter type arrayClass<? >[] parameterTypes = method.getParameterTypes();if (parameterTypes.length == 1) {
// Get method annotations
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
// 15, if there are annotations
if(subscribeAnnotation ! =null) { Class<? > eventType = parameterTypes[0];
// Add method and eventType to findState
if (findState.checkAdd(method, eventType)) {
// Get the threadMode object in the annotation
ThreadMode threadMode = subscribeAnnotation.threadMode();
// create a SubscriberMethod object and add it 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"); }}}// Get all subscriber methods from findState and release them
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
// Get a collection of all subscription methods for the subscriber
List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
// findState retrievals
findState.recycle();
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
if (FIND_STATE_POOL[i] == null) {
FIND_STATE_POOL[i] = findState;
break; }}}// return the collection
returnsubscriberMethods; }}Copy the code
Here, the registration process is almost finished, you can have a rest and relax.
summary
In the process of subscriber registration, the following steps are mainly carried out, detailed steps are in the code notes, you can view.
1. Create an EventBus object based on the singleton design pattern and initialize the EventBus by creating an EventBus.Builder object with three relatively important collections and a SubscriberMethodFinder object. 2. Call the Register method and first get the subscriber’s Class object through reflection. 3. Get the set of all subscribed events from the subscriber through the SubscriberMethodFinder object, which is first fetched from the cache and returned directly if there is one in the cache; If there are none in the cache, the annotated methods are reflected through the subscriber and returned in the collection. 4. Loop through the collection obtained in step 3 to bind subscribers to events. 5. After binding, it determines whether the bound event is a sticky event, and if it is, it calls postToSubscription directly to send the previously sent sticky event to the subscriber. And this makes a lot of sense, because when we talked about stickiness events, if a subscriber that was registered before the stickiness event was sent, when the stickiness event was sent, they would receive that event; That’s why subscribers who sign up after a sticky event is sent can also receive the event.
EventBus.getDefault().post(Object event)
public class EventBus {...public void post(Object event) {
// get the PostingThreadState of the current thread, which is a ThreadLocal
PostingThreadState postingState = currentPostingThreadState.get();
// set of events for the current thread
List<Object> eventQueue = postingState.eventQueue;
// add the event to the collection
eventQueue.add(event);
// Check whether events are being sent
if(! postingState.isPosting) {// Check whether it is the main thread
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
// as long as there are events in the event set, keep sending
while(! eventQueue.isEmpty()) { postSingleEvent(eventQueue.remove(0), postingState); }}finally {
postingState.isPosting = false;
postingState.isMainThread = false; }}}/ / currentPostingThreadState contains PostingThreadState ThreadLocal object
ThreadLocal is an internal data store class that stores data in a specified thread independently of each other.
// It creates an array of generic objects that it wraps around. Different threads have different array indexes. Each thread uses the get method to get the corresponding thread data.
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue(a) {
return newPostingThreadState(); }};// Data stored in each thread
final static class PostingThreadState {
final List<Object> eventQueue = new ArrayList<>(); // The event queue for the thread
boolean isPosting; // Whether it is being sent
boolean isMainThread; // Whether to send in the main thread
Subscription subscription; // Encapsulation of event subscribers and subscription events
Object event; // Event object
boolean canceled; // Whether to cancel sending}...private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
// Get the event Class objectClass<? > eventClass = event.getClass();boolean subscriptionFound = false;
if (eventInheritance) { // eventInheritance is generally true
// Find the class set of all the parent classes of the current event and the interface that implements itList<Class<? >> eventTypes = lookupAllEventTypes(eventClass);int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) { Class<? > clazz = eventTypes.get(h);// send a single event through the collectionsubscriptionFound |= postSingleEventForEventType(event, postingState, clazz); }}else {
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) { post(new NoSubscriberEvent(this, event)); }}}private staticList<Class<? >> lookupAllEventTypes(Class<? > eventClass) {synchronized (eventTypesCache) {
// Get the set of eventsList<Class<? >> eventTypes = eventTypesCache.get(eventClass);if (eventTypes == null) { // If it is null
eventTypes = newArrayList<>(); Class<? > clazz = eventClass;while(clazz ! =null) {
eventTypes.add(clazz); // Add events
addInterfaces(eventTypes, clazz.getInterfaces()); // Add the interface class for the current event
clazz = clazz.getSuperclass();// Gets the parent class of the current event
}
eventTypesCache.put(eventClass, eventTypes);
}
returneventTypes; }}// Loop to add the current event's interface class
static void addInterfaces(List
> eventTypes, Class
[] interfaces)
> {
for(Class<? > interfaceClass : interfaces) {if(! eventTypes.contains(interfaceClass)) { eventTypes.add(interfaceClass); addInterfaces(eventTypes, interfaceClass.getInterfaces()); }}}private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class
eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
// get all subscribers based on events
subscriptions = subscriptionsByEventType.get(eventClass);
}
if(subscriptions ! =null && !subscriptions.isEmpty()) {
// 9
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
// 10. Send events to subscribers
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
// reset postingState
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break; }}return true;
}
return false;
}
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
Call the subscription method according to the thread pattern of the subscription method
switch (subscription.subscriberMethod.threadMode) {
case POSTING: // The default type, which means that the send event operation calls the subscriber's response method directly, without switching between threads
invokeSubscriber(subscription, event);
break;
case MAIN: // Main thread, indicating that the subscriber's response method receives events on the main thread
if (isMainThread) { If the sender is in the main thread
invokeSubscriber(subscription, event);// Call the subscriber's response method directly
} else { If the sender of the event is not the main thread
// Add to the queue of mainThreadPoster and call the response method on the main thread
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:// Main thread priority mode
if(mainThreadPoster ! =null) {
mainThreadPoster.enqueue(subscription, event);
} else {
Call the response method in the sender's thread if it is not the main thread
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
If the event sender is on the main thread, join the queue of backgroundPoster and call the response method in the thread pool
backgroundPoster.enqueue(subscription, event);
} else {
// If it is not the main thread, the response method is called in the thread of the event sender
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
// There is no thread judgment, that is, the response method is called in the child thread whether it is in the main thread or not
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: "+ subscription.subscriberMethod.threadMode); }}... }Copy the code
summary
The analysis of sending events is over here. Let’s summarize what event sending does.
1. Get the current thread’s event collection, and add the event to the collection. 2. Through the loop, send events as long as there are events in the event set. 3. Get the Class object of the event. Find the Class collection of all the parent classes of the current event and the interface that implements it. Iterate over the collection and send by calling the method that sends a single event. 4. Get the set of all subscribers subscribing to it according to the event, traverse the set, and send the event to the subscribers. 5. When sent to the subscriber, the subscription method is called according to the thread mode of the subscription method. If thread switching is required, the subscription method is called by switching threads; Otherwise, call directly.
The issue of thread switching will be explored in a separate topic later in the article.
EventBus.getDefault().postSticky(Object event)
Next, let’s look at sending sticky events.
public class EventBus {
public void postSticky(Object event) {
synchronized (stickyEvents) {
// add the event to the sticky event set
stickyEvents.put(event.getClass(), event);
}
// 2. Send eventspost(event); }}Copy the code
summary
Sending sticky events does two things:
1. Add sticky events to the set of sticky events in the EventBus object. When a new subscriber enters, if the subscriber subscribes to the sticky event, it can be directly sent to the subscriber. 2. Send sticky events to existing event subscribers.
EventBus.getDefault().unregister(Object subscriber)
Having explored subscriber registration and message sending, let’s look at the method of unregistration.
public class EventBus {...public synchronized void unregister(Object subscriber) {
// get all events subscribed to by the subscriberList<Class<? >> subscribedTypes = typesBySubscriber.get(subscriber);if(subscribedTypes ! =null) {
// 2
for(Class<? > eventType : subscribedTypes) {// 3. Remove the subscriber from the collection of all subscribers subscribing to the event
unsubscribeByEventType(subscriber, eventType);
}
// Remove the subscriber from the collection
typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: "+ subscriber.getClass()); }}private void unsubscribeByEventType(Object subscriber, Class
eventType) {
// Get all subscribers of the event
List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if(subscriptions ! =null) {
int size = subscriptions.size();
// iterate over the collection
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
// Remove the subscriber from the collection
if (subscription.subscriber == subscriber) {
subscription.active = false; subscriptions.remove(i); i--; size--; }}}}... }Copy the code
summary
There are two things you can do to unregister:
1. Get all subscription methods of the subscriber and iterate over them. Then we get all the set of subscribers corresponding to each method and remove the subscribers from the set. Remove all subscription methods from the subscriber.
How does EventBus switch between threads?
Since the sending of the event involves the switching of the future, it is more likely to be mentioned in the usual interview, so it is studied as a separate topic.
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 {
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
This method calls the subscriber’s subscription method based on the ThreadMode type. ThreadMode is explained earlier, if not more clearly. There are two aspects of future switching involved in this method: mainThreadPoster and backgroundPoster. How ASYNC mode works will also be explored here.
mainThreadPoster.enqueue
MainThreadPoster is created when EventBus initializes, so let’s take a look.
.private final MainThreadSupport mainThreadSupport;
private finalPoster mainThreadPoster; . EventBus(EventBusBuilder builder) { ...// create mainThreadSupport
mainThreadSupport = builder.getMainThreadSupport();
// Create mainThreadPostermainThreadPoster = mainThreadSupport ! =null ? mainThreadSupport.createPoster(this) : null; . }Copy the code
Two objects are created: mainThreadSupport and mainThreadPoster. Let’s first look at what mainThreadSupport is.
MainThreadSupport getMainThreadSupport(a) {
if(mainThreadSupport ! =null) {
return mainThreadSupport;
} else if (Logger.AndroidLogger.isAndroidLogAvailable()) {
// Get the main thread Looper object, which may be empty again
Object looperOrNull = getAndroidMainLooperOrNull();
/ / according to the main thread which object returns AndroidHandlerMainThreadSupport object
return looperOrNull == null ? null :
new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull);
} else {
return null; }}Object getAndroidMainLooperOrNull(a) {
try {
// return the main thread Looper
return Looper.getMainLooper();
} catch (RuntimeException e) {
return null; }}public interface MainThreadSupport {
boolean isMainThread(a);
Poster createPoster(EventBus eventBus);
class AndroidHandlerMainThreadSupport implements MainThreadSupport {
private final Looper looper;
public AndroidHandlerMainThreadSupport(Looper looper) {
// Local initialization based on the looper object passed in externally
this.looper = looper;
}
@Override
public boolean isMainThread(a) {
// Determine whether the thread is the master thread
return looper == Looper.myLooper();
}
@Override
public Poster createPoster(EventBus eventBus) {
// Create a HandlerPoster object when the createPoster method is called
return new HandlerPoster(eventBus, looper, 10); }}}Copy the code
We see that mainThreadSupport is actually created from the main thread’s Looper object. MainThreadSupport is actually an interface, So here return AndroidHandlerMainThreadSupport object is its implementation class. Let’s have a look at mainThreadPoster object, when creating mainThreadPoster objects, invoke the mainThreadSupport. CreatePoster method, because the mainThreadSupport is an interface, So it actually calls the createPoster method of its implementation class object, creates a HandlerPoster object inside the method and returns, let’s look at HandlerPoster.
// This class inherits from Handler and implements the Poster interface
public class HandlerPoster extends Handler implements Poster {
private final PendingPostQueue queue;
private final int maxMillisInsideHandleMessage;
private final EventBus eventBus;
private boolean handlerActive;
protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
// This Handler object is the Handler for the main thread
super(looper);
this.eventBus = eventBus;
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
// Create an event queue
queue = new PendingPostQueue();
}
public void enqueue(Subscription subscription, Object event) {
// Encapsulate a PendingPost object based on the arguments passed in
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
// Add PendingPost to the queue
queue.enqueue(pendingPost);
if(! handlerActive) { handlerActive =true;
// Call sendMessage to send the event back to the main thread
// The following handleMessage method is eventually called
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();
// Infinite loop
while (true) {
PendingPost pendingPost = queue.poll();
// Double check to make sure pendingPost is not null, if it is, jump out of the loop
if (pendingPost == null) {
synchronized (this) {
pendingPost = queue.poll();
if (pendingPost == null) {
handlerActive = false;
return; }}}// Call the subscriber's subscription method with the reflected method.
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; }}}Copy the code
MainThreadPoster. Enqueue We’ve explored this, but it’s still running the Handler mechanism.
backgroundPoster.enqueue
BackgroundPoster is also initialized when EventBus is initialized
public class EventBus {...private final BackgroundPoster backgroundPoster;
// Thread pool object
private final ExecutorService executorService;
EventBus(EventBusBuilder builder) {
...
backgroundPoster = new BackgroundPoster(this); . }... }public class EventBusBuilder {
// Create a thread pool
private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();
ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
}
public class Executors {
public static ExecutorService newCachedThreadPool(a) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
newSynchronousQueue<Runnable>()); }}Copy the code
This also initializes a thread pool object, which will be used later. Take a look at the internal structure of BackgroundPoster.
final class BackgroundPoster implements Runnable.Poster {
private final PendingPostQueue queue;
private final EventBus eventBus;
private volatile boolean executorRunning;
BackgroundPoster(EventBus eventBus) {
this.eventBus = eventBus;
// Initialize the queue
queue = new PendingPostQueue();
}
public void enqueue(Subscription subscription, Object event) {
// Encapsulate the PendingPost object
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
// Add the PendingPost object to the queue
queue.enqueue(pendingPost);
if(! executorRunning) { executorRunning =true;
// The thread pool initialized earlier is used here
eventBus.getExecutorService().execute(this); }}}// Thread pool execution callback
@Override
public void run(a) {
try {
try {
// Infinite loop
while (true) {
// Get PendingPost in the queue to double check
// If null is returned, end the loop
PendingPost pendingPost = queue.poll(1000);
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
executorRunning = false;
return; }}}// Call the subscriber's subscription method using reflectioneventBus.invokeSubscriber(pendingPost); }}catch (InterruptedException e) {
eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e); }}finally {
executorRunning = false; }}}Copy the code
From the code above we can see that backgroundposter.enqueue actually uses thread pooling.
asyncPoster.enqueue
Let’s first look at the internal structure of asyncPoster.
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
There is a sense of deja vu in its internal structure, which is, yes, a simplified version of BackgroundPoster, but the principle is the same, again using thread pools.
conclusion
This is the end of the exploration of EventBus. The key steps are given in the code notes. If there is anything wrong, please criticize and correct it. One final question: What’s wrong with EventBus?
The resources
EventBus website