Digging into Android Asynctask

Implementation of our mighty AsyncTask

This post deals with the internal implementation of AsyncTask.. I was just curious to find out what happens when we run an AsyncTask. How are the tasks handled and result is passed to the UI thread?Is it just a background thread for doInBackground()?

My Implementation

I was not aware of Future or Callable or Thread Pools before this.So the best i could think of AsyncTask is that it does the following when i call doInBackground(params).

Thread thread = new Thread() {
    @Override
    public void run() {

    }
};
thread.start();

I had no idea how the different Threads are handled in this case or how do i return the result from get() method.

AsyncTask Components

The important components that constitute an AsyncTask which will help to understand its functioning better is

Callable

This component is almost like Runnable in the sense that both are designed for classes whose instances are to be executed on other thread. But the difference between them is that it can also return a result and it can throw checked exceptions while a Runnable can’t do these. While implementing it yourself, you would have to override its call() method to execute the task and make it return a value.

In AsyncTask, we wrap our main task in a Callable and pass that to a FutureTask(explained below). The Callable that is used here is WorkerRunnable<Params,Result>mWorker. Result is passed as parameter to the Callable because it returns a value of type Result from its call() method. We will discuss later what happens in this callable.

 class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;

FutureTask

In AsyncTask, a FutureTask is created, which upon running will execute the above Callable.This FutureTask is executed on a different thread.And we will specify what should be done when FutureTask finishes its operation in done() method.

  mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {

            }
  };

Executor

This component executes the submitted tasks.An Executor can be used for running the Runnable objects without having to explicitly write new Thread(new(RunnableTask())).start() for each task and also mostly reusing the already created threads. We can use the syntax executor.execute(new RunnableTask1()) for running the tasks.

Its an interface with only one method execute(Runnable). The task submitted can execute in the same thread, in a different thread or a pool of threads, depending on the implementation of the execute method.

The Executor used in AsyncTask is SerialExecutor. Its nothing but an implementation of Executor, that executes tasks one at a time in serial order. It maintains an ArrayDeque in which tasks are enqueued and are extracted for execution.

Thread Pools

Thread Pools are used for executing the task using a ThreadPoolExecutor,which is a kind of manager for thread pools. I have written a description and implementation of Thread Pools here.

ThreadPoolExecutor

It is an ExecutorService that executes each submitted task using one of possibly several pooled threads. A description is given here.Its instance THREAD_POOL_EXECUTOR is used in AsyncTask.We will see how this is used in AsyncTask.

AsyncTask Working

Those were the key components that will help in getting how AsyncTask works. Now lets see what happens in each step

What happens when we initialize an AsyncTask?

It initializes the following things

When we execute() an AsyncTask?

The execute() method is declared in AsyncTask as

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

It returns itself and calls the Executor. Here sDefaultExecutor is a SerialExecutor. Now in executeOnExecutor(),status of the task is checked and is set to RUNNING if the task is not already running and it throws exception if you try to run the same task again before it has finished. Also it is this place that onPreExecute() is called from, which calls onPreExecute() of our instance of AsyncTask.

Now, the params that are passed with AsyncTask are set in the Callable(mWorker). And then the most important part of Asynctask happens here i.e the Executor(sDefaultExecutor) executes the FutureTask(mFuture). Note that the SerialExecutor used here is static and hence the same one will be used across all AsyncTask instances.

SerialExecutor maintains an ArrayDeque of Runnable(mTasks) . So when we say executor.execute(),it inserts the FutureTask(mFuture)at the end of the queue(mTasks). That task is extracted from the queue and it is executed by ThreadPoolExecutor(THREAD_POOL_EXECUTOR).

The ThreadPoolExecutor is used to execute tasks in parallel using its pool of threads.Its CORE_POOL_SIZE is the number of available processor cores in the phone+1 and MAX_POOL_SIZE is twice the no. of available processor cores.Here is the structure of the TPE used in AsyncTask.

public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

sThreadFactory is literally a Thread Factory which is used to generate Thread for the Thread Pools. ThreadFactory is actually an interface which you can implement to give a custom implementation of the available function Thread newThread(Runnable r). Its used to remove the hardwiring of calls to new Thread(Runnable) and we can also keep track of a thread statistics like its count using our own implementation. The following is the implementation of ThreadFactory in AsyncTask.

 private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };

sPoolWorkQueue is a BlockingQueue. A BlockingQueue supports operations that wait for the queue to become non-empty when retrieving an element, and wait for space to become available in the queue when storing an element.Storing and retrieving of elements is done according to the conditions mentioned in TPE page.

Now coming back to the process, the THREAD_POOL_EXECUTOR executes the FutureTask(mFuture), which in turn executes the task of Callable(mWorker). As mentioned earlier, doInBackground() is executed from this Callable and its result is wrapped into a class AsyncResult and is sent to the Handler(sHandler) with the key MESSAGE_POST_RESULT. The Handler decodes the result using the passed key and it sends the result to onPostExecute() from there.

For passing the Progress values to the UI thread,again it is sent by Handler(sHandler) to the UI thread using the key MESSAGE_POST_PROGRESS. The Handler then calls onProgressUpdate(Progress... values) on the main thread itself.

Handler is used here because we are passing the data from worker thread to the UI thread.

For AsyncTask we have also a function get() which returns the result calculated from doInBackground(). This internally calls mFuture.get() which gives the result computed by the FutureTask. It is a blocking call and it will wait if it is called and the result has not been calculated.

Finally, there is cancel(boolean mayInterruptIfRunning) function available in AsyncTask. Calling this will call mFuture.cancel() and a flag is set.Quoting from the docs itself, if the task has not been started yet and a call to cancel() is made,the task will never start. If the task is already running, then the parameter mayInterruptIfRunning determines whether the thread executing this task should be interrupted or not. If the task is cancelled, then onPostExecute() will never be called.