Android AsyncTask internal implementation

Anmol Sehgal
5 min readApr 14, 2019

--

Would also suggest going through the Handlers/Loopers working, and the basics of AsyncTasks at https://tinyurl.com/y4mbxa2w. The AsyncTasks are heavily using the ThreadPoolExecutors to do its tasks on the worker thread, and once done it passes back the result to the UI thread using Handlers.

Handling Tasks in Background and communication with UI thread:

Basic functions in Async are:
onPreExecute: Before the task begins
doInBackground: To do the task in background
onProgressUpdate: To update the progress of the task run
onPostExecute: After the task has run

But how do all these work together? How UI and worker threads communicate?

Let us start from the beginning:
Async created:

Handlers are the Android Components used to communicate between the UI thread and the background threads.
The background thread(who has UI handler reference) can post messages to it, and the messages will be received in handleMessage() on the UI thread.

Line 2–4: When Async is created, it should know who will receive the callbacks of task begin/complete, etc. So in case, no callback is provided, it sends the callback to the InternalHandler, which will be discussed later.

Line 6–23: Creates the Worker runnable(which implements Callable)

Which will be called by FutureTasks.
Its call() implementation will call the doInBackground(Line: 13).
The result is then sent to 2 places:
1. postReply() — line:19
2. Back to the FutureTask which executed this callable -Line:21

Line 25–39: creates the future Tasks, which when executed will, in turn, execute the Worker thread created.
once the Worker thread has done its work, postResultIfNotInvoked is run.

So Handler + WorkerRunnable + FutureTask is created in the Async constructor.
Handler: responsible to send messages to Main Thread.
WorkerRunnable: which contains the tasks which are run using doInBackground().
FutureTask:
Executes the work and sends back the result using postResultIfNotInvoked.

Let's hold for now how the postReply and postResultIfNotInvoked do their work.

Execute():

Details on the executors below.

So when the Async is run,
Line 7–19: Checks that no other task is running, else throw the exception. So one Async can only run one task, e.g.:

Will throw an error:

When a task is about to run, it sets its status as RUNNING, and again if same Async is run, if the status is other than PENDING, it throws an Exception.

Line 21: Calls the onPreExecute() method. Please note that we are still at the UI thread, so this method is called on UI thread before any background task begins.

Line 23: Sets the parameters to the already created WorkerRuynnable(in the constructor).

Line: 24: executes the FutureTask, which in turn executes this Callable, as explained before. This will in turn call doInBackground and then send back the result using postReply and postResultIfNotInvoked.

Sending Back result to UI:
Here are where the Handlers are used.
After the background task is complete:
the postReply() is called which sends back the result.

Note that postReply and postResultIfNotInvoked sends back the result to UI with a minor difference.
postResultIfNotInvoked will send back the response for tasks which were not invoked from the WorkerRunnable
Whereas postReply will send back the result executed from the Worker thread.

Line 3: it gets the Handler- InternalHandler inside the Async itself(line 12).
So when the message is sent to this handler, it will be received inside handleMessage of InternalHandler.

Please note that the InternalThread has MainLooper provided(Line 12), so the handleMessage will run on UI thread.

It creates the Message with
what: MESSAGE_POST_RESULT
Obj: AsyncTaskResult encapsulating the Async itself and the reply(Line: 19–24)

Line 5: sends the message to the handler( default: InternalHandler).

The InternalHandler receives the message, and it calls the finish() method of the Async(reference from mTask) and passing in the result(from mData).

Its the finish method which runs on UI thread(as InternalHandler is associated with the MainLooper). The finish method does 2 things:

If the Async is canceled: onCancelled will be called.
Or onPostExecute will be called.

So onPostExecute will not be called when the Async is cancelled, but onCalcelled will be called instead.

Publish progress:

Line 4–5: Similar to sending the result back after the task is complete when the publishProgress is called(from background thread), it sends the message to the handler similarly(passing in the progress values in the Message).
The progress will be updated only if the Async is not canceled.

Line 15: As InternalHandler is associated with the MainThread(using MainLooper), it calls onProgressUpdate() which will run on the Main Thread.

Executors: Order of tasks to run in the background

The biggest reason to use Async is to delegate the work in the background. It does it using executors:

Executors are the Java APIs, which contains the queue to which the new tasks are enqueued, and have a fixed number of threads to run. The threads turn by turn dequeue the tasks from the queue and run them. So there can be as many numbers of parallel tasks running as many threads are there in the executor.

The default implementation of async has SerialExecutor which executes the tasks serially,

So when we execute the Async, this is the default executor run which runs the tasks in the background.

Line 6: Creates and adds a new Runnable(encapsulating the new task)
to the queue, to be run in serial.
This new Runnable does 2 things when run() is called:
1. It will run the task it encapsulates- Line 7
2. Once the task is run, it executes finally(Line 11) and executes the next task queued on the Queue(Line 22).
So this was the tasks are run in a serial manner.

Running in parallel:
Apart from SerialExecutor Async also has THREAD_POOL_EXECUTOR, but that needs to be provided inside the executeOnExecutor() method, So that Async can then use this executor instead of the SerialExecutor.

The THREAD_POOL_EXECUTOR is device dependent on how many threads/pools can it support(modified after API-19), earlier the values were hardcoded to 5, I guess.

As evident, depending on the Cores the device has, that many threads can run in parallel.
The thread factory is responsible for creating new threads(Each new thread has its number incremented by 1 in its name).

Execute method:

Which internally calls the executeOnExecutor() method with defaultExecutor i.e. SerialExecutor.

Example inputs and outputs:
Consider the Async which prints the Input param infintely:

1. Serial:

As we have submitted 3 tasks using execute(), they will run serially:

As each task is infinitely times run, other tasks will never get the chance to run.

2. Parallel

Now depending upon the Core size,(2 in my case), that many threads will run in parallel.

3. parallel functionality override:
We will allow all three to run, independent of our device configuration:

And can see all of them running as:

Conclusion:
The Async task by default runs the tasks in serial fashion, one after the other. Once the task is not complete, the other tasks are waiting in the Queue to be picked up.
We can override this serial runner by passing in our Executor, which can run multiple tasks in Parallel.

--

--