Speaking of AsyncTask, it’s almost the easiest way to execute operations asynchronously and present them to the UI thread. You don’t have to write a thread and then use a Handler to return the results to the UI thread. Simply rewrite onPreExecute doInBackground, onProgressUpdate, onPostExecute four methods, and then call the execute method, is a super easy. However, do you understand how AsyncTask operates your tasks? How does it encapsulate the Handler to return the results of asynchronous task execution to the UI thread? What should I pay attention to when using AsyncTask? This article analyzes the working principle of AsyncTask from source code, and part of the content comes from source code.

Task execution mode

Asynctasks are executed sequentially in a single thread by default. There was a time when asyncTasks could be executed in a thread pool.




The execute method executes tasks in a queue in a background single thread or thread pool. The first version of AsyncTask is sequential execution. In version 1.6 (DONUT), it was changed to multi-tasking thread pools. However, after 3.2 (HONEYCOMB), execution was switched back to a single thread in order to avoid some thread synchronization errors. If you want to execute in a thread pool, you can do this:

new AsynTask().executeOn(AsyncTask.THREAD_POOL_EXECUTOR,"")Copy the code

Obviously, this approach is not recommended.

Now that we’re talking about asynctask.thread_pool_executor, what is it?

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

THREAD_POOL_EXECUTOR is an executor of a thread pool (see this article for details on thread pools). All you need to know here is that it’s a core and the number of threads is number of cpus +1, and the maximum number of threads is 2 times number of cpus +1.

In other words, how does single-threaded sequential execution work? Please look at:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;Copy the code

This sDefaultExecutor is the AsyncTask task executor. If you look at the source code, you will find a method like this:

/** @hide */
public static void setDefaultExecutor(Executor exec) {        
    sDefaultExecutor = exec;
}Copy the code

SDefaultExecutor can be set, but you can’t call it and it is hidden (@hide).

So what is SERIAL_EXECUTOR? It is an instance of SerialExecutor.




As you can see, the offer method is called in execute and it wraps the Runnable R into an ArrayDeque queue. The wrapped new Runnable ensures that the original Runnable will then fetch the next Runnable in the queue without causing an interruption. What does scheduleNext do? As you can see, the scheduleNext picks up the Runnable from the queue and gives it to THREAD_POOL_EXECUTOR for execution. That is, SerialExecutor simply sorts tasks into a queue in order; THREAD_POOL_EXECUTOR actually executes the tasks.

Task Execution Process

This is what happens when you call execute:

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

You see, executeOnExecutor is still called, and SERIAL_EXECUTOR is passed by default. And, you see at sign MainThread, execute must be called on the MainThread.

Please see executeOnExecutor:




Each AsyncTask has a Status that represents the Status of the AsyncTask. Status is an enumeration variable. Each Status is assigned once during the life cycle of the Task. The Task must go through PENDING -> RUNNING -> FINISHED. PENDING indicates that the Task has not been executed, RUNNING indicates that the Task is being executed, and FINISHED indicates that the onPostExecute method has FINISHED, not doInBackground.

/** 
  * Indicates the current status of the task. Each status will be set only once 
  * during the lifetime of a task. 
  */
 public enum Status {       
    PENDING,    
    RUNNING,   
    FINISHED,
}Copy the code

Back to executeOnExecutor, if the current Task’s state is not PENDING, then an exception is thrown. You can execute a Task only once. You can invoke the execute method again only after the asynchronous Task is executed. Otherwise, an error will be reported. The onPreExecute method is then called, which is then submitted to SERIAL_EXECUTOR for execution. But what is this mWorker? What is mFuture?

MWorker mWorker is a concrete implementation of WorkerRunnable, which implements the Callable interface. It is equivalent to a Runnable that can save parameters and return results.

private static abstract class WorkerRunnable implements Callable {    
  Params[] mParams;
}Copy the code

MWorker and mFutureTask are created when you create an AsyncTask.




As you can see, the main job of mWorker’s call method is to set whether or not the call will be called, call your overridden doInBackground method, and get Result(which is the type passed in when you declare AsyncTask). Result is returned by calling the postResult method. See below for postResult.

MFuture can see that there is a postResultIfNotInvoked(Get ()) in the mFuture; Method, using the get method to get the execution result of the mWorker, and then calling the postResultIfNotInvoked method. For some reason, the mWorker’s call may not be invoked, So the postResultIfNotInvoked ensures that the postResult is executed once, either in the mWorker’s call or the postResultIfNotInvoked.

private void postResultIfNotInvoked(Result result) {    
      final boolean wasTaskInvoked = mTaskInvoked.get();    
      if (!wasTaskInvoked) {        
          postResult(result);    
      }
}Copy the code

So what does this postResult do?

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

You can see that postResult actually takes a Handler inside an AsyncTask, wraps the result in AsyncTaskResult, and sends it to the Handler in a message.

So how is AsyncTaskResult encapsulated?

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

You can see instances (mTask) and data (mData) containing AsyncTask. When the Result of task execution is returned, mData stores Result, and when Progress is updated, mData stores data of the same type as Progress. You can look down.

Let’s start with Handler.




Each AsyncTask gets an instance of InternalHandler. As you can see, InternalHandler is bound to the main thread’s Looper (see this article for more information about Looper and Handler), so the results that you execute in an asynchronous thread can eventually be passed to the main thread through InternalHandler. Now look at the handlerMessage method and get the AsyncTaskResult object. If MESSAGE_POST_RESULT is passed, Call the AsyncTask finish method (remember that result.mTask is the current AsyncTask).

What does Finish do?

private void finish(Result result) {    
      if (isCancelled()) {        
          onCancelled(result);    
      } else {        
          onPostExecute(result);    
       }    
      mStatus = Status.FINISHED;
}Copy the code

As you can see, if you cancel the Task, the onCancelled callback will take precedence, otherwise the onPostExecute callback will be executed and the state of the Task will change.

If it is a MESSAGE_POST_PROGRESS, the onProgressUpdate method is executed. Who sent the MESSAGE_POST_PROGRESS message? Please look at:

protected final void publishProgress(Progress... values) {    
    if (!isCancelled()) {        
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,                
               new AsyncTaskResult(this, values)).sendToTarget();    
    }
}Copy the code

That is, when you call publishProgress, the values passed are wrapped as AsyncTaskResult, and the mData of AsyncTaskResult holds the progress data and sends the message to handler.

One method that needs to be explained is the Cancle method.

public final boolean cancel(boolean mayInterruptIfRunning) {    
      mCancelled.set(true);    
      return mFuture.cancel(mayInterruptIfRunning);
}Copy the code

The effect is to set the cancelled state and then cancel the execution of FutureTask. False is returned when the task has finished executing, has been canceled, or cannot be canceled for some reason. If the Task has already been executed, mayInterruptIfRunning determines whether to interrupt the thread currently executing the Task. Calling this method will call onCancelled when doInBackground returns, and onPostExecute will not execute, so remember to check isCancelled at doInBackground if you need to cancel the Task.


Matters needing attention

1. Since AsyncTask is executed in single-thread order, do not use AsyncTask to perform operations that take a long time. If there are many threads that take a long time, it is better to use thread pool. 2. OnPreExecute, onProgressUpdate, and onPostExecute are all called in the UI thread, while doInBackground is executed in the background thread. 3. Call the cancel method to cancel the execution of the task. At this time, onPostExecute will not be executed, instead, the cancel method will be used. 4. Other

  • AsyncTask must be loaded on the main thread
  • An instance of AsyncTask must be created on the main thread
  • executeMethod must be called on the main thread
  • Don’t actively invokeonPreExecuteMethods such as
  • A task can only be executed once before completion.