1. An overview of the

AsyncTask is a lightweight asynchronous task class, which encapsulates a thread pool and a Handler in the middle, so we can use it to perform background thread operations and UI update switch more easily.


2. Usage

AsyncTask is an abstract generic class that provides three generic parameters: Params, Progress, and Result. Params is the parameter type of asynchronous task, Progress indicates the Progress type of task execution, and Result indicates the Result type returned by background task. It has four methods.

  • OnPreExecute (), the operation before executing the task, runs on the UI thread.
  • DoInBackground (), the task to be executed, runs on a thread.
  • OnProgressUpdate (), progress update, runs on the UI thread and requires a manual call to the publishProgress method on doInBackground.
  • OnPostExecute (), called after executing the task, runs on the UI thread.

3. Source code analysis

3.1 ThreadPoolExecutor for thread pools

You can see the following set of variable definitions in the source code:

private static final// Number of mobile cpusint CPU_COUNT = Runtime.getRuntime().availableProcessors();
// The number of core threads in the thread pool
int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1.4));
// Maximum number of threads in the thread pool
int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
int KEEP_ALIVE_SECONDS = 30;
ThreadFactory sThreadFactory = new ThreadFactory() {};Copy the code

All of these parameters have one thing in common, which is to instantiate a thread pool with these parameters

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

Public static final AsyncTask maintains a static thread pool. By default, AsyncTask tasks are performed by this thread pool.

AsyncTask maintains an internal thread pool of THREAD_POOL_EXECUTOR.

3.2 Implementation of the Executor interface SerialExecutor

SerialExecutor is a static inner class that implements Executor’s AsyncTask

private static class SerialExecutor implements Executor {
    // a double-ended queue for Rnnable class
    ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    // The Runnable currently being executed
    Runnable mActive;

    // Our call to SerialExecutor's execute() encapsulates Runnable again, placing it at the end of the two-end queue mTasks. Check whether mActive is null to determine whether there is a task in execution. If there is no task in execution, remove a task from the task queue to execute it. If there is a task in execution, wait until the task is finished and fetch the next task in finally
    public synchronized void execute(final Runnable r) {
        mTasks.offer(new Runnable() {
            public void run() {
                try {
                    r.run();
                } finally{ scheduleNext(); }}});if (mActive == null) { scheduleNext(); }}// mTasks fetches a Runnable and hands it to mActive, which in turn hands it to the THREAD_POOL_EXECUTOR thread pool for execution
    protected synchronized void scheduleNext() {
        if((mActive = mTasks.poll()) ! =null) { THREAD_POOL_EXECUTOR.execute(mActive); }}}Copy the code

As you can see from the code analysis above, when runnable in mTasks is passed as an argument to THREAD_POOL_EXECUTOR to execute, the try-finally section of the anonymous inner runnable class is executed in the worker thread of the thread pool. That is, the.run() method is executed in the worker thread, and we can see that either the normal execution or the exception is thrown and the scheduleNext() method is eventually called, which continues to pass the next runnable from mTasks, so we can conclude that, SerialExecutor executes tasks one by one in serial rather than in parallel.

In addition, SerialExecutor assigns the Runnable from mTasks to THREAD_POOL_EXECUTOR, indicating that the tasks in SerialExecutor are actually handled by the THREAD_POOL_EXECUTOR thread pool.

3.3 Fields defined by AsyncTask

Fields defined in AsyncTask have these

// A static inner class that is bound to the UI thread
private static InternalHandler sHandler;
// Publish the result message code through handler
private static final int MESSAGE_POST_RESULT = 0x1;
// Publish progress message code through handler
private static final int MESSAGE_POST_PROGRESS = 0x2;

// AsyncTask uses SERIAL_EXECUTOR as its Executor by default, so AsyncTask is executed in serial rather than in parallel by default
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

// is an object that implements the Callable interface
private final WorkerRunnable<Params, Result> mWorker;
// Future is instantiated with mWorker
private final FutureTask<Result> mFuture;

// AsyncTask is not started by default
private volatile Status mStatus = Status.PENDING;
// Indicates whether the task was canceled
private final AtomicBoolean mCancelled = new AtomicBoolean();
// Indicates whether the task has actually started
private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
Copy the code
3.3.1 Enumeration types representing states

An AsyncTask internally defines an enumerated type to represent the state of execution of the task.

// Indicates that a task has not been executed, a task is being executed, or a task is completed
public enum Status {
    PENDING, RUNNING, FINISHED,
}Copy the code

The state of an AsyncTask must change as PENDING->RUNNING->FINISHED.

3.3.2 Default thread pool

The initial value of sDefaultExecutor is SERIAL_EXECUTOR, and we know that AsyncTask is executed serally by default.

3.3.3 InternalHandler

As we mentioned before, AsyncTask encapsulates a thread pool and a Handler. This sHandler is the static internal class of AsyncTask, InternalHandler

private static class InternalHandler extends Handler {
    public InternalHandler() {
        super(Looper.getMainLooper());
    }

    @SuppressWarnings({"unchecked"."RawUseOfParameterizedType"})
    @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;
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break; }}}Copy the code

We know from the InternalHandler constructor that sHandler is associated with Looper on the UI thread so that means sHandler is bound to the UI thread.

3.3.4 mWorker and mFuture

MWorker is a WorkerRunnable class. WorkerRunnable is a lazy class that implements the Callable interface. Callable is similar to Runnable. The call method can have a return value, but the run method cannot.

MFuture is the object of FutureTask. Since Executor’s execute method needs to receive a Runnable object, we need to return a result after executing the task. It implements both the Callable and Runnable interfaces, and its constructor requires passing in a Callable object, so we can also pass the FutureTask mFuture to Executor’s Execute method.

When a task is executed, the Done method of FutureTask is called. During the task execution, you can also call the Cancel method of FutureTask to cancel the task. After the task is canceled, the Done method is still called.

3.4 Constructor of AsyncTask

The AsyncTask constructor is essentially an instantiation of mWorker and mFuture.

Instantiation of mWorker

mWorker = new WorkerRunnable<Params, Result>() {
    public Result call() throws Exception {
        // Set the task start identifier to true
        mTaskInvoked.set(true);
        Result result = null;
        // Set the call method to the background thread level
        try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            // Execute the doInBackground() method in the thread pool and return result
            result = doInBackground(mParams);
            Binder.flushPendingCommands();
        } catch (Throwable tr) {
            mCancelled.set(true);
            throw tr;
        } finally {
            // Pass the result to the postResult() method
            postResult(result);
        }
        returnresult; }};Copy the code

Instantiation of mFuture

mFuture = new FutureTask<Result>(mWorker) {
    @Override
    protected void done() {
        try {
            postResultIfNotInvoked(get());
        } catch (InterruptedException e) {
            android.util.Log.w(LOG_TAG, e);
        } catch (ExecutionException e) {
            throw new RuntimeException("An error occurred while executing doInBackground); } catch (CancellationException e) { postResultIfNotInvoked(null); }}};Copy the code

MWorker is actually an object of type Callable. Instantiate mWorker to realize the Call method of Callable interface. The Call method is executed in a thread in the thread pool, not in the main thread. The doInBackground method is executed in the worker thread of the thread pool to perform the actual task and return the result. When doInBackground completes execution, the result is passed to the postResult method. The postResult method we’ll talk about later.

MFuture is an object of type FutureTask that instantiates the mFuture with mWorker as a parameter. In this case, it implements the Done method of FutureTask, which we mentioned before is executed when the FutureTask task completes or cancels. The logic in the done method we’ll talk about later.

3.5 AsyncTask. The execute ()

So how does Execute work

3.5.1 track of executeOnExecutor ()

After instantiating AsyncTask, you need to call the AsyncTask execute method to execute the task

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

You can see that execute actually calls the executeOnExecutor method.

@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params) {
    if(mStatus ! = Status.PENDING) {switch (mStatus) {
            case RUNNING:
                throw new IllegalStateException("");
            case FINISHED:
                throw new IllegalStateException(""); }}// Change the state before starting the task
    mStatus = Status.RUNNING;

    // Call this method before the task is actually executed
    onPreExecute();

    // 
    mWorker.mParams = params;
    exec.execute(mFuture);

    return this;
}Copy the code

MStatus If the status is not PENDING, an exception is raised, indicating that the AsyncTask can execute the task only once.

Since @mainThread we know that the executeOnExecutor() method is running on the UI thread, so onPreExecute() is also running on the UI thread.

Exec. Execute (mFuture) is called to execute the task. Since exec is SERIAL_EXECUTOR by default, the mFuture containing the task is placed in a static queue, and the SERIAL_EXECUTOR queue is used to execute the task.

Once the task is executed, the call method in mWorker is called, and the result of the execution is passed to the postResult() method after the doInBackground() method is executed.

3.5.2 postResult ()

The code for the postResult() method is as follows

private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}Copy the code

It sends a message with a Result to the UI thread through the sHandler, and AsyncTaskResult appears, what is that?

3.5.3 AsyncTaskResult
private static classAsyncTaskResult<Data> { final AsyncTask mTask; final Data[] mData; AsyncTaskResult(AsyncTask task, Data... data) { mTask = task; mData = data; }}Copy the code

MTask indicates which AsyncTask it is the result of, and mData indicates the stored data

3.5.4 sHandler handleMessage ()
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;
        case MESSAGE_POST_PROGRESS:
            result.mTask.onProgressUpdate(result.mData);
            break; }}Copy the code

Executing the result.mtask.finish () method executes the finish method of the current AsyncTask

2.6.2 finish ()
private void finish(Result result) {
        if (isCancelled()) {
            // If the task is cancelled, the onCancelled method is executed
            onCancelled(result);
        } else {
            // Pass the result to the onPostExecute method
            onPostExecute(result);
        }
        // Finally, set AsyncTask to completed state
        mStatus = Status.FINISHED;
    }Copy the code

The Finish method is straightforward, executing onCancelled() if the task is cancelled or onPostExecute() if not, since sHandler is associated with the UI thread, so both methods run in the UI thread.

3.5.6 done ()

After the mWorker’s call() is invoked, the mFuture’s done() method is invoked. We look at the source code and know that the postResultIfNotInvoked() method is invoked regardless of whether the task is invoked normally or cancelled.

private void postResultIfNotInvoked(Result result) {
    final boolean wasTaskInvoked = mTaskInvoked.get();
    if(! wasTaskInvoked) {// The postResult method is executed only if mWorker's call is not calledpostResult(result); }}Copy the code

The mTaskInvoked in the mWorker’s call method is true, so the postResult() method is invoked if the mWorker’s call method is not invoked.

If the Call methods are all executed while the AsyncTask is normal, the mTaskInvoked is set to True and the last call method invoked is the postResult method and then goes to the DONE method of the mFuture, The postResultIfNotInvoked method is then invoked, but the postResult method is not invoked because the mTaskInvoked is already invoked.

If the AsyncTask cancel method is executed immediately after the AsyncTask execute method (which actually executes the mFuture cancel method), then the done method is executed and a CancellationException is caught. The statement postResultIfNotInvoked(NULL) is invoked, but the mTaskInvoked is not false because the mWorker’s call method is not invoked yet, so the null is passed to the postResult method.

This should make the AsyncTask details clear.


4. Deepen understanding

4.1 example

We know that execute() is called serially and execute() is called concurrently.

When execute is called, sDefaultExecutor is set to SERIAL_EXECUTOR by default. SERIAL_EXECUTOR is a serial execution process.

To call executeOnExecutor(sDefaultExecutor, xx), we set the sDefaultExecutor variable to the thread pool we passed in, or we can use the thread pool ThreadPoolExecutor maintained internally by AsyncTask. The execute() method of ThreadPoolExecutor, in which the task is passed, executes in parallel.

To verify this conclusion, we wrote pseudo code for these tests

class MyAsyncTask01 extends AsyncTask<String, Object, String>{

        @Override
        protected void onPreExecute() {
            Log.i(Tag."01-onPreExecute");
            super.onPreExecute();
        }

        @Override
        protected String doInBackground(String. params) {
            for ( String string : params) {Log.i(Tag."01 - doInBackground." + string);
                Thread.sleep(5000);
            }
            return "Complete";
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            Log.i(Tag."01 - onPostExecute." + s);
        }

}

class MyAsyncTask02 extends AsyncTask<String, Object, String>{

    @Override
    protected void onPreExecute() {
        Log.i(Tag."02-onPreExecute");
        super.onPreExecute();
    }

    @Override
    protected String doInBackground(String. params) {
        for ( String string : params) {Log.i(Tag.02 - doInBackground: "" + string);
        }
        return "Complete";
    }

    @Override
    protected void onPostExecute(String s) {
        super.onPostExecute(s);
        Log.i(Tag.02 - onPostExecute: "" + s);
    }

}

MyAsyncTask01 myTask01 = new MyAsyncTask01();
MyAsyncTask02 myTask02 = new MyAsyncTask02();
Copy the code

Run:

/ / the first1Group myTask01.execute("a");        
myTask02.execute("b");/ / the first2Group myTask01.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "a");
myTask02.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "b");Copy the code

Group 1

Because it is executed sequentially, task 2 is not executed until task 1 completes.

The second group

It is executed in parallel, so task 1 and task 2 are executed simultaneously.


4.2 doInbackground() is not executed

We know that parallel execution calls the executeOnExecutor() method, but my doInbackgroud method didn’t execute or waited too long to execute for this reason. AsyncTask maintains a static thread pool, ThreadPoolExecutor, and because it is static, it manages not only the threads in which we define AsyncTask tasks, but also the threads of other tasks. If the app has a lot of downloading tasks running in the background, AsyncTask tasks are downloaded concurrently using AsyncTask. There are no free threads in the ThreadPoolExecutor thread pool. So your doInbackground() method has to wait for a thread to be idle before executing, causing the above situation. We can simulate the situation.

for (int i=0; i<100; i++){
    MyAsyncTask01 t1 = new MyAsyncTask01();
    t1.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "a");
}
MyAsyncTask02 t2 = new MyAsyncTask02();
t2.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "b");Copy the code

As can be seen from the figure, a full 25 seconds elapsed before t2 was executed. At this point we should replace the thread pool with our own custom thread pool.