This article covers the difference in threads and Asyncs, using Asyncs, stopping them, and avoiding memory leaks associated with them.
The basic idea of why Android released Asynctasks was to perform non-UI tasks on worker threads, and leaving the UI tasks for important stuff(UI rendering) only. But what was the need? Why can't we have used java Threads only?
UI thread in Android:
The Android has the main Thread(also called the UI thread), which renders the UI after every 16ms window.
So say if the UI thread is doing some extensive work, and it does not get the time to render UI within that window, the user will see the lag in the application. And furthermore if the UI is not rendered for 5 seconds, then the Application will crash with the ANR error: Application not responding.
Nobody would like this. So its the best to let UI thread be responsible only to render the UI, and move other tasks to other threads, which will do the work and reply back to UI thread once work is done. And then UI thread can show the status of that work or act accordingly.
The issue with Java Threads in Android architecture
Why can't we use simple Java threads in Android to do all background tasks? well, WE CAN.
For example: the UI thread can start another thread, which will notify the UI thread once task is complete, as:
And this is not an issue, as that 7 seconds’ work is delegated to the worker thread. The logs are:
And this is what we desire.
What are the issues associated with this?
- We can't update the UI from the Thread directly. For example:
This throws the following error:
2. The configuration changes can’t be handled from the Threads. These are tied to the Main threads, and other threads are not notified by android about these changes.
3. Canceling/stopping the normal threads is not very elegant.
Async tasks usage:
To tackle all these issues, async tasks were introduced in Android. These communicate directly with the UI threads, before/after the task is started/completed. And does the work in the background. Also, we can update the status of the task while it is in progress(e.g. showing the download percentage while the download is still going on).
So it provides clarity on how the code is run.
AsyncTask will go through the following 4 stages:
Invoked on the UI thread before the task is executed
Invoked on the background thread immediately after onPreExecute() finishes executing.
Invoked on the UI thread after a call to publishProgress(Progress…).
Invoked on the UI thread after the background computation finishes.
Any attempt to update UI inside DoInBackground will throw the similar exception:
But we can update the UI from onPreExecute and onPostExecute:
Canceling Async task:
There is a method
public final boolean cancel(boolean mayInterruptIfRunning)
in AsyncTask, but it is not how it looks like 😔.
let's see it through examples:
Example 1- task: Sleep the thread
So the doInBackground which should run for 7 seconds, will be stopped in between by UI thread after 2seconds.
So DoInBackground was interrupted(which in turn called thread inside Async to interrupt, which flowed down to Thread.sleep to interrupt).
But after this, the log after sleep() is still printed.
So can't make out from this if the doInBackground was doing its work or not after it was canceled.
Example 2: Task- increment count which usually takes 6–7 seconds to run:
This is the perfect example to understand the behavior.
So after the Async was canceled only after 2 seconds, we see the DoInBackground was still doing its work. The task at log was printed at 55. So is the cancel method not doing its work?
To understand more in detail: Go to its internal implementation at https://tinyurl.com/y4h5bua4.
Example 3- Correct way to cancel the Async:
The cancel method will not stop the doInbackground to stop its execution, but it will only set a boolean true. We ourselves need to stop its execution using that boolean. This can be done like:
So isCancelled() is the one we need to use wisely.
So doInBackground did not continue to run after 2 seconds, as per the logs. Good.
Configuration changes and Async Tasks:
Say the activity(A1) started an Async, and before it could finish the screen was rotated. Now
1. The activity(A1) should be destroyed and new Activity(A2) will be created.
2. The Async running from A1 will continue to run, even though the A1 is no more visible to the user and is going to be destroyed.
Let's see with the help of an example.
The Activity will start the Async(which takes 5 sec to complete), and before that the screen will be rotated. To differentiate between the Async logs, lets print the hashcode of various activities.
If we see the logs:
Activity- 8483517 started, which started the Async.
The screen was rotated before the task could complete.
Its Async task was still running.
51491206 the new Instance of that activity created, which launched new ASync.
Later on, we see “8483517" task completed. Meaning that it was still running, and holding the instance of the activity- which is a potential memory leak.
Solving memory leaks:
To solve this issue,
1. we can use a static inner class that does not maintain a strong reference to the instance of the outer class.
2. Use weakReferences, so that if required the garbage collection can clear down the previous instance: