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.