The AsyncTask Android class lets us sort of bind background tasks to the UI thread. So using this class, you can perform background operations and then publish the results to the UI thread that updates the UI components. This way you won’t have to deal with threads, handlers, runnables, etc. directly yourself. It’s sort of a helper class around Thread
and Handler
.
So when should you use an AsyncTask ? Well any long running operation that may block the main thread and make the app unresponsive could be done via AsyncTask. Like downloading multiple files, or making HTTP requests to your server, decoding images, etc.
According to the documentation, AsyncTask should ideally be used for short background operations that lasts for a few seconds at most. For longer tasks, the Executor framework from the java.util.concurrent
package should be used that contains classes/interfaces like Executor
, ThreadPoolExecutor
and FutureTask
.
Creation and Implementation (Usage)
In order to use AsyncTask, you’ll have to subclass it. This is the simplest form of implementation of an AsyncTask:
1
2
3
4
5
6
7
8
9
10
|
// AsyncTask class MyAsyncTask extends AsyncTask { @Override protected Object doInBackground(Object... params) { // Do some background work Log.d(TAG, "MyAsyncTask@doInBackground from another thread" ); return new Object(); } } |
To execute the task you’ll have to instantiate it and call execute()
:
1
|
new MyAsyncTask().execute(); |
The execute()
method can be called only once per AsyncTask instance, that means AsyncTask can be executed only once – just like a Thread
. The example that we just saw is the most basic form of an AsyncTask implementation. Let’s now see all the important methods that you can override in a full-blown implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
class MyAsyncTask extends AsyncTask<Params, Progress, Result> { @Override protected void onPreExecute() { // Runs on the UI thread before doInBackground() } @Override protected Result doInBackground(Params... params) { // Perform an operation on a background thread } @Override protected void onProgressUpdate(Progress... values) { // Runs on UI thread after publishProgress(Progress...) is invoked // from doInBackground() } @Override protected void onPostExecute(Result result) { // Runs on the UI thread after doInBackground() } @Override protected void onCancelled(Result result) { // Runs on UI thread after cancel() is invoked // and doInBackground() has finished/returned } } |
As you can see (read in the comments actually) all callbacks except the doInBackground()
method are executed on the UI thread. Also all of them are executed in a sequence except onProgressUpdate()
which is initiated by and runs with doInBackground()
concurrently. Using this onProgressUpdate()
callback, the user can be notified in the user interface of how much work is done or you can deliver results in chunks rather than waiting for onPostExecute()
to be called and sending everything at once.
It is very important to understand the three AsyncTask generic types:
- Params – Type of data passed to the task upon execution, i.e., arguments passed to
execute()
and accepted bydoInBackground()
. - Progress – Type of progress data reported by the background thread, i.e., from
doInbackground()
to the UI thread inonProgressUpdate()
. - Result – Type of result returned/produced by the background thread (
doInBackground()
) and sent to the UI thread (onPostExecute()
).
The 3 dots argument is referred to as varargs(arbitrary number of arguments).
States
An AsyncTask can be in one of the following states:
- PENDING – AsyncTask has been instantiated but
execute()
hansn’t been called on the instance. - RUNNING –
execute()
has been called. - FINISHED – The execution has been done as well as
onPostExecute()
oronCancelled()
has been called.
The states change in the order shown above. The instance cannot switch to a backward state. Once finished a new instance has to be created for execution (cannot go back to RUNNING
state). The current state can be monitored using getStatus()
.
Cancellation
If you want to cancel an AsyncTask operation, then the cancel()
method can be invoked.
1
2
3
4
5
|
// Execute the task AsyncTask task = new MyAsyncTask().execute(); // Cancel the task task.cancel( true ); |
Calling cancel()
sets a flag due to whichisCancelled()
will return true
. So the background thread (doInBackground()
method) should check for it whereever possible and just return from itself incase of true
so that no time and resources are wasted doing further operations. If the argument passed tocancel()
is true
(not false
) then the executing background thread will be interrupted. Sending an interrupt to the thread is a strict approach where any blocking methods (Thread.sleep()
for instance) will be relieved and the background thread can check forThread.isInterrupted()
or catch InterruptedException
thrown. Interruption is just a cancellation strategy as threads cannot be forced to terminate. The idea is to terminate as early as possible to release allocated resources, avoid further resource consumption, quicker return and reduce risk of memory leaks. So basically in your doInBackground()
check forisCancelled()
(between long running operations) and if possible wrap everything in a try/catch where you check for InterruptedException
.
Calling cancel()
ensures that onPostExecute()
is never invoked. But it does invoke the onCancelled()
callback on the UI thread after doInBackground()
returns. Due to this, a cancelled task might take just as much time as a task without cancellation.
execute(Runnable)
The AsyncTask class has another version of itsexecute()
method which is static and accepts a Runnable. It is just a convenience version of the otherexecute()
(that we’ve seen before) for use with simple Runnable objects.
Sequential Execution
AsyncTask can be subclassed, instantiated and executed from any component in the application. That means multiple AsyncTasks can be in RUNNING
state inside the application. However, if two different components (Activity and Service) launch (instantiate and execute) two different tasks that too from two different threads, both of them will be executed sequentially on a single application-wide worker thread.
1
2
3
4
5
|
// From Component A LongTaskOne().execute(); // From Component B at the same time LongTaskTwo().execute(); |
LongTaskOne
will keep the LongTaskTwo
from executing until its own execution is finished. This means background tasks could get delayed if there are just too many of them.
Concurrent Parellel Execution
To overcome the sequential nature, AsyncTask provides us with the executeOnExecutor(Executor exec, Params... params)
method using which we can run multiple tasks in parellel on a pool of threads managed by AsyncTask. We can also use our own Executor (with a pool of threads) for custom behaviour. Don’t forget to read the warning section in the documentation of this method which basically says if you’re using it, do the needful to ensure thread safety.
The AsyncTask class has two static Executors that you can pass to executeOnExecutor()
:
AsyncTask.THREAD_POOL_EXECUTOR
– An Executor that can be used to execute tasks in parellel on its pool of threads.AsyncTask.SERIAL_EXECUTOR
– An Executor that executes one task at a time serially (sequentially), hence thread-safe. This serialization is global app-wide (process-wise). In this case tasks are stored in an unbounded queue that are passed toTHREAD_POOL_EXECUTOR
to be executed. The task can then be executed in any thread in the executor’s pool butSERIAL_EXECUTOR
makes sure only one task is passed and executed at a time, not multiple.
These two executors are global to the entire application, i.e., the pools are shared by all the AsyncTask operations across the application process. Thus, when there are too many background tasks to execute, delays could be noticed affecting the app’s performance. If this is the case then you can make use of a custom Executor which’ll have its own set of threads.
So for instance you could create a new custom Executor with a single thread operating off an unbounded queue, by callingExecutor.newSingleThreadExecutor()
in your Application class. Then you can reuse this instance from different Activity, Service, etc. components by passing to new LongTaskOne().execute(customExec, ...)
and new LongTaskTwo.execute(customExec, ...)
and so on.
Conclusion
So we looked into how AsyncTask makes it so simple to execute an operation in the background and port results to the UI thread that can just update the app’s user interface, fulfilling the user’s expectations.
However, you’ve to be smart by choosing the best solution for your problem, not just AsyncTask always. For instance if you want to perform a background task that won’t affect the UI, then you should probably just use Thread
or even HandlerThread
that facilitates message passing. There are times when you want a MessageQueue attached with your thread, again HandlerThread
is a good candidate for that. Also there will be cases where IntentService
will make the most sense over any other option. So be careful with your choices!