Multithreaded event handling used by Android EventBus

  • 2020-11-03 22:36:03
  • OfStack

In the final installment of this series, I want to talk about how EventBus of GR is simple and effective in handling multithreaded asynchronous tasks.

AsyncTask Loader and Executor... Come on!

There are many ways to perform asynchronous operations in Android (that is, parallel to the UI thread). AsyncTask is the simplest mechanism for users and requires only a small amount of setup code. However, its use is limited, as described in the official Android document:

AsyncTask was designed as a utility class containing Thread and Handler internally, but it is not itself a part of the generic threading framework. AsyncTask should be used whenever possible to perform 1 short operation (a few seconds at most). If you need to perform tasks for a long time in a thread, it is recommended that you use the various API's available in the ES21en.util.concurrent package, such as Executor, ThreadPoolExecutor, and FutureTask.

However, even performing short operations can cause problems, especially in relation to the Activity/Fragment life cycle. As AsyncTask will continue to run (even if the Activity/Fragment that started them has been destroyed). Thus, once you try to update UI in the onPostExecute method, you will end up throwing an IllegalStateException exception.

Loader API was introduced in Android 3.0 to address the Activity/Fragment lifecycle issues (and they do work). Loader API is designed to load data asynchronously to Activity/Fragment. While loading data is a very common asynchronous operation, it is not the only 11 operations that need to be separated from the UI thread. Loader also needs to implement an additional listening interface in Activity/Fragment. While there's nothing wrong with doing this, I personally don't like this pattern (What I mean is that you end up with code that contains so many callbacks that it becomes unreadable). Finally, Activity and Fragment are not the only places where threading for asynchronous operations is required. For example, if you're in Service, you can't access LoaderManager, so you'll end up using either AsyncTask or java.util.concurrent.

The ES63en.util.concurrent package is great and I can use it on both Android and non-ES67en projects. However, it requires a little more configuration and management than AsyncTask. You will need to initialize ExecutorService, manage and monitor its life cycle, and may need to work with some Future objects.

AsyncTask, Loader, and Executor are very effective when used properly. But in complex applications, you need to choose the right tool for each task, and you might end up using all three. You have to maintain three different frameworks for handling concurrency.

Green Robot is here to help!

GR's EventBus has a great concurrency mechanism built in. In a listening class, you can implement four different types of processing. When a matching event is sent, EventBus sends the event to the corresponding processing method according to the different concurrency model:

onEvent(T event) : Runs in the same thread as the event being sent.
onEventMainThread(T event) : Runs in the main (UI) thread, regardless of which thread the event is sent from.
onEventAsync(T event) : Runs in a separate thread, neither the UI thread nor the thread that sends the event.
onEventBackgroundThread(T event) : If the thread sending the event is not an UI thread, it runs in that thread. If the UI thread is sending the event, it runs in a separate thread maintained by EventBus. Multiple events are handled synchronously by this single background thread.

These methods are powerful and easy to use. For example, there is a time-consuming operation (maybe network call, massive data processing, etc.) that needs to be triggered by the behavior on UI and needs to be updated to UI after the operation is completed. In this example, the UI behavior is button clicking, the button is in activity, and the time-consuming operation is in service. We can do this in the following way:

Java

Although this example is relatively simple, it illustrates the point very succinctly. There is no need to implement a listening interface, and there are no life-cycle issues (since activity can only receive OperationCompleteEvent events as long as it exists). In addition, if a configuration change occurs (rotating the screen) or some other reason causes OperationCompleteEvent to be destroyed and rebuilt between events, the OperationCompleteEvent event can still be received in the end.

In addition, we can easily think of some other USES. For example, if you need to send an update progress, you can simply implement an additional event class that encapsulates the progress value and then send it out. Alternatively, if you want one of the other events (whether of the same or different types) not to be processed in parallel (executed synchronously), you can choose to use onEventBackgroundThread.

Rely on Bus

The easiest way to instantiate EventBus is through ES134en.getDefault (). However, the EventBusBuilder class (obtained through ES137en.builder ()) contains one more useful configuration method. Especially as mentioned in this article using your own ExecutorService. By default EventBus creates your own ExecutorService with ES141en.newCachedThreadPool (), which in most cases meets your needs. However, sometimes you may still want to explicitly control the number of threads used by EventBus, in which case you can initialize EventBus as follows:

Java


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

The other controls available in EventBusBuilder are those related to exception handling and a control switch that allows event classes to be inherited. These are beyond the scope of this article, but I suggest you study it carefully. GR may not have written all of this in the documentation, but if you read the source code for EventBusBuilder and EventBus, you will understand them very well.


Related articles: