Hi, all!
Although there a lot of materials covering the background work in Android applications, the complete guide is always missing due to the pace the instruments for the Android applications are developed at. The goal of this series is to provide a detailed guide on principles, tools and approaches for doing the work asynchronously.
Disclaimer: the approach described in this part is an obsolete one; please do not use AsyncTasks in modern applications, there are tools much-much better and we will consider them in the upcoming articles.
The plan is as follows:
- Main thread, doing the work off the main with AsyncTask, posting the results back to the main one(this article)
- The Async Tasks approach issues; Loaders as one of the ways to avoid them(please find here)
- The custom background approach with ThreadPools and EventBus
- The RxJava 2 approach to async work
- The coroutines approach to async work
Let’s start with the first part immediately.
UI Basics
The first thing we need to understand is why we even bother with threading on the mobile device.
In every Android application there is always at least one thread, and the UI rendering and user input handling occurs on it. That’s why it is called the main thread or UI thread.
Every lifecycle method of every component in your application being activity, broadcastreceiver, service, etc. is called on the UI thread.
The human eye perceives changing images as a smooth video if it has 60 frames per second rate (yeah, this magic number comes from here) giving the main thread only 16 ms for redrawing the whole screen.
Length of a network call could be thousands times longer.
The moment we want to load anything from the internet (weather forecast, traffic jams, how much your part of bitcoin is worth now) we should not do that on the main thread. Moreover, Android won’t let us and throws a NetworkOnMainThreadException.
Back when I was developing my first Android applications seven years ago the approach from Google was limited to AsyncTasks. Let’s take a look at the code and check how the servers were polled back then (pretty much pseudo-code here):
Here the doInBackground() method is guaranteed to be called off the main thread. But on which one? It depends on the implementation. That’s how Android picks a thread(this is a part of AsyncTask class source code):
Here you can see that the execution depends on the Executor parameter, let’s see where it comes from:
As stated here, the default executor is a single-sized thread pool executor, which means all the AsyncTasks in your application are launched consequently. That wasn’t always true for OS versions from DONUT to HONEYCOMB; the threadpool of size from 2 to 4 was used(depending on the processor cores count). After HONEYCOMB, the serial executor is set by default again.
Ok, job done, the bytes finished their long journey from another half of the planet. We need to convert them into something understandable and display them on the screen. Happy we are, our activity is right there, let’s place the result into one of its views:
Oh, damn! Exception again!
But we didn’t make any network calls on the main thread! That’s right. But we tried to break another law of UI: the UI can be only modified from its own thread. That’s true not only for Android but virtually for any system you would code for: either UWP or Desktop Java. The reason for this is well explained in Java Concurrency in Practice and making long story short the architects wanted to avoid complex locking from several sources (user input, data binding and other updates). Limiting the updates to a single thread solves the problem.
So, now you have to update the UI. The AsyncTask gives you another method for that onPostExecute which is called on the UI thread again:
How does this magic work? Let’s see it in AsyncTask source code:
AsyncTask uses Handler to invoke your onPostExecute on UI, so do all the postOnUiThread methods in Android components.
The Handler class hides some work under the hood from you. Which work? The idea is to have an infinite loop checking the messages for UI and provide appropriate reactions. Again Android does not reinvent the wheel here, although it gets a bit of spinning.
For Android it is implemented with Looper class which is passed to InternalHandler in the code above. The core of Looper class is in the loop method:
It simply polls the queue of incoming messages and dispatches them in an infinite loop.
That means UI thread should have a looper instance initialized. You can access it with static method:
By the way, you’ve just got a method to check if your own code is being invoked on the UI thread:
If you try to instantiate a Handler in doInBackground method you will get another exception telling you it is necessary to have a Looper for the thread; you now know what that means.
Needless to say, AsyncTask can only be created within the UI thread for the reasons above.
You can think that AsyncTasks are a convenient way to perform background work as long as they hide the complexity and require a little to be used; but there are several issues they bring with them:
- Each time you need to write a lot of boilerplate to solve a trivial task.
- AsyncTasks don’t know anything about the Activity lifecycle and therefore may introduce memory leaks at best and a crash at worst if used incorrectly.
- They don’t support progress or result reusage.
Those issues we are addressing with Loaders in the next chapter: as the Android OS development team did several years ago.