There are many articles about the background work of applications in Android, but I did not manage to find a detailed guide on how to work in the background - I think, due to the fact that there are more and more new tools for such work. Therefore, I decided to write a series of articles on the principles, tools and methods of asynchronous work in Android applications.
There will be several parts:
Let's start with the first part.
The first thing to understand is why we generally worry about working in the background in mobile applications.
In all applications for Android, there is at least one stream on which the UI is drawn and the user input is processed. Therefore, it is called a UI thread or main stream.
Each lifecycle method of each component of your application, including Activity, Service and BroadcastReceiver, is executed on the UI stream.
The human eye converts shifting 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.
The duration of a network call can be thousands of times longer.
When we want to download something from the Internet (weather forecast, traffic jams, how much your bitcoin costs at the moment), we should not do it from the main stream. Moreover, Android will not allow us, throwing a NetworkOnMainThreadException .
Seven years ago, when I developed my first Android apps, Google’s approach was limited to using AsyncTasks. Let's see how we wrote the code to communicate with the server (mostly pseudocode):
public class LoadWeatherForecastTask extends AsyncTask<String, Integer, Forecast> { 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 which one? Depends on the implementation. Here's how Android selects the stream (this is part of the source code for the AsyncTask class):
@MainThread public final AsyncTask<Params, Progress, Result> 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 the 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<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
As indicated here, by default executor refers to a pool of size 1 threads. This means that all AsyncTasks in your application are launched sequentially. This was not always true, since for OS versions from DONUT to HONEYCOMB, a pool from 2 to 4 was used (depending on the number of processor cores). After HONEYCOMB, AsyncTasks are again executed sequentially by default.
So, the work is done, the bytes have completed 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<String, Integer, Forecast> { 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:
But we did not make any network calls on the main thread! That's 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 virtually any system that you come across. The reason for this is well explained in the Java Concurrency In Practice. In short - the 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 has another method onPostExecute, which is called on the UI thread:
public void onPostExecutre(Forecast forecast) { mTemperatureView.setText(forecast.getTemp()); }
How does this magic work? Let's look in the source code of AsyncTask:
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 Handler to call onPostExecute in the UI, exactly like the postOnUiThread method in Android components.
Handler hides the entire domestic kitchen. Which one? The idea is to have an infinite loop of checking messages arriving on a UI thread and processing them accordingly. Nobody invents bicycles here, although there was some torsion of the pedals.
For Android, this is implemented by the Looper class, which is passed to 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 a static method:
public static Looper getMainLooper()
By the way, you just learned how to check if your code is 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 report 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 the UI stream for the reasons stated above.
You might think that AsyncTask is a convenient way to do background work, as it hides complexity and requires a bit of effort when used, but there are a few problems that have to be solved along the way:
In the next part, I will examine these problems in detail and show how Loaders can help solve them.
Source: https://habr.com/ru/post/348894/
All Articles