Using an EventBus in Android Pt 3: Multi-Threaded Event Handling

Originally posted February 12, 2015

In this final installment of EventBus series I want to discuss how Green Robot’s EventBus can be used to more simply handle asynchronous tasks running across multiple threads.

AsyncTasks, Loaders, and Executors… oh my!

There are quite a number of ways to execute code asynchronously (i.e. in parallel to the UiThread) on Android. AsyncTask is the easiest to user mechanism and requires a relatively small amount of set-up and code. However, it’s proper use is limited in scope. As it states in the Android Documentation:

AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask.

But even for “short operations” you can have issues, particularly given the nature of Activity/Fragment lifecycles. Since AsyncTasks will continue running even if the Activity/Fragment that started them is destroyed, it can be likely that your onPostExecute will end up throwing an IllegalStateException, especially if you are trying to update the UI.

Android 3.0 introduced the Loaders API which addresses the Activity/Fragment lifecycle problem, and is in fact very effective in its goal. However, the Loaders API is specifically designed for the asynchronous loading of data into an Activity or Fragment. And while the loading of data is a very common operation that should be performed asynchonously, it’s certainly not the only type of operation that you might want to move out of the UiThread. Also, using Loaders requires yet another Listener interface to be implemented by your Activity/Fragment. There’s nothing inherently wrong with this, but I am personally not a fan of the pattern. (My main issue with it is that you easily end up with to many callback implementations in one place, resulting in code that can be difficult to read.) Finally, Activities and Fragments are not the only place from which you may need to kick-off an asynchronous operation. If you are in a Service, for example, you have no access to a LoaderManager, so you are either back to using AsyncTask or java.util.concurrent.

The java.util.concurrent package is great, and I have used it extensively in both Android and non-Android applications. It does, however, require a little more set-up and management that something as simple as AsyncTask. You need to initiate your ExecutorService, manage/monitor its lifecycle, and possibly keep up with a number of Future objects.

Given the right use-case, AsyncTasks, Loaders, and Executors can all be very effective. But in a complex application, selecting the right tool for each individual job, you are likely to end up using all three. And if you do, you now have 3 distinct frameworks to maintain, all just to handle concurrency.

Green Robot to the Rescue!

GR’s EventBus has a great built-in mechanism for handling concurrency. In your in your subscribing class, you can implement any of 4 different types of handler methods. When a matching event is posted, EventBus will pass the event to each of the handler methods in a different way in terms of concurrency:

  • onEvent(T event): Runs on the same thread from which the event was posted.
  • onEventMainThread(T event): Runs on the Main (UI) Thread, regardless of the thread from which was posted.
  • onEventAsync(T event): Runs on a thread different from both the thread from which posted and the UI Thread.
  • onEventBackgroundThread(T event): If the posting thread is not the UI Thread, it runs on the posting thread. If the event was posted from the UI Thread, however, it runs on a single thread that EventBus maintains. Multiple events are handled by this single background thread synchronously.

These options are very powerful and very easy to use. Let’s say you have an operation that will take some time (because of network calls, large amount of processing, etc). This operation needs to be triggered via some UI action, and the UI needs to be updated when the operation is complete. For this example, I’m going to say that the UI action is a Button click, the Button is defined in an Activity, and the long-running operation will be performed in a Service. We can accomplish this as follows:

SomeActivity.java:

...
@Inject EventBus bus;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
eventBus.post(new DoOperationEvent());
}
});
}

@Override
protected void onResume() {
super.onResume();
bus.register(this);
}

@Override
protected void onPause() {
bus.unregister(this);
super.onPause();
}

public void onEventMainThread(OperationCompleteEvent event) {
Toast.makeText(this, "Operation complete!", Toast.LENGTH_SHORT).show();
}

...

SomeService.java:

...
@Inject EventBus bus;
...
@Override
protected void onCreate() {
super.onCreate();
bus.register(this);
}

@Override
protected void onDestroy() {
bus.unregister(this);
super.onDestroy();
}

public void onEventAsync(DoOperationEvent event){
// DO SOMETHING LONG-RUNNING HERE
eventBus.post(new OperationCompleteEvent());
}

...

While this is a very simple example, it shows how concise this approach can be. There are no listener interfaces to implement, and the lifecycle issues are dealt with since the Activity will only receive the OperationCompleteEvent if it is active. What’s more, if there is a configuration change or the Activity is otherwise destroyed and recreated in the time between the 2 events, the OperationCompleteEvent can still be received.

It is also very easy to see how we can build upon this. For example, if you want to post progress updates, you can simply implement another event class encapsulating the amount complete and post it. Or, if you need to ensure that other events of either the same or another type are not handled in parallel you can switch to using the onEventBackgroundThread method instead.

Pimp Your Bus

The easiest way to instatiate EventBus is via EventBus.getDefault(). However, there are a number of useful of configurations that can be set by using the EventBusBuilder class which is available via EventBus.builder(). Particularly relevant to this article is the option to specify your own ExecutorService. By default the EventBus creates its own ExecutorService via Executors.newCachedThreadPool() which is your best option most of the time. However, there may be situations where you want to have more explicit control of the number of threads used by the EventBus. In that case you can initialize your EventBus like this:

EventBus.builder().executorService(Executors.newFixedTheadPool(NUM_THREADS)).installDefaultEventBus();

The other configuration options available in the EventBusBuilder provide a number controls around Exception handling, as well as a switch for controlling whether or not inheritance is supported for Event classes. These options are outside the scope of this article, but I highly recommend looking into them. It doesn’t appear that GR has documented them all, but if you look at the source code for EventBusBuilder and EventBus it should be pretty easy to understand them.

That wraps up my EventBus series. In my next post I will be discussion my own approach to implementing the MVP pattern in Android. Until then, happy bussing! :)


The original Disqus thread for this article can be found here.