EventBus is an open source library for Android and Java developed by GreenRobot that uses the publisher/subscriber model for decoupling. EventBus enables communication between components in just a few lines of code, greatly decouples and simplifies code, eliminating dependencies and speeding up application development. Here’s EventBus at greenrobot.org/eventbus/. Before introducing the communication methods of EventBus, I will first introduce the conventional communication methods, such as setting up listening or using broadcast, to illustrate the disadvantages of conventional methods. Then, I will use EventBus to complete the communication between components. The advantages of EventBus in component communication are realized through a case study.

Developed by the GreenRobot organization, the open source library is well known for its project name, EventBus, which consists of two words — Event and Bus. So what is an Event? There are many types of events on The Android platform, the most common of which is touch events. When touch events occur, the application needs to make some responses to interact with the user. Communication is required between the components of the application and between the application and the user. With messaging, applications are interactive. So the next question is how do you communicate? That is, how do you send and process messages?

Before we answer that question, let’s take a look at what a Bus is. Now think outside of software development and into everyday life scenarios. We often need to get from one place to another, for example, starting in Shanghai. The destination is Hangzhou. In the beginning, we were at the starting point, and if we were on our own two feet. You know the route is a challenge. Usually, we use the public transportation system. With such a system, we only need to do two things: the first thing is to get on the bus from the point of departure; The second thing is to get off at your destination. The transportation system is invisible to us during the entire process, and we don’t need to know its internal details. There are only two things to do, the first is to get on the train at the point of departure, and the second is to get off at the destination.

So now we’re back in software development, and this time it’s not us we need to transport. It’s an event message that has a sender, specifically a method in a Java class that sends the message. There’s also a subscriber, and a subscriber is saying I’m interested in this event. I’m going to receive this event message, and I’m going to process this event message, using EventBus as a transportation system. There are two things we need to do. The first thing we need to do is publish the message on the sender, and then EventBus’s transport system takes care of delivering the message internally. The second thing we need to focus on is subscribing to and handling the event on the subscriber side. EventBus was a black box, and we only had to do two things. The first thing you do is publish events, and the second thing you do is subscribe and process events. This time, EventBus should have seen the light of day as a tool to help us communicate between components. You need to know how to subscribe to and publish events using EventBus to make it easy for components to communicate with each other using such an open source library.

Conventional communication method

Before I introduce these methods, let me introduce the functionality of the project Demo,

Listener Indicates the listening mode



MainActivity.java

public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private ImageView ivEmoji; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ivEmoji = findViewById(R.id.iv_emoji); } public void showDialogFragment(View view) { // Display DialogFragment PublisherDialogFragment fragment = new PublisherDialogFragment(); fragment.show(getSupportFragmentManager(), "publisher"); fragment.setEventListener(new PublisherDialogFragment.OnEventListener() { @Override public void onSuccess() { ivEmoji.setImageResource(R.drawable.ic_happy); } @Override public void onFailed() { ivEmoji.setImageResource(R.drawable.ic_bad); }}); }}Copy the code

Activity_main.xml layout file

<? The XML version = "1.0" encoding = "utf-8"? > <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <ImageView android:id="@+id/iv_emoji" android:layout_centerInParent="true" android:layout_width="200dp" android:layout_height="200dp"/> <Button android:layout_width="50dp" android:layout_height="50dp" android:layout_alignParentBottom="true" android:layout_alignParentEnd="true" android:layout_marginBottom="20dp" android:layout_marginEnd="20dp" android:onClick="showDialogFragment" android:background="@drawable/ic_add" /> </RelativeLayout>Copy the code

PublisherDialogFragment.java

public class PublisherDialogFragment extends DialogFragment { private static final String TAG = "PublisherDialogFragment"; private OnEventListener onEventListener; @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { AlertDialog.Builder builder = new  AlertDialog.Builder(getActivity()); builder.setTitle("Publisher"); String[] items = new String[]{"Success", "Failed"}; builder.setItems(items, (dialog, which) -> { switch (which){ case 0: // Success onEventListener.onSuccess(); break; case 1: // Failed onEventListener.onFailed(); break; default: break; }}); return builder.create(); } public interface OnEventListener{ void onSuccess(); void onFailed(); } public void setEventListener(OnEventListener listener){ this.onEventListener = listener; }}Copy the code

Do realize the function, but coupling or higher, to the issuer of the event must be defined interfaces, and when time is more and more when we need to define a growing number of interfaces and methods of such event receiver and sender coupling together, when the two components of dependence, coupling relationship is established, One of these changes affects the other component, and too much coupling is bad for software development, so what other means of communication are there? Let’s look at local radio.

Local broadcasting

Why use local broadcast? Other apps do not need to listen to this broadcast because it only communicates within the App, so local broadcast can be used:



Register broadcast listener in MainActivity. Disable broadcast listener when Activity is destroyed.

. import androidx.localbroadcastmanager.content.LocalBroadcastManager; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private ImageView ivEmoji; public static final String HAND_EVENT_ACTION = "hand_event_action";; public static final String result = "result"; private LocalBroadcastManager broadcastManager; BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if(intent.getAction().equals(HAND_EVENT_ACTION)){ if(intent.getBooleanExtra(result, false)){ ivEmoji.setImageResource(R.drawable.ic_happy); }else { ivEmoji.setImageResource(R.drawable.ic_bad); }}}}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ivEmoji = findViewById(R.id.iv_emoji); broadcastManager = LocalBroadcastManager.getInstance(this); } @Override protected void onStart() { super.onStart(); IntentFilter filter = new IntentFilter(HAND_EVENT_ACTION); broadcastManager.registerReceiver(receiver, filter); } @Override protected void onStop() { super.onStop(); broadcastManager.unregisterReceiver(receiver); } public void showDialogFragment(View view) { // Display DialogFragment PublisherDialogFragment fragment = new PublisherDialogFragment(); fragment.show(getSupportFragmentManager(), "publisher"); }}Copy the code

PublisherDialogFragment.java

. import androidx.localbroadcastmanager.content.LocalBroadcastManager; public class PublisherDialogFragment extends DialogFragment { private static final String TAG = "PublisherDialogFragment"; @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { AlertDialog.Builder builder = new  AlertDialog.Builder(getActivity()); builder.setTitle("Publisher"); String[] items = new String[]{"Success", "Failed"}; builder.setItems(items, (dialog, which) -> { Intent intent = new Intent(MainActivity.HAND_EVENT_ACTION); switch (which){ case 0: // Success intent.putExtra(MainActivity.result, true); LocalBroadcastManager.getInstance(getActivity()) .sendBroadcast(intent); break; case 1: // Failed intent.putExtra(MainActivity.result, false); LocalBroadcastManager.getInstance(getActivity()) .sendBroadcast(intent); break; default: break; }}); return builder.create(); }}Copy the code

For broadcast communication, the sender needs to create the Intent, and both parties must specify the Action of the Intent, and must specify the data fields in the Intent. For the recipient of the message, parsing the data in the Intent can be cumbersome. And the broadcast is running in the UI thread, if you want to in the background thread requires us to hand switch, it is not very convenient to use.

Communicate using EventBus

Let’s use EventBus directly to communicate between components, as in the example above, and see how much code can be omitted.



Using EventBus requires three key steps: Define the event -> subscribe to the subscriber -> publish the event to the publisher. First you need to introduce dependencies:

Implementation Group: 'org. greenRobot ', name: 'eventbus', version: '3.1.1'Copy the code

Define an event, which is essentially a normal Java class:

public class MyEvent { private boolean result; public MyEvent(boolean result) { this.result = result; } public boolean isResult() { return result; } public void setResult(boolean result) { this.result = result; }}Copy the code

Subscribing to events in mainActivity.java:

import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; public class MainActivity extends AppCompatActivity { private ImageView ivEmoji; @Override protected void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override protected void onStop() { super.onStop(); EventBus.getDefault().unregister(this); } @Subscribe public void onEvent(MyEvent myEvent){ if(myEvent.isResult()){ ivEmoji.setImageResource(R.drawable.ic_happy); }else { ivEmoji.setImageResource(R.drawable.ic_bad); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ivEmoji = findViewById(R.id.iv_emoji); } public void showDialogFragment(View view) { // Display DialogFragment PublisherDialogFragment fragment = new PublisherDialogFragment(); fragment.show(getSupportFragmentManager(), "publisher"); }}Copy the code

PublisherDialogFragment. Java as a publisher publish event:

public class PublisherDialogFragment extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle("Publisher"); String[] items = new String[]{"Success", "Failed"}; builder.setItems(items, (dialog, which) -> { switch (which){ case 0: // Success EventBus.getDefault().post(new MyEvent(true)); break; case 1: // Failed EventBus.getDefault().post(new MyEvent(false)); break; default: break; }}); return builder.create(); }}Copy the code

EventBus thread mode

POSTING is the default mode, which means that the event handler thread is in the same thread as the POSTING thread. 2. MAIN: The thread that represents the event handler is in the MAIN thread (UI) thread, so time-consuming operations cannot be performed here. BACKGROUND: Indicates that the event handler thread is in the BACKGROUND thread and therefore cannot perform UI operations. If the thread publishing the event is the main thread (the UI thread), the event handler will start a background thread, and if the thread publishing the event is a background thread, the event handler will use this thread. 4. ASYNC: indicates that no matter which thread the event is published on, the event handler will always create a new child thread to run and also cannot perform UI operations.

POSTING mode

POSTING mode is the default mode of EventBus. We don’t need to add schema options after the annotation of the subscriber method, but it can only be received in the same thread. That is, if you are POSTING in the main thread, you can only receive messages in the main thread. The message can only be received in the same child thread. In the following example, we demonstrate that there is a 50% random chance that the event will be sent in the new thread and a 50% random chance that the event will be sent in the UI thread.

Subscriber code :(mainactivity.java)

@subscribe (threadMode = threadmode. POSTING) public void onPostingEvent(final PostingEvent event){String threadInfo = Thread.currentThread().toString(); runOnUiThread(()->{ setPublisherThreadInfo(event.threadInfo); setSubscriberThreadInfo(threadInfo); }); }Copy the code

The publisher code: (PublisherDialogFragment. Java)

If (math.random () > 0.5f){new Thread("002"){@override public void run() {eventbus.getDefault ().post(new) PostingEvent(Thread.currentThread().toString())); } }.start(); }else { EventBus.getDefault().post(new PostingEvent(Thread.currentThread().toString())); }Copy the code

The MAIN mode

The MAIN mode ensures that the receiving method specified by the subscriber must be executed in the MAIN thread, where UI updates can be safely performed. Whether the publisher publishes the message in the main thread or in that sliver thread, this side receives the message in the main thread.

Subscriber code :(mainactivity.java)

Subscribe(threadMode = threadmode. MAIN) public void onMainEvent(final MainEvent event){String threadInfo = Thread.currentThread().toString(); runOnUiThread(()->{ setPublisherThreadInfo(event.threadInfo); setSubscriberThreadInfo(threadInfo); }); }Copy the code

The publisher code: (PublisherDialogFragment. Java)

If (math.random () > 0.5f){new Thread("002"){@override public void run() {eventbus.getDefault ().post(new) MainEvent(Thread.currentThread().toString())); } }.start(); }else { EventBus.getDefault().post(new MainEvent(Thread.currentThread().toString())); }Copy the code

MAIN_ORDER mode

The MAIN_ORDER mode is similar to the MAIN mode, but with a slight difference: the MAIN_ORDER mode is in the MAIN thread like MAIN, except that the publisher can continue to run its own code without waiting for the subscriber to complete the subscription:

Subscriber code :(mainactivity.java)

Subscribe(threadMode = threadmode. MAIN_ORDERED) public void onMainOrderEvent(final MainOrderEvent event){ Log.i(TAG, "onMainOrderEvent: enter @" + SystemClock.uptimeMillis()); setPublisherThreadInfo(event.threadInfo); setSubscriberThreadInfo(Thread.currentThread().toString()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } Log.i(TAG, "onMainOrderEvent: exit @" + SystemClock.uptimeMillis()); }Copy the code

The publisher code: (PublisherDialogFragment. Java)

Log.i(TAG, "onCreateDialog: before @" + SystemClock.uptimeMillis());
EventBus.getDefault().post(new MainOrderEvent(Thread.currentThread().toString()));
Log.i(TAG, "onCreateDialog: after @" + SystemClock.uptimeMillis());
Copy the code

Same code when using MAIN mode :(i.e@Subscribe(threadMode = ThreadMode.MAIN)) :

Same code when using MAIN_ORDER mode :(i.e@Subscribe(threadMode = ThreadMode.MAIN_ORDER)) :

BACKGROUND



Subscriber code :(mainactivity.java)

@subscribe (threadMode = Threadmode. BACKGROUND) public void onBackgroundEvent(final MainOrderEvent event){ final String threadInfo = Thread.currentThread().toString(); runOnUiThread(()->{ setPublisherThreadInfo(event.threadInfo); setSubscriberThreadInfo(threadInfo); }); }Copy the code

The publisher code: (PublisherDialogFragment. Java)

If (math.random () > 0.5f){new Thread("002"){@override public void run() {eventbus.getDefault ().post(new) BackgroundEvent(Thread.currentThread().toString())); } }.start(); }else { EventBus.getDefault().post(new BackgroundEvent(Thread.currentThread().toString())); }Copy the code

ASYNC

The event handler will always create a new child thread regardless of the thread on which the event is published, and also cannot perform UI operations:



Subscriber code :(mainactivity.java)

Subscribe(threadMode = threadmode. ASYNC) public void onAsyncEvent(final AsyncEvent event){final String threadInfo = Thread.currentThread().toString(); runOnUiThread(()->{ setPublisherThreadInfo(event.threadInfo); setSubscriberThreadInfo(threadInfo); }); }Copy the code

The publisher code: (PublisherDialogFragment. Java)

If (math.random () > 0.5f){new Thread("002"){@override public void run() {eventbus.getDefault ().post(new) AsyncEvent(Thread.currentThread().toString())); } }.start(); }else { EventBus.getDefault().post(new AsyncEvent(Thread.currentThread().toString())); } break;Copy the code

The following is the code of the whole project:

public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setTitle("Subscriber"); } @Override protected void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override protected void onStop() { super.onStop(); EventBus.getDefault().unregister(this); } @subscribe (threadMode = threadmode. POSTING) public void onPostingEvent(final PostingEvent event){String  threadInfo = Thread.currentThread().toString(); runOnUiThread(()->{ setPublisherThreadInfo(event.threadInfo); setSubscriberThreadInfo(threadInfo); }); Subscribe(threadMode = threadmode. MAIN) public void onMainEvent(final MainEvent event){String threadInfo = Thread.currentThread().toString(); runOnUiThread(()->{ setPublisherThreadInfo(event.threadInfo); setSubscriberThreadInfo(threadInfo); }); Subscribe(threadMode = threadmode. MAIN_ORDERED) public void onMainOrderEvent(final MainOrderEvent event){ Log.i(TAG, "onMainOrderEvent: enter @" + SystemClock.uptimeMillis()); setPublisherThreadInfo(event.threadInfo); setSubscriberThreadInfo(Thread.currentThread().toString()); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } Log.i(TAG, "onMainOrderEvent: exit @" + SystemClock.uptimeMillis()); } @subscribe (threadMode = threadmode. BACKGROUND) public void onGroundevent (final BackgroundEvent) event){ final String threadInfo = Thread.currentThread().toString(); runOnUiThread(()->{ setPublisherThreadInfo(event.threadInfo); setSubscriberThreadInfo(threadInfo); }); Subscribe(threadMode = threadmode. ASYNC) public void onAsyncEvent(final AsyncEvent event){final String threadInfo = Thread.currentThread().toString(); runOnUiThread(()->{ setPublisherThreadInfo(event.threadInfo); setSubscriberThreadInfo(threadInfo); }); } private void setPublisherThreadInfo(String threadInfo){ setTextView(R.id.tv_publisher_thread, threadInfo); } private void setSubscriberThreadInfo(String threadInfo){ setTextView(R.id.tv_subscriber_thread, threadInfo); } // Run on UI Thread private void setTextView(int resId, String text){ TextView textView = findViewById(resId); textView.setText(text); TextView. SetAlpha (0.5 f); textView.animate().alpha(1).start(); } public void showDialogFragment(View view) { // Display DialogFragment PublisherDialogFragment fragment = new PublisherDialogFragment(); fragment.show(getSupportFragmentManager(), "publisher"); }}Copy the code

PublisherDialogFragment.java

public class PublisherDialogFragment extends DialogFragment { private static final String TAG = "PublisherDialogFragment"; @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { AlertDialog.Builder builder = new  AlertDialog.Builder(getActivity()); builder.setTitle("Publisher"); String[] items = new String[]{"Posting", "Main", "MainOrder", "Background", "Async"}; builder.setItems(items, (dialog, which) -> { switch (which){ case 0: // Posting Mode if(math.random () > 0){new Thread("002"){@override public void run() {// Posting Mode if(math.random () > 0){new Thread("002"){@override public void run() { EventBus.getDefault().post(new PostingEvent(Thread.currentThread().toString())); } }.start(); }else { EventBus.getDefault().post(new PostingEvent(Thread.currentThread().toString())); } break; case 1: // Main Mode if(math.random () > 0.5f){new Thread("002"){@override public void run() {eventbus.getDefault ().post(new) MainEvent(Thread.currentThread().toString())); } }.start(); }else { EventBus.getDefault().post(new MainEvent(Thread.currentThread().toString())); } break; case 2: // Main_Order Mode Log.i(TAG, "onCreateDialog: before @" + SystemClock.uptimeMillis()); EventBus.getDefault().post(new MainOrderEvent(Thread.currentThread().toString())); Log.i(TAG, "onCreateDialog: after @" + SystemClock.uptimeMillis()); break; case 3: // BACKGROUND Mode if(math.random () > 0.5f){new Thread("002"){@override public void run() { EventBus.getDefault().post(new BackgroundEvent(Thread.currentThread().toString())); } }.start(); }else { EventBus.getDefault().post(new BackgroundEvent(Thread.currentThread().toString())); } break; case 4: // ASYNC Mode if(math.random () > 0.5f){new Thread("002"){@override public void run() {eventbus.getDefault ().post(new) AsyncEvent(Thread.currentThread().toString())); } }.start(); }else { EventBus.getDefault().post(new AsyncEvent(Thread.currentThread().toString())); } break; default: break; }}); return builder.create(); }}Copy the code

activity_main.xml

<? The XML version = "1.0" encoding = "utf-8"? > <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <ImageView android:id="@+id/iv_emoji" android:layout_centerInParent="true" android:layout_width="200dp" android:layout_height="200dp"/> <LinearLayout android:orientation="vertical" android:layout_width="wrap_content" android:layout_centerInParent="true" android:layout_height="wrap_content"> <TextView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Publisher:"/> <TextView android:id="@+id/tv_publisher_thread" android:textColor="#00f" android:textSize="20sp" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:layout_width="wrap_content"  android:layout_height="wrap_content" android:text="Subscriber:"/> <TextView android:id="@+id/tv_subscriber_thread" android:textColor="#f00" android:textSize="20sp" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout> <Button android:layout_width="50dp" android:layout_height="50dp" android:layout_alignParentBottom="true" android:layout_alignParentEnd="true" android:layout_marginBottom="20dp" android:layout_marginEnd="20dp" android:onClick="showDialogFragment" android:background="@drawable/ic_add" /> </RelativeLayout>Copy the code

That concludes the basic use of EventBus and the threading pattern.

The resources

EventBus: Events for Android

Wikipedia — Publish/subscribe