Guide to background work in Android. Part 3: Executors and EventBus
Greetings, colleagues. I am glad to see you again in the third part of the “Background manual for Android”. If you have not seen the previous parts, here they are:
Last time, we figured out how Loaders work, and right after that, Google took it and said that they had completely rewritten LoaderManager. Apparently, I need to return to this topic later, but for the time being I will follow the plan and share the details of how to organize background work in Android exclusively using java thread pool executors, as well as how EventBus can help with this, and how it all works under the hood. ')
Let's remember what the main snag is: time-consuming operations like network calls should be carried out in a background thread, but the result can only be published in the main UI thread.
It would also be great to save the result of a lengthy operation when the configuration changes.
In the previous texts we figured out how to do this with AsyncTasks and Loaders. However, these APIs have their drawbacks, due to which you have to implement rather complex interfaces and abstract classes. In addition, they do not allow us to write modules with asynchronous work in pure Java due to the use of Android-specific APIs.
Because of these limitations, an approach based on executors has emerged. Let's look at it. For a start, of course, we need to get hold of streams, where we can send our tasks for background work. Let's create the Background class for this:
publicclassBackground{ privatefinal ExecutorService mService = new ScheduledThreadPoolExecutor(5); public Future<?> execute(Runnable runnable) { return mService.submit(runnable); } public <T> Future<T> submit(Callable<T> runnable){ return mService.submit(runnable); } }
So, we have an executor, and there is a method to run some code asynchronously, wrapping it in Runnable or Callable.
Great, let's try to shove the result of the operation into the UI thread. Not a problem, we know that we only need a Handler:
But wait, we don’t know if our UI exists at all, and if so, how does it know that something needs to be changed?
Here an approach called the “event bus” or event bus comes to the rescue. The general idea is that there is some kind of common bus (or even several) where events are published. Anyone can start listening to the tire at any time, receive events, and then stop listening (sounds like RxJava, huh? Wait for the next article!)
In general, we need three components:
Tire itself Source (s) of events Listener (or listeners) of events
You can reflect this structure with such a diagram:
Schematic diagram of the transmission of events on the bus
Event tire
No one requires self-implement tire from scratch. You can choose one of the existing implementations: Google Guava , Otto or EventBus from greenrobot (the latter has stylish support for sending events to different streams using annotations).
We can use the bus object directly in our presenters, activities, fragments, and so on, but I prefer to encapsulate it in the same class Background:
publicclassBackground { private final Bus mEventBus; publicvoidpostEvent(final Object event) { mEventBus.post(event); } }
Let's write client code using the construct we built. Suppose we want to initialize the database before using the application, and this, of course, takes time. So inside the application we launch initialization in the background and publish the event that the initialization of the database has completed:
The problem here is already well known to us: you cannot modify the UI from the background thread, and the code above tries to do just that.
So we need to either take advantage of the possibilities of working with the stream from the greenrobot library, or do everything ourselves. You should not reinvent the wheel in a production application, but for the purposes of training, let's try to do it with our bare hands, especially since it is very simple.
But before that, let's dig in a little bit and see how the method marked with the Subscribe annotation is called when the event is published.
Let's look at the source code of the Google Guava event bus:
publicclassEventBus { private final SubscriberRegistry subscribers = new SubscriberRegistry(this); publicvoidregister(Object object) { subscribers.register(object); } publicvoidunregister(Object object) { subscribers.unregister(object); } publicvoidpost(Object event) { Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event); if (eventSubscribers.hasNext()) { dispatcher.dispatch(event, eventSubscribers); } elseif (!(event instanceof DeadEvent)) { // the event had no subscribers and was not itself a DeadEvent post(new DeadEvent(this, event)); } } }
As you can see, the event bus stores subscribers in the SubscriberRegistry and tries to pass each event to the subscriber of this particular event (the key here is the name of the object class). The list of subscribers can be thought of as Map <EventType, Subscriber> .
Handling of threads depends on the dispatcher object, which by default is set to Dispatcher.perThreadDispatchQueue () .
The main thing here: PerThreadQueuedDispatcher uses ThreadLocal to store the event queue. In essence, this means that the subscriber method will be called on the same thread that the event was posted on.
And what should we do about it? The solution is simple - just publish events in the stream in which you want to process them:
This works, but introduces the problem that the event bus seems to be solving: reducing connectivity through the separation of publication and event handling. With this solution, we oblige the code publishing the events to know in which thread the client would like to process the code.
Another solution would be to use Handlers directly in the UI:
public classSplashActivityextendsActivity{ @Subscribe public void on(DatabaseLoadedEvent event) { runOnUiThread(newRunnable() { @Override public void run() { progressBar.setVisibility(View.GONE); showMainActivity(); } }) } }
It also does not look like a complete solution. And this is the limitation of the event bus. How can this be handled? Of course, using RxJava! But about this - in the next part.
From the author: I am a member of the program committee of the Mobius conference, and its program is 90% complete. Rather, see what the conference has prepared for you, and wait for news about the finalization of the program!