I. Introduction to EventBus
EventBus README:github.com/greenrobot/… The official document: greenrobot.org/eventbus/do…
# EventBus is a publish/subscribe event bus forAndroid and Java. # EventBus is an event publish/subscribe framework.Copy the code
2. Basic Usage
For example, in MainActivity, after the user enters the name and age, click OK to send the user information to another Activity for display. EventBus implements the following method:
2.1 Adding a Dependency
dependencies {
// eventbus
implementation 'org. Greenrobot: eventbus: 3.1.1'
}
Copy the code
2.2 define the Event
First, define Event, the user information class as follows:
public class User{
public String name;
public int age;
}
Copy the code
2.3 the Event Subscribers
Event subscriber; It is divided into three parts: registration, processing and cancellation of registration. If you do not cancel registration, there will be memory leakage. An event subscriber can be an Activity/Fragment/View, for example:
public class HelloActivity extends Activity {
@Override
public void onStart(a) {
super.onStart();
// Register subscribers
EventBus.getDefault().register(this);
}
Subscribers are not called from any thread in threadMode. // Declare event handlers
@Subscribe(threadMode = ThreadMode.MAIN)
public void welcomeUser(User user) {
CenterToast.makeText(this."hello,"+ user.name, Toast.LENGTH_SHORT).show();
}
@Override
public void onStop(a) {
super.onStop();
// Unregister subscribers
EventBus.getDefault().unregister(this); }}Copy the code
Similarly, event subscribers can be custom classes:
public class UserRequestManager {
public UserRequestManager {
// Register subscribers
EventBus.getDefault().register(this);
}
Subscribers are not called from any thread in threadMode. // Declare event handlers
@Subscribe(threadMode = ThreadMode.MAIN)
public void addUser(User user) {
UserRequest reqeust = new UserReqeust(user);
request.request();
}
public void release(a) {
// Unregister subscribers
EventBus.getDefault().unregister(this); }}Copy the code
2.4 the Event Publisher
Event sender;
public class MainActivity extends Activity {
public void onConfirmClick(a) {
User user = new User();
user.name = nameTextView.getText();
user.age = Integer.parseInt(ageTextView.getText());
/ / send the EventEventBus.getDefault().post(user); }}Copy the code
Third, ThreadMode
The official document: greenrobot.org/eventbus/do…
In the annotation declaring Event handlers, you can set ThreadMode to indicate from which thread Subscribers are called when the Event is sent to them. Subscribers have five threadmodes to choose from:
3.1 ThreadMode: POSTING
By default, Subscribers are called on the same thread as the event sender. Event publishing is synchronous, that is, after event publishing, Subscribers of all events are called in turn. Since there are no thread switches, this method of execution is the least expensive; Consider this approach for short tasks that do not require the main thread;
3.2 ThreadMode: MAIN
Subscribers are called on the main thread. It is used to update the UI when data changes, to avoid time-consuming tasks blocking the main thread.
3.3 ThreadMode: MAIN_ORDERED
Subscribers are called on the main thread. In contrast to MAIN, MAIN_ORDERED goes to the queue first and Subscribers are given later, for example if another Event was sent within an Event:
public class Demo1 {
@Subscribe
public void addUser(User user) {
Log.d("DemoTag"."demo1 post start");
EventBus.getDefault().post(user);
Log.d("DemoTag"."demo1 post end"); }}public class Demo2 {
@Subscribe(threadMode = ThreadMode.MAIN)
public void addUser(User user) {
Log.d("DemoTag"."demo2:"+ user.name); }}public class Demo3 {
@Subscribe(threadMode = ThreadMode.MAIN_ORDER)
public void addUser(User user) {
Log.d("DemoTag"."demo3:"+ user.name); }}Copy the code
In the example above, the output is:
>demo1 post start
>demo2:jack
>demo1 post end
>demo3:jack
Copy the code
Since MAIN executes synchronously, MAIN executes before the method ends, whereas MAIN_ORDERED executes after the method ends.
3.4 ThreadMode: BACKGROUND
Subscribers are called in the background thread. (1) If POST thread is not Subscribers, Subscribers are called directly from the POST thread. (2) If the publishing thread is the main thread, EventBus will use a single background thread to deliver all events sequentially, so event handlers using this pattern should return quickly to avoid blocking the background thread.
3.5 ThreadMode: ASYNC
Subscribers are always called on a single thread (neither POST nor main thread). EventBus uses a thread pool to implement thread scheduling in this mode.
Four, Sticky Events
For some events that need to be retained for a long time, sticky events can be used. The last sticky event of EventBus is kept in memory, and the latest event can be obtained after a new subscriber is initialized, or the latest event can be queried explicitly. Use method as follows: (1) Sticky Event Publisher
EventBus.getDefault().postSticky(new User("jack".18));
Copy the code
(2) Sticky Event Subscriber
@Override
public void onStart(a) {
super.onStart();
EventBus.getDefault().register(this);
}
// When an Activity is created, it receives the latest event data
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEvent(MessageEvent event) {
textField.setText(event.message);
}
@Override
public void onStop(a) {
EventBus.getDefault().unregister(this);
super.onStop();
}
Copy the code
(3) Explicitly query events
public class Demo{
public void fun(a) {
User user = EventBus.getDefault().getStickyEvent(User.class);
if(user ! =null) {
// "Consume" the sticky event
EventBus.getDefault().removeStickyEvent(user);
// Now do something with it}}Copy the code
Five, source code analysis
Source code analysis is done through two processes:
# 1.Eventbus.getdefault ().register(this);
# 2.Eventbus.getdefault ().post("hello");
Copy the code
5.1 EventBus. GetDefault ()
Eventbus.getdefault () is a singleton:
public class EventBus {
static volatile EventBus defaultInstance;
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
// Subscribe to method lookup utility classes: get class and method names and annotations by reflection, and save objects and methods in the HashMap
private final SubscriberMethodFinder subscriberMethodFinder;
//
private final ExecutorService executorService;
// Singleton mode
public static EventBus getDefault(a) {
if (defaultInstance == null) {
synchronized (EventBus.class) {
if (defaultInstance == null) {
defaultInstance = newEventBus(); }}}returndefaultInstance; }}Copy the code
5.2 Subscription Process
The subscription process is as follows:
EventBus.getDefault().register(this);
Copy the code
Register ()
public class EventBus {
// Key is EventType and value is the Subscriber list for all events of this type
// EventType is the EventType, such as the User or String examples in this article;
private finalMap<Class<? >, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;// Key is the Subscriber object, and value is all the event types to which the object subscribes
// correspond to subscriptionsByEventType
private finalMap<Object, List<Class<? >>> typesBySubscriber;public void register(Object subscriber) { Class<? > subscriberClass = subscriber.getClass();// The subscriberMethodFinder gets all the methods in the class marked with @SUBSCRIBE by reflection
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
// Loop adds Subscribe to the corresponding hashMap based on EventTypesubscribe(subscriber, subscriberMethod); }}}private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<? > eventType = subscriberMethod.eventType; Subscription newSubscription =new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
// ...
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
// Add the object to the subscriber list corresponding to EventType by priority
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break; }}// add the EventType to the EventType list of the Subscriber subscriptionList<Class<? >> subscribedEvents = typesBySubscriber.get(subscriber); subscribedEvents.add(eventType);// ...
}
// The deregistration process works similarly
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()); }}}Copy the code
5.3 Release Process
The release process is as follows:
EventBus.getDefault().post("hello");
Copy the code
Post () :
public class EventBus {
// TheadMode corresponds to the Poster;
// There are 5 kinds of ThreadMode, and only 3 kinds of poster are implemented synchronously because of POSTING, and MAIN does not need poster distribution
private final Poster mainThreadPoster;
private final BackgroundPoster backgroundPoster;
private final AsyncPoster asyncPoster;
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;
eventQueue.add(event);
if(! postingState.isPosting) { postingState.isMainThread = isMainThread(); postingState.isPosting =true;
try {
while(! eventQueue.isEmpty()) {// Post single Event
postSingleEvent(eventQueue.remove(0), postingState); }}finally {
postingState.isPosting = false;
postingState.isMainThread = false; }}}private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<? > eventClass = event.getClass();boolean subscriptionFound = false;
// ...
/ / according to the EventType
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
if(! subscriptionFound) {// No subscriber found...}}private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class
eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
// Get the subscriber list based on EventType
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if(subscriptions ! =null && !subscriptions.isEmpty()) {
// Iterate through the list to send events to subscribers
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
// The actual post method
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break; }}return true;
}
return false;
}
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); }}void invokeSubscriber(Subscription subscription, Object event) {
// Finally call Subscriber's event handler by reflection
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
PostToSubscription ThreadMode: ASYNC
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);
// hand it over to the thread pool of eventBusBuilder.default_executor_service to schedule execution
eventBus.getExecutorService().execute(this);
}
@Override
public void run(a) {
PendingPost pendingPost = queue.poll();
if(pendingPost == null) {
throw new IllegalStateException("No pending post available");
}
// Finally call subscriber's event handler by reflectioneventBus.invokeSubscriber(pendingPost); }}Copy the code
Six, the annotation processor
It can be seen from the source code analysis that when registering Subscriber, the method identified by @SUBSCRIBE annotation is searched through reflection. In addition to this approach, EventBus provides an annotation-processor approach for handling annotations, eliminating the need for reflection searches. The usage method is as follows:
6.1 Gradle configuration is as follows
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
// Custom annotation-processor generated class name
arguments = [eventBusIndex: 'com.bc.demo.EventSubscriberInfoIndex']}}}dependencies {
implementation 'org. Greenrobot: eventbus: 3.1.1'
annotationProcessor 'org. Greenrobot: eventbus -- the annotation processor: 3.0.1'
}
Copy the code
6.2 Modifying the Default EventBus
public class App extends Application{
@Override
public void onCreate(a) {
super.onCreate();
Change the default EventBus
EventBus.builder().addIndex(newEventSubscriberInfoIndex()).installDefaultEventBus(); }}Copy the code
After that, the process for using EventBus is the same as before.