1. Introduction to eventbus
EventBus is a publish / subscribe event bus framework used in Android development. Based on the observer mode, it separates the receiver and sender of events, simplifies the communication between components, especially the communication between fragments, and can avoid many inconveniences caused by broadcast communication.
Simple to use, high efficiency and small size! Below is the official schematic diagram of EventBus:
2. How to use eventbus
2.1 use steps
a. Add dependent Library
Add in the build.gradle file corresponding to the project
compile 'org.greenrobot:eventbus:3.0.0'
b. Register
EventBus.getDefault().register(this);
c. Write off
EventBus.getDefault().unregister(this);
d. Construct send event class
public class MyEvent { private int mMsg; public MyEvent(int msg) { mMsg = msg; } public int getMsg(){ return mMsg; } }
e. Publish event
EventBus.getDefault().post(new MyEvent(time));
f. Receive event
@Subscribe(threadMode = ThreadMode.MAIN) public void onEvent(MyEvent event){ progressBar.setProgress(event.getMsg()); }
onEvent is Subscriber and MyEvent is the event type to listen to
2.2 use examples
Implement A progress bar, thread A sends the progress percentage, and thread B receives the progress percentage and displays it
reference resources: https://blog.csdn.net/bzlj2912009596/article/details/81664984
a. build.gradle
dependencies { //1. Add dependent Library compile 'org.greenrobot:eventbus:3.0.0' }
b. activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.wgh.eventbusdemo.MainActivity" android:orientation="vertical"> <ProgressBar android:id="@+id/progressbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="150dp" android:max="100" style="@style/Widget.AppCompat.ProgressBar.Horizontal"/> <Button android:id="@+id/button" android:layout_marginTop="10dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Start downloading"/> </LinearLayout>
c. MainActivity.java
package com.example.wgh.eventbusdemo; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.ProgressBar; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; public class MainActivity extends Activity { public ProgressBar progressBar = null; public int time = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { while (time<100){ time += 15; //4. Send message EventBus.getDefault().post(new MyEvent(time)); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }); progressBar = (ProgressBar) findViewById(R.id.progressbar); //2. Register EventBus EventBus.getDefault().register(this); } @Override protected void onDestroy() { super.onDestroy(); //6. Log off EventBus EventBus.getDefault().unregister(this); } //5. Receive message @Subscribe(threadMode = ThreadMode.MAIN) public void onEvent(MyEvent event){ progressBar.setProgress(event.getMsg()); } }
d. MyEvent.java
//3. Construct send message class public class MyEvent { private int mMsg; public MyEvent(int msg) { mMsg = msg; } public int getMsg(){ return mMsg; } }
Note: the function parameters of the sent message and the received message must be consistent, otherwise the function receiving the message cannot receive the message
For example, send a message: EventBus.getDefault().post(new MyEvent1(time)); When, only message function 1 can receive messages.
Receive message function 1: public void onEvent(MyEvent1 event) {...}
Receive message function 2: public void onEvent(MyEvent2 event) {...}
3. Principle of eventbus
3.1 Subscribe comments
Eventbus 3.0 starts to configure the event subscription method with the Subscribe annotation, and the method name is no longer used
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Subscribe { // Specifies the thread mode of the event subscription method, that is, the thread that executes the event subscription method to process events. The default is POSTING ThreadMode threadMode() default ThreadMode.POSTING; // Whether sticky events are supported. The default value is false boolean sticky() default false; // Specifies the priority of the event subscription method, which is 0 by default. If multiple event subscription methods can receive the same event, the one with higher priority will receive the event first int priority() default 0; }
- ThreadMode.POSTING is the default thread mode. Events sent in that thread are processed in the corresponding thread, avoiding thread switching and high efficiency.
- ThreadMode.MAIN. If an event is sent in the main thread (UI thread), the event is directly processed in the main thread; If an event is sent in a child thread, the event is queued first, and then the Handler switches to the main thread to process the event in turn.
- ThreadMode.MAIN_ORDERED: no matter which thread sends events, first put the events into the queue, and then switch to the main thread through the Handler to process the events in turn.
- Threadmode.backgroup: if events are sent in the main thread, the events will be queued first, and then the events will be processed in turn through the thread pool; If an event is sent on a child thread, the event is processed directly on the thread that sent the event.
- ThreadMode.ASYNC: no matter which thread sends events, the events are queued and processed through the thread pool.
3.2 relationship between subscribers and Events
Registered subscribers will be saved to subscriptionsByEventType and typesBySubscriber
a. subscriptionsByEventType
subscriptionsByEventType is a HashMap. key is the event type eventType of registered subscribers listening to events, and value is all subscribers listening to the same event type eventType
b. typesBySubscriber
typesBySubscriber is a HashMap. The key is the MainActivity of the current class of registered subscribers, and the value is all types of subscribers contained in the current class
3.3 viscous events
Generally, we use EventBus to prepare the method of subscribing to events, then register events, and finally send events, that is, we need to have the receiver of the event first. However, sticky events are just the opposite. We can send events first, and then prepare methods for subscribing to events and registering events. This event is called sticky events.
That is, for general events, first register the Subscriber listening to an event, and then send this event; However, a sticky event is to send an event first and then register the Subscriber of the event
4. Analysis of eventbus source code
4.1 register registration events
EventBus.getDefault().register(this);
Source code analysis:
EventBus.java (frameworks\base\packages\systemui\src\com\android\systemui\recents\events) 39817 2017/8/25 public class EventBus extends BroadcastReceiver { public static EventBus getDefault() { if (sDefaultBus == null) synchronized (sLock) { if (sDefaultBus == null) { if (DEBUG_TRACE_ALL) { logWithPid("New EventBus"); } sDefaultBus = new EventBus(Looper.getMainLooper()); } } return sDefaultBus; } } public void register(Object subscriber) { Class<?> subscriberClass = subscriber.getClass(); //Get the class of the currently registered Subscriber, here is MainActivity List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); //The function that gets all registered events in the current class, that is, the function that gets all Subscribe annotations synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); //Loop register all events } } }
subscriber is MainActivity, and subscriberMethods is a collection of methods that save all subscription events in the currently registered class MainActivity and its parent class
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class<?> eventType = subscriberMethod.eventType; //Get the parameter type of the current registered event, and distinguish different events according to the parameter type Subscription newSubscription = new Subscription(subscriber, subscriberMethod); //1. Create a Subscription corresponding to the current listening event CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType); //1. Key: subscriptionsByEventType is a HashMap, key is the eventType event type, and value is all functions that listen to the same event type if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); } 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); //1. Save the registered Subscriber to subscriptionsByEventType break; } } //------------------------------------------- List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber); //2. Key: typesBySubscriber is a HashMap. The key is the MainActivity of the current class of registered subscribers, and the value is all types of subscribers contained in the current class // If it does not exist, create a subscribedEvents and save it to typesBySubscriber if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); //2. Save the event type eventType corresponding to the registered Subscriber to typesBySubscriber // Related to viscous events, which will be analyzed 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); } } }
Registered subscribers will be saved to subscriptionsByEventType and typesBySubscriber
subscriptionsByEventType is a HashMap. key is the event type eventType of registered subscribers listening to events, and value is all subscribers listening to the same event type eventType
typesBySubscriber is a HashMap. The key is the MainActivity of the current class of registered subscribers, and the value is all types of subscribers contained in the current class
4.2 post sending event
EventBus.getDefault().post(new MyEvent(time));
Source code analysis:
public void post(Object event) { // currentPostingThreadState is a ThreadLocal of PostingThreadState type // The PostingThreadState class holds information such as event queues and thread patterns PostingThreadState postingState = currentPostingThreadState.get(); List<Object> eventQueue = postingState.eventQueue; // Add the event to be sent to the event queue eventQueue.add(event); // isPosting defaults to false if (!postingState.isPosting) { // Whether to be the main thread postingState.isMainThread = isMainThread(); postingState.isPosting = true; if (postingState.canceled) { throw new EventBusException("Internal error. Abort state was not reset"); } try { // Traverse event queue while (!eventQueue.isEmpty()) { // Send a single event // eventQueue.remove(0) to remove events from the event queue postSingleEvent(eventQueue.remove(0), postingState); } } finally { postingState.isPosting = false; postingState.isMainThread = false; } } }
Save the sent event to the eventQueue, traverse the eventQueue, and execute postSingleEvent to process the event
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class<?> eventClass = event.getClass(); boolean subscriptionFound = false; // eventInheritance is true by default, indicating whether to look up the parent class of the event if (eventInheritance) { // Find the Class of the current event type and save it to the collection together with the Class of the current event type List<Class<?>> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); // Traverse the Class collection and continue processing events for (int h = 0; h < countTypes; h++) { Class<?> clazz = eventTypes.get(h); subscriptionFound |= 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 boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) { CopyOnWriteArrayList<Subscription> subscriptions; synchronized (this) { // Gets the Subscription collection corresponding to the event type subscriptions = subscriptionsByEventType.get(eventClass); } // If you have subscribed to events of the corresponding type if (subscriptions != null && !subscriptions.isEmpty()) { for (Subscription subscription : subscriptions) { // Record events postingState.event = event; // Record the corresponding subscription postingState.subscription = subscription; boolean aborted = false; try { // Final event handling 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; }
According to the event type of the currently sent event, get all subscribers listening to this event type from subscriptionsByEventType, traverse these subscribers and execute postToSubscription for further processing, that is, the methods corresponding to all subscribers listening to this event will be called
4.3 event handling
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { // Determine the thread mode of the subscription event method switch (subscription.subscriberMethod.threadMode) { // The default thread mode is to send events in that thread and process events in that thread case POSTING: invokeSubscriber(subscription, event); break; // Handling events in the main thread case MAIN: // If an event is sent on the main thread, the event is processed directly on the main thread through reflection if (isMainThread) { invokeSubscriber(subscription, event); } else { // If an event is sent in a child thread, the event is queued, and the Handler switches to the main thread to process the event // mainThreadPoster is not empty mainThreadPoster.enqueue(subscription, event); } break; // No matter which thread sends events, first put the events into the queue, and then switch to the main thread through the Handler to process the events in turn. // mainThreadPoster is not empty case MAIN_ORDERED: if (mainThreadPoster != null) { mainThreadPoster.enqueue(subscription, event); } else { invokeSubscriber(subscription, event); } break; case BACKGROUND: // If events are sent in the main thread, the events are queued first, and then processed in turn through the thread pool if (isMainThread) { backgroundPoster.enqueue(subscription, event); } else { // If an event is sent in a child thread, the event is processed directly in the thread sending the event through reflection invokeSubscriber(subscription, event); } break; // No matter which thread sends the event, the event is queued and processed through the thread pool. case ASYNC: asyncPoster.enqueue(subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); } }
Determine how to handle the event according to the thread mode of the event subscription method and the thread sending the 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); } }
Use reflection to execute the onEvent method of subscribing to the event, so that the sent event will be received by the Subscriber and processed accordingly
4.4 viscous events
EventBus.getDefault().postSticky(new MyEvent(time));
Source code analysis:
public void postSticky(Object event) { synchronized (stickyEvents) { stickyEvents.put(event.getClass(), event); } post(event); }
Save the sent sticky events to stickyEvents
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { ...... ...... ...... // If the sticky attribute of the Subscribe annotation of the method currently subscribing to the event is true, the method can accept sticky events if (subscriberMethod.sticky) { // The default value is true, indicating whether to look up the parent class of the event if (eventInheritance) { // stickyEvents is to save the event type and corresponding events when sending sticky events Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet(); for (Map.Entry<Class<?>, Object> entry : entries) { Class<?> candidateEventType = entry.getKey(); // If candidateEventType is a subclass of eventType or if (eventType.isAssignableFrom(candidateEventType)) { // Get the corresponding event Object stickyEvent = entry.getValue(); // Handling sticky events checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) { if (stickyEvent != null) { postToSubscription(newSubscription, stickyEvent, isMainThread()); } }
eventType is the event type monitored by the current Subscriber, and the sent sticky events are retrieved from stickyEvents. If the event type monitored by the registered Subscriber is exactly the event type of sticky events, checkPostStickyEventToSubscription is directly called for event processing
5. EventBus summary
- Registration event: save the registered Subscriber to subscriptionsByEventType and typesBySubscriber
subscriptionsByEventType is a HashMap. key is the event type eventType of registered subscribers listening to events, and value is all subscribers listening to the same event type eventType
typesBySubscriber is a HashMap. The key is the MainActivity of the current class of registered subscribers, and the value is all types of subscribers contained in the current class - Send event: according to the event type of the current send event, get all subscribers listening to this event type from subscriptionsByEventType, traverse these subscribers and execute postToSubscription for further processing, that is, the methods corresponding to all subscribers listening to this event will be called
- Event handling: use reflection to execute the onEvent method of subscribing to event, so that the sent event will be received by the Subscriber and processed accordingly
Good article:
https://www.jianshu.com/p/d9516884dbd4
https://www.6hu.cc/archives/215.html