AsyncTask basis

AsyncTask is a lightweight asynchronous task class provided by the Android operating system. Before Android1.6, it was a serial task, but after Android1.6, it was changed to a parallel task. Since parallel tasks introduce concurrency problems,Android 3.0 provides support for serial tasks again. After that,AsyncTask defaults to a serial task, but can be specified as a parallel task through the executeOnExecutor() method.

AysncTask is an abstract generic class. It provides three generic parameters: Params,Progress, and Result, respectively, to represent the input parameter type, background task execution Progress type, and task output Result type. If you do not need to specify the parameter type to Void. In addition AsyncTask provides the following four methods to inform the caller of the task status:

  • onPreExecute(): Notifies background tasks to start, working on the main thread.
  • doInBackground(Params... params): Executes tasks in the background thread pool. In this method you can passpublishProgress()To update the progress of the task, which eventually results in a call to onProgressUpdate().
  • onProgressUpdate(Progress... values): Is executed when the background task progress changes (i.epublishProgress()After the call), work on the main thread
  • onPostExecute(Result result): Notifies the caller of the execution result of the task. Result is the return result of the background taskdoInBackground()Again, this method works on the main thread

In addition,AsyncTask provides a method to cancel the task, at which point onCancelled() is called back. When used, we need to inherit the class and implement the related methods in it. Note that AsyncTask is created on the main thread, and its execution methods execute() and executeOnExecutor() are also called on the main thread. An AsyncTask object can be executed only once; otherwise, an error will be reported.

AsyncTask principle

The overall implementation of AsyncTask is very simple, with two internal thread pools :SerialExecutor and custom Excutor, which queue tasks and actually execute tasks, respectively. In addition, its internal implementation of the task encapsulation. In terms of its design, what matters is the encapsulation of the task and the SerialExecutor, on which the entire AsyncTask is built.

Task creation

See the definition of an AsyncTask’s internal task through its construction process. The main thread Handler is first associated with its construction, which is used for thread switching, which is why the onProgressUpdate() method works on the main thread.

public abstract class AsyncTask<Params,Progress,Result>{ public AsyncTask(){ this((Looper) null); } public AsyncTask(){ this(handler ! = null ? handler.getLooper() : null); } public AsyncTask(Looper callbacklooper){ //1. Associated with the main thread of the Handler mHandler = callbackLooper = = null | | callbackLooper = = stars. GetMainLooper ()? getMainHandler() : new Handler(callbackLooper); WorkerRunnable = new WorkerRunnable<Params, WorkerRunnable<Params, WorkerRunnable<Params, WorkerRunnable<Params, WorkerRunnable<Params, WorkerRunnable<Params, WorkerRunnable<Params, WorkerRunnable<Params, WorkerRunnable<Params, Result>() {public Result Call () throws Exception {//2.1 Marks that the task has been executed, and mTaskInvoked is of AtomicBoolean type mTaskInvoked.set(true); Result result = null; Try {//2.2 Set the priority of the current task thread to THREAD_PRIORITY_BACKGROUND to affect the execution efficiency of the main thread, that is, do not steal the CPU resources of the main thread Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); Result = doInBackground(mParams); / / in 2.4 you can ensure the subsequent operations will be blocked for a long time, will invoke the command prompt Kernal release has been hung up / / object references, can reduce the process of resource utilization Binder. The flushPendingCommands (); } catch (Throwable tr) {//2.5 An exception occurs during the execution of the task, marking it as cancelled mCancelled. Set (true); throw tr; } finally { postResult(result); } return result; }}; 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()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); }}}; }}Copy the code

The two most core objects in the above code are mWorker and mFuture. MWorker is the instance of WorkerRunnable, which is the real task object in AsyncTask. In order to get the execution structure of the task,WorkerRunnable implements the Callable interface A non-runnable interface is defined as follows:

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

In mWorker’s call() method, doInBackground(), which is actually used to perform background tasks, is called.

Once the mWorker object is created, the mFuture object is created, which is a direct subclass of FutureTask (in the form of an anonymous inner class) whose constructor takes mWorker as an argument. FutureTask is a Java package class. It implements the RunnableFuture interface, which in turn inherits from the Runnable and Future interfaces, so it’s not hard to guess that FutureTask can be executed as Runnable by threads, You can also get the return value of the Callable as a Future (which, of course, it does). Eventually the mWorker will be put into the thread pool for execution.

In a nutshell, our custom background operations are finally encapsulated in an mFuture. To perform an mFuture is to perform our custom background operations. Arguably, the most important thing for AsyncTask implementation is the creation of mWoker and mFuture objects. The realization of tasks in AsyncTask has been clear so far. Next, we need to pay attention to the principle that AsyncTask is serial and parallel.

Serial task

After Android 3.0, execute() of AsyncTask is executed sequentially by default, that is, tasks are executed one after another. The following code is used to reveal how AsyncTask implements serial tasks. Normally, after an AsyncTask instance is created, the task is executed using the following methods:

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

Call executeOnExecutor(Executor exec,Params… Params) to continue the operation:

@MainThread public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... //1. Check the task status 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)"); }} //2. In normal cases, set the task Status to RUNNING mStatus = status.running. //3. Invoked before background task execution. Since executeOnExecutor() is called on the main thread, onPreExecute() is also called on the main thread onPreExecute(); //4. Assign the params parameter we passed to mWorker to the mParam member variable. //5. Commit the task to the thread pool and execute exec.execute(mFuture); return this; }Copy the code

Note that after executing the execute() method on an AsyncTask, the task state is set to RUNNING. Calling the execute() method of an AsyncTask object again when its current state is RUNNING or FINISHED will raise an exception. This is why we said that an AsyncTask instance can only be executed once.

From the above code we can see that the task object mFuture will eventually be executed by the thread executor EXEC. Exec is now assigned to sDefaultExecutor, which is defined as follows:

public abstract class AsyncTask<Params,Progress,Result>{ ... public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); . private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; . }Copy the code

SerialExecutor

SerialExecutor is a custom executor in AsyncTask that introduces an ArrayDeque to hold a queue of tasks:

Private static class SerialExecutor implements Executor {// mTask Used to save tasks Final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive; public synchronized void execute(final Runnable r) { // 1. Mtasks.offer (new Runnable() {public void run() {try {r.run(); } finally {// After the current task is completed, the next task is scheduled to be executed by the scheduleNext(); }}}); If (mActive == null) {scheduleNext(); }} protected synchronized void scheduleNext() {if ((mActive = tasks.poll ())! = null) { THREAD_POOL_EXECUTOR.execute(mActive); }}}Copy the code

SerialExecutor has a member variable of type ArrayDeque, mTasks, which is used to hold the task queue.

An ArrayDeque is a two-ended queue based on a circular array implementation, with an offer() operation to add elements to the end of the queue and a poll() operation to fetch and remove elements at the head of the queue.

The SerialExecutor instance saves a new task to the mTasks via offer() when execute() is executed. If no task is currently in progress, the SerialExecutor instance uses scheduleNext() to schedule the next task. The only thing scheduleNext() does is to take a task from mTasks and hand it to the THEAD_POOL_EXECUTOR thread pool. summary

To summarize, newly added tasks are first stored at the end of the queue via SerialExecutor, and then pulled from the head of the queue to be executed by the THREAD_POOL_EXECUTOR thread pool. Although THREAD_POOL_EXECUTOR supports simultaneous execution of multiple tasks, SerialExecutor only pulls new tasks from the dual-end queue and puts them into the thread pool for execution after a single task has completed, so the execution of tasks is serial.

Thread pool Definition

THREAD_POOL_EXECUTOR is a user-defined thread pool object in AsyncTask. The number of core threads can be set as required. The number of core threads ranges from two to four. The maximum number of threads is the number of CPU cores *2+1. The default non-core thread lifetime is 30 seconds, which is fully defined as follows:

public abstract class AsyncTask<Params, Progress, Result>{ private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final int KEEP_ALIVE_SECONDS = 30; 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()); }}; static { ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); threadPoolExecutor.allowCoreThreadTimeOut(true); THREAD_POOL_EXECUTOR = threadPoolExecutor; }... }Copy the code

Parallel tasks

We mentioned that AsyncTask has a thread pool, THREAD_POOL_EXECUTOR, but the task could not be parallel because it was submitted one task at a time to this thread pool. In this case, it is very simple to implement the task parallelism, just need to submit multiple tasks directly to the thread pool for execution. AsyncTask executeOnExecutor provided during the () is so so, when use direct call exec and specify the method parameter as the AsyncTask. THREAD_POOL_EXECUTOR task Executor can. (of course we can also customize the new Executor )

@MainThread 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; }Copy the code

conclusion

So far the analysis of AsyncTask is over. In terms of practical use AsyncTask has a slightly good design but is still cumbersome to use and limited to a few scenarios. In the early days of Android,AsyncTask was very popular, but now as the entire Android development ecosystem grows,AsyncTask is becoming less and less used. But with AsyncTask we can still revisit some of the things we learned about thread pool design/thread usage.