Android AsyncTask Source Analysis

  • 2021-06-28 14:06:01
  • OfStack

UI operations can only be performed in the main thread in Android, or in the case of other sub-threads, Handler, an asynchronous message processing mechanism, is required.In addition, there is a very convenient AsyncTask class that encapsulates the Handler and thread pools internally.This article first briefly introduces the usage of AsyncTask, then analyzes the implementation.

Basic Usage
AsyncTask is an abstract class and we need to create subclasses to inherit it and override some methods.AsyncTask accepts three generic parameters:

Params: Specifies the type of parameters passed to the task when it executes
Progress: Specifies the type of parameter that returns task progress to the UI thread when background tasks execute
Result: Specifies the type of results returned after the task is completed
In addition to specifying generic parameters, you need to override a few methods as needed, commonly used as follows:

onPreExecute(): This method is called by the UI thread to do some initialization before the task executes, such as displaying the load progress control on the interface
doInBackground: Called in the background thread immediately after the end of onPreExecute() for time-consuming operations.In this method, the publishProgress method can be called to return the progress of the task
onProgressUpdate: Called after doInBackground calls publishProgress, working on the UI thread
onPostExecute: Called after the background task ends, working on the UI thread
Source Code Analysis
The following is an analysis of the implementation of this class, which consists mainly of a thread pool and Handler.

1. Thread Pool
From this point on, when an AsyncTask is executed, the execute() method is called:


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, the task is checked for execution or completion, and then the task is marked as running.Start with onPreExecute, then assign the parameter to the mWorker object.This mWorker is an Callable object and is eventually wrapped as 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); 
  } 
 } 
}; 

From the code above, you can see that 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 is analyzed in more detail later.

After the mWorker object is encapsulated as FutureTask, it is handed over to the thread pool for execution. As you can see from the execute method, sDefaultExecutor is used, and its value defaults to SERIAL_EXECUTOR, also known as serial executor, is implemented as follows:


 private static class SerialExecutor implements Executor { 
 // Linear two-way queue to store all AsyncTask task  
 final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); 
 // Currently Executing AsyncTask task  
 Runnable mActive; 

 public synchronized void execute(final Runnable r) { 
  // Will be new AsyncTask Join a two-way queue  
  mTasks.offer(new Runnable() { 
   public void run() { 
    try { 
     // implement AsyncTask task  
     r.run(); 
    } finally { 
     // Execute the current task after it has finished executing 1 Tasks 
     scheduleNext(); 
    } 
   } 
  }); 
  if (mActive == null) { 
   scheduleNext(); 
  } 
 } 

 protected synchronized void scheduleNext() { 
  // Tasks that remove the head of the queue from the task queue and, if any, leave it to the concurrent thread pool to execute  
  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 there is a task execution, the execute method of SerialExecutor is called, its logic is to add the Runnable object to the ArrayDeque queue, and then determine if mActivie is empty.mActive is of course empty on the first execution, so executing scheduleNext is actually taking the first task out of the task queue and giving it to the thread pool (THREAD_POOL_EXECUTOR) Execution.scheduleNext is always called in the run method of the Runnable object joining the mTask queue, and then the queue header task is taken out of the task queue to execute.This enables single-threaded sequential execution of tasks, so single-threaded execution is enabled by default in AsyncTask, and only the last task executes before the next one executes.If you want to enable multithreaded execution, you can call executeOnExecutor (Executor exec, Params... params) directly, where the Executor parameter can use THREAD_which comes with AsyncTaskPOOL_EXECUTOR can also be defined by itself.

2. Handler
AsyncTask delivers messages internally using Handler, which is implemented as follows:


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; 
  } 
 } 
} 

If the message type is the return value after the task executes (MESSAGE_POST_RESULT) will call the finish() method:


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

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) will call onProgressUpdate, which is empty by default and can be overridden as needed.

summary
The main logic of AsyncTask, as analyzed above, summarizes a few things to note:

1) Classes of AsyncTask must be loaded on the UI thread (the system will help us do this automatically starting at 4.1)
2) The AsyncTask object must be created in the UI thread
3) The execute method must be called on the UI thread
4) Do not manually call the onPreExecute(), doInBackground, onProgressUpdate methods
5), a task can only be called once (the second call throws an exception)

There is one more detail to study the source code for yourself, and several good articles are recommended:

Android AsyncTask is fully interpreted, leading you to a thorough understanding of the source code


Related articles: