Detailed explanation of Android for thread processing AsyncTask class usage and source code

  • 2021-07-10 20:50:34
  • OfStack

Why use AsyncTask
We all write App with one principle. The main thread cannot run tasks that require a large number of CPU time slices, For example, a large number of complex floating-point operations, large disk IO operations, network socket, etc., all of which will lead to the slow response of our main thread to users, even ANR, which will make the user experience of applications worse, but sometimes it is necessary to perform these time-consuming tasks, so we can usually use AsyncTask or new Thread
To process, so that the task is put into the worker thread for execution, Does not occupy the time slice of the main thread, Therefore, the main thread will respond to the user's operation in time, If new Thread is used to execute tasks, if we need to cancel task execution in the middle or return task execution results, we need to maintain a lot of extra code ourselves, while AsyncTask is implemented based on concurrent classes provided by concurrent package, and the above two requirements have been encapsulated for us, which is why we chose AsyncTask.

How to use AsyncTask
Let's briefly introduce some examples of AsyncTask1. We first create a new class DemoAsyncTask that inherits AsyncTask, because AsyncTask is an abstract class where the doInBackground method must be overridden.


private class DemoAsyncTask extends AsyncTask<String, Void, Void> {
 @Override
 protected void onPreExecute() {
  super.onPreExecute();
 }

 @Override
 protected Void doInBackground(String... params) {
  return null;
 } 

 @Override
 protected void onPostExecute(Void aVoid) {
  super.onPostExecute(aVoid);
 }

 @Override
 protected void onProgressUpdate(Void... values) {
  super.onProgressUpdate(values);
 }

 @Override
 protected void onCancelled(Void aVoid) {
  super.onCancelled(aVoid);
 }

 @Override
 protected void onCancelled() {
  super.onCancelled();
 }
}

DemoAsyncTask task = new DemoAsyncTask();
task.execute("demo test AsyncTask");
//task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, "test");
//myTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "test");

Under simple analysis
The above is the simplest way to use AsyncTask. Among the methods we rewrote above, onInBackground method runs on the worker thread, and all other methods run on the main thread. In addition, Android provides us with two methods, which are listed above.

1. Method 1 performs our task using the default Executor, In fact, it is SERIAL_EXECUTOR, SERIAL_EXECUTOR We can actually customize it through methods. The default implementation of Android is to execute tasks one by one, That is, single-threaded, There is still an interesting history about whether the task execution of AsyncTask is single-threaded or multi-threaded. The earlier version was single-threaded implementation. Starting from Android2.X, Google changed it to multi-threaded implementation. Later, Google found that there would be a lot of extra work to ensure thread safety for developers, so starting from Android3.0, the default implementation was changed to single-threaded implementation. Today, we demonstrate that Framwork code version is 21 (Android5.0).
2. It also provides an interface for multithreaded implementation, the last line of code we wrote above, AsyncTask.THREAD_POOL_EXECUTOR.


public final AsyncTask<Params, Progress, Result> execute(Params... params) {
 return executeOnExecutor(sDefaultExecutor, params);
}
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

In fact, I believe that we often use AsyncTask in our usual work and study. We go directly to see the source code of AsyncTask (insert a digression, and we can also summarize our experience in our work and study at ordinary times and write it down ~ ~)


public abstract class AsyncTask<Params, Progress, Result> {
....
}

AsyncTask abstract class, there are three generic parameter types, the first is the parameter type you need to pass in, the second is the type of task completion progress, and the third is the return type of task completion results. Sometimes these parameters are not all needed, and those that are not needed are set to Void. In addition, there is only one Result, but there can be multiple Params.
We can see that the default constructor for AsyncTask initializes two objects, mWorker and mFuture.


private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
 mWorker = new WorkerRunnable<Params, Result>() {
  public Result call() throws Exception {
   mTaskInvoked.set(true);

   Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
   //noinspection unchecked
   return postResult(doInBackground(mParams));
  }
 };

 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 occured while executing doInBackground()",
      e.getCause());
   } catch (CancellationException e) {
    postResultIfNotInvoked(null);
   }
  }
 };

mWoker implements Callback interface, and Callback interface is an interface in the advanced concurrent frame package added by JDK 1.5, which can have a generic return value.


public interface Callable<V> {
/**
 * Computes a result, or throws an exception if unable to do so.
 *
 * @return computed result
 * @throws Exception if unable to compute a result
 */
V call() throws Exception;
}

FutureTask implements RunnableFuture interface, RunnableFuture interface is provided by JDK, and it inherits Runnable and Future interfaces by looking at its name. mFuture is an instance of FutureTask, which can be directly instantiated by Executor class execute. Let's continue to look at the execute method of AsyncTask.


public final AsyncTask<Params, Progress, Result>  execute(Params... params) {
 return executeOnExecutor(sDefaultExecutor, params);
}
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
  Params... params) {
 if (mStatus != Status.PENDING) {
  switch (mStatus) {
   case RUNNING:
    throw new IllegalStateException("Cannot execute task:"
      + " the task is already running.");
   case FINISHED:
    throw new IllegalStateException("Cannot execute task:"
      + " the task has already been executed "
      + "(a task can be executed only once)");
  }
 }

 mStatus = Status.RUNNING;

 onPreExecute();

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

 return this;
}

First call the onPreExecute () method, which is still on the main thread (strictly speaking, the thread that calls AsyncTask's execution), then exec. execute (mFuture), give the task to exec for processing, execute mFuture is actually invoke mWoker, and then call postResult (doInBackground (mParams), which is already running in the worker thread pool and will not block the main thread. The MESSAGE_POST_RESULT message is then sent to mHandler, and then the finish method is called. If isCancelled, onCancelled is called back, otherwise onPostExecute is called back.


private Result postResult(Result result) {
 @SuppressWarnings("unchecked")
 Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
   new AsyncTaskResult<Result>(this, result));
 message.sendToTarget();
 return result;
}
private static final InternalHandler sHandler = new InternalHandler();
private static class InternalHandler extends Handler {
 @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;
  }
 }
}
private void finish(Result result) {
 if (isCancelled()) {
  onCancelled(result);
 } else {
  onPostExecute(result);
 }
 mStatus = Status.FINISHED;
}

Now, in fact, we have finished the whole process of AsyncTask, and the callback methods exposed to us have also come. Now let's look back, AsyncTask is actually only an advanced concurrency feature provided by JDK 1.5. concurrent package is a package, which is convenient for developers to deal with asynchronous tasks. Of course, there are many detailed processing methods worth learning, such as feedback of task execution progress and guarantee of task execution atomicity, which are left for everyone to learn.

Source code analysis
Let's go deeper and look at the source code of AsyncTask. The following analysis of the implementation of this class, mainly thread pool and Handler.

Thread pool
The execute () method is called when executing an AsyncTask, so start with this:


public final AsyncTask<Params, Progress, Result> execute(Params... params){
 return executeOnExecutor(sDefaultExecutor, params);
}
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, 
  Params... params) { 
 if (mStatus != Status.PENDING) { 
  switch (mStatus) { 
   case RUNNING: 
    throw new IllegalStateException("Cannot execute task:" + " the task is already running."); 
       
   case FINISHED: 
    throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); 
      
      
  } 
 } 
 
 mStatus = Status.RUNNING; 
 // Execute first  onPreExecute
 onPreExecute(); 
 
 mWorker.mParams = params; 
 
 exec.execute(mFuture); 
 return this; 
} 

The execute method calls executeOnExecutor. In this method, check whether the task has been executed or finished, and then mark the task as running. onPreExecute is executed at first, and then parameters are assigned to mWorker objects. This mWorker is an Callable object that is eventually wrapped as an FutureTask with the following code:


private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { 
 Params[] mParams; 
} 

mWorker = new WorkerRunnable<Params, Result>() { 
  public Result call() throws Exception { 
   mTaskInvoked.set(true); 

   Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 
   //noinspection unchecked 
   return postResult(doInBackground(mParams)); 
  } 
 };
 
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 occured while executing doInBackground()", 
     e.getCause()); 
  } catch (CancellationException e) { 
   postResultIfNotInvoked(null); 
  } 
 } 
}; 


As you can see from the above code, the call () method in the mWorker object calls doInbackground, and the return value is given to the postResult method, which sends a message through Handler, which will be analyzed in detail later.

After the mWorker object is encapsulated as FutureTask, it is executed by the thread pool. As can be seen from the execute method, sDefaultExecutor is used, and its value defaults to SERIAL_EXECUTOR, that is, serial executor, which is implemented as follows:


private static class SerialExecutor implements Executor { 
 // Linear bidirectional queue, which is used to store all the AsyncTask Mission  
 final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); 
 // Currently executing AsyncTask Mission  
 Runnable mActive; 

 public synchronized void execute(final Runnable r) { 
  // Will the new AsyncTask Tasks are added to bidirectional queues  
  mTasks.offer(new Runnable() { 
   public void run() { 
    try { 
     // Execute AsyncTask Mission  
     r.run(); 
    } finally { 
     // Execute after the execution of the current task 1 Tasks 
     scheduleNext(); 
    } 
   } 
  }); 
  if (mActive == null) { 
   scheduleNext(); 
  } 
 } 

 protected synchronized void scheduleNext() { 
  // Take out the task at the head of the queue from the task queue, and hand it over to the concurrent thread pool for execution if there is one  
  if ((mActive = mTasks.poll()) != null) { 
   THREAD_POOL_EXECUTOR.execute(mActive); 
  } 
 } 
}

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

In the above code, if a task is executed, the execute method of SerialExecutor is called, and its logic is to add the Runnable object to the ArrayDeque queue, and then determine whether mActivie is empty. mActive is of course empty at the first execution, so when scheduleNext is executed, it is actually taking out the first task in the task queue and handing it to the thread pool (THREAD_POOL_EXECUTOR) for execution. In the run method of Runnable object added to mTask queue, scheduleNext will be called eventually, and then the queue head task will be taken out from the task queue to execute. In this way, single-threaded sequential execution of tasks is realized, so single-threaded execution is enabled by default in AsyncTask, and the next task will be executed only after the previous task is executed. If you want to enable multithreaded tasks, you can call executeOnExecutor directly (Executor exec, Params... params). The Executor parameters here can use THREAD_POOL_EXECUTOR that comes with AsyncTask, or you can define it yourself.

Handler
AsyncTask uses Handler to deliver messages internally, and its implementation is as follows:


public final AsyncTask<Params, Progress, Result> execute(Params... params) {
 return executeOnExecutor(sDefaultExecutor, params);
}
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
0

If the message type is the return value after task execution (MESSAGE_POST_RESULT), the finish () method is called:


public final AsyncTask<Params, Progress, Result> execute(Params... params) {
 return executeOnExecutor(sDefaultExecutor, params);
}
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
1

As you can see from the above, if the task is cancelled, onCancelled will be called, otherwise onPostExecute will be called, so if an AsyncTask task is cancelled, onPostExecute will not be executed.

If the message type is Execution Progress (MESSAGE_POST_PROGRESS), onProgressUpdate will be called, which is empty by default and can be overridden as needed.

Summarize
The main logic of AsyncTask is as analyzed above, summarizing several points that need attention:

The AsyncTask class must be loaded on the UI thread (the system will do it automatically for us from 4.1 onwards) AsyncTask objects must be created on an UI thread The execute method must be called on an UI thread Do not manually call onPreExecute (), doInBackground, onProgressUpdate methods A task can only be called once (the second call will throw an exception)

Related articles: