Android background tutorial. Part 1
- Transfer
There are many articles on the background work of applications in Android, but I could not find a detailed guide on how to implement work in the background - I think because more and more new tools for such work appear. Therefore, I decided to write a series of articles about the principles, tools, and methods of asynchronous operation in Android applications.

There will be several parts:
- The main thread, the implementation of background work using AsyncTask, the publication of the results in the main thread.
- Difficulty using AsyncTask. Loaders as one way to avoid them.
- Work in the background using ThreadPools and EventBus.
- RxJava 2 as a method of asynchronous operation.
- Coroutines in Kotlin as a method of asynchronous operation.
Let's start with the first part.
UI Basics
The first thing to understand is why we generally worry about working in the background in mobile applications.
In all Android applications, there is at least one thread on which the UI is drawn and user input is processed. Therefore, it is called the UI thread or the main thread.
Each life cycle method of each component of your application, including Activity, Service and BroadcastReceiver, is executed on a UI thread.
The human eye converts changing images into smooth video if the shift frequency reaches 60 frames per second (yes, this magic number is taken from here), giving the main stream only 16 ms to draw the entire screen.
A network call can be thousands of times longer.
When we want to download something from the Internet (weather forecast, traffic jams, how much is your part of bitcoin at the moment), we should not do this from the main stream. Moreover, Android will not let us throw a NetworkOnMainThreadException .
Seven years ago, when I was developing my first Android applications, Google’s approach was limited to using AsyncTasks. Let's see how we wrote the code to communicate with the server (pseudocode mainly):
public class LoadWeatherForecastTask extends AsyncTask {
public Forecast doInBackground(String... params) {
HttpClient client = new HttpClient();
HttpGet request = new HttpRequest(params[0]);
HttpResponse response = client.execute(request);
return parse(response);
}
} The doInBackground () method is guaranteed to be called not on the main thread. But on which one? Depends on the implementation. This is how Android selects the stream (this is part of the source code for the AsyncTask class):
@MainThread
public final AsyncTask executeOnExecutor(Executor exec, Params... params) {
...
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture); // <-- mFuture contains a Runnable with our doInBackgroundMethod
return this;
} Here you can see that execution depends on the Executor parameter. Let's see where it comes from:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
...
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
...
@MainThread
public final AsyncTask execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
} As indicated here, by default, the executor refers to a thread pool of size 1. This means that all AsyncTasks in your application run sequentially. This was not always true, since for OS versions from DONUT to HONEYCOMB a pool of 2 to 4 sizes was used (depending on the number of processor cores). After HONEYCOMB, AsyncTasks are run sequentially by default again.
So, the work is done, the bytes finished their long journey from the other hemisphere. You need to turn them into something understandable and place on the screen. Fortunately, our Activity is right there. Let's put the result in one of our View.
public class LoadWeatherForecastTask extends AsyncTask {
public Forecast doInBackground(String... params) {
HttpClient client = new HttpClient();
...
Forecast forecast = parse(response);
mTemperatureView.setText(forecast.getTemp());
}
} Oh shit! Again an exception!
android.view.ViewRoot $ CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
But we did not make any network calls on the main thread! Right, but we tried to break another UI law. The user interface can only be changed from the UI stream. This is true not only for Android, but also for almost any system you come across. The reason for this is well explained in Java Concurrency In Practice. In short, architects wanted to avoid complex blocking when changing from multiple sources (user input, binding, and other changes). Using a single thread solves this problem.
Yes, but the UI still needs to be updated. AsyncTask also has an onPostExecute method, which is called on the UI thread:
public void onPostExecutre(Forecast forecast) {
mTemperatureView.setText(forecast.getTemp());
}How does this magic work? Let's see in the AsyncTask source code:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
}
}
}AsyncTask uses a Handler to call onPostExecute in the UI, just like the postOnUiThread method in Android components.
Handler hides the entire interior kitchen. Which one? The idea is to have an infinite loop of checking messages arriving at the UI stream and process them accordingly. Nobody invents bicycles here, although they could not do without pedaling.

For Android, this is implemented by the Looper class, which is passed to the InternalHandler in the code above. The essence of the Looper class is in the loop method:
public static void loop() {
...
for (;;) {
Message msg = queue.next();
....
msg.target.dispatchMessage(msg);
}
...
}It simply polls the incoming message queue in an infinite loop and processes these messages. This means that there must be an initialized Looper instance on the UI thread. You can access it using the static method:
public static Looper getMainLooper()By the way, you just learned how to check if your code is being called in a UI thread:
if (Looper.myLooper() === Looper.getMainLooper()) {
// we are on the main thread
}If you try to create an instance of Handler in the doInBackground method, you will get another exception. It will indicate the need for a Looper for the stream. Now you know what that means.
It should be noted that AsyncTask can only be created in a UI thread for the above reasons.
You might think that AsyncTask is a convenient way to do background work, as it hides complexity and requires a little effort when using, but there are a few problems that have to be solved along the way:
- Every time you need to write enough code to solve a relatively simple task
- AsyncTasks do not know anything about the life cycle. If mistreated, the best you get is a memory leak, at worst, a failure
- AsyncTask does not support saving progress and reusing download results.
In the next part, I will go over these issues in detail and show how Loaders can help solve them.