1. Introduction

In this article, we will take a detailed look at the use and principle of AsyncTask.

2. Basic use

AsyncTask is an asynchronous task class provided by Android. It can be used as follows: 1. Implement custom AsyncTask, duplicate corresponding methods

class MyAsyncTask extends AsyncTask<String, Integer, String> {
        int percent = 0;

        @Override
        protected void onPreExecute() {
            //doLog.d(TAG,"onPreExecute:" + Thread.currentThread().getName() + "Asynchronous task ready to start");
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            //doLog.d(TAG,"onProgressUpdate:" + Thread.currentThread().getName() + "Mission in progress:" + values[0] + "%");
        }

        @Override
        protected void onPostExecute(String result) {
            //doLog. D (TAG,"onPostExecute:" + Thread.currentThread().getName() + "" + result);
        }

        @Override
        protected String doInBackground(String... strings) {
            String param = strings[0];
            Log.d(TAG, "The doInBackground." + Thread.currentThread().getName() + "Asynchronous task in execution, get parameter:" + param);
            while (percent < 100) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                percent += 10;
                publishProgress(percent);
            }
            Log.d(TAG, "The doInBackground." + Thread.currentThread().getName() + "Asynchronous task complete!");
            return "Mission accomplished."; }}Copy the code

2. Create a user-defined AsyncTask object and invoke the execute method to start the task

    MyAsyncTask myAsyncTask = new MyAsyncTask();
    myAsyncTask.execute("Test");
Copy the code

Running results:

AsyncTask
doInBackground
onPreExecute
onProgressUpdate
onPostExecute

  • DoInBackground: by its name, is the method responsible for executing asynchronous tasks, running in the background child thread, is the method that must be overridden.
  • OnPreExecute:doInBackgroundMethod is called before it can do some preparatory work before performing an asynchronous task, running on the main thread.
  • OnPostExecute:doInBackgroundMethod is then called to get a large return result, which can do some processing on the main thread.
  • OnProgressUpdate:doInBackgroundMethod callpublishProgressMethod is called after it is used to update progress when an asynchronous task is running on the main thread.

In the example code, the time-consuming task is simulated in doInBackground method and the log is output. Through the log, we can confirm the execution order of several methods of overwrite and find that onPreExecute, onProgressUpdate and onPostExecute are running in the main thread. The doInBackground method runs in a child thread. This is as expected, so the time consuming tasks are written in the doInBackground method. In addition, AsyncTask can pass in required parameters when calling the execute method. In the doInBackground method, these parameters can be obtained, and there is no limit on the number of parameters. So much for AsyncTask, let’s see how it works.

3. Operation principle

Understand the operation principle to view his source code, AsyncTask source code is not much, a total of less than 800 lines to achieve the required functions. We follow the steps of AsyncTask to see its source flow. After implementing your own AsyncTask, the first step is to create a custom AsyncTask object, so start with the constructor.

    public AsyncTask() { this((Looper) null); } public AsyncTask(@Nullable Handler handler) { this(handler ! = null ? handler.getLooper() : null); } public AsyncTask (@ Nullable stars callbackLooper) {/ / to mHandler initialization assignment mHandler = callbackLooper = = null | | callbackLooper == Looper.getMainLooper() ? getMainHandler() : new Handler(callbackLooper); // create WorkerRunnable mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Result result = null; Try {/ / set the thread priority Process. The setThreadPriority (Process. THREAD_PRIORITY_BACKGROUND); / / calldoInBackground method and return result =doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true); throw tr; } finally {// will return the result post pass postResult(result); }returnresult; }}; // Create FutureTask and pass in WorkerRunnable mFuture = new FutureTask<Result>(mWorker) {@override protected voiddone() {
                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

AsyncTask has three constructors, the most commonly used is a constructor with no arguments. It can also call a constructor with arguments and pass in a Handler or Looper. Either call will end up in the constructor with a Looper. Assignment checks whether the Looper passed in is null, If it is null or the Looper passed in is the main thread’s Looper, getMainHandler is called to assign the main thread’s Handler directly to the mHandler, otherwise a Handler is created using the passed Looper to assign the member variable.

Then we create a WorkerRunnable, which is a worker thread, and we see that it overwrites the call method and passes the WorkerRunnable to a FutureTask, which is implemented by a Callable, Click trace to find this class, which is an inner class.

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

As you can see, this is an abstract class that implements the Callable interface and encapsulates our incoming parameters. The call method of the WorkerRunnable constructor shows that the priority of the current thread is set to background, the doInBackground method is executed to get the result of the asynchronous task, and the postResult method is called in the finally code block to pass the result.

The last step in the constructor is to create an asynchronous task object, FutureTask, which passes in the WorkRunnable. When the asynchronous task is completed, the done method is called. The postResultIfNotInvoked method passes the result. For those that are not clear about Callable and FutureTask, see a brief introduction to the basics of multithreading in the previous article.

Before we go ahead and call the execute method, let’s take a look at the AsyncTask class and its static code block. We know that static code blocks are loaded as the class loads, so let’s take a look at this static code block.

Private static final int CPU_COUNT = Runtime.getruntime (). AvailableProcessors (); private static final int CPU_COUNT = Runtime.getruntime (). 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 = newThreadFactoryPrivate final AtomicInteger mCount = new AtomicInteger(1); // Returns an AsyncTask# numbered threads
        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #"+ mCount.getAndIncrement()); }}; Private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128); /** * An {@link Executor} that can be used to execute tasksin parallel.
     */
    public static final Executor THREAD_POOL_EXECUTOR;

    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

The static code block initializes a thread pool and assigns it to THREAD_POOL_EXECUTOR in the member variable, which is commented to be a thread pool for performing parallel tasks. Let’s go to the execute method to see how AsyncTask works.

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

Execute method calls the executeOnExecutor method, so in addition to passing params we’re passing an executeOnExecutor, so let’s see what executeOnExecutor is.

    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    /**
     * An {@link Executor} that executes tasks one at a time in serial
     * order.  This serialization is global to a particular process.
     */
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
Copy the code

It references the member variable SERIAL_EXECUTOR, which in turn is a SerialExecutor object, annotated as a thread pool for serial execution of tasks. Take a closer look at the SerialExecutor.

Private static class SerialExecutor implements Executor {// Final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive; Public synchronized void execute(final Runnable r) {// Add a task to the task queue mtasks. offer(new)Runnable() {
                public void run() {try {// Call the run method of the passed Runnable r.run(); } finally { scheduleNext(); }}}); // Call scheduleNext directly on the first entry or when the mActive column is nullif(mActive == null) { scheduleNext(); }} // Retrieve the task from the queue to the THREAD_POOL_EXECUTOR thread pool for processing protected synchronized voidscheduleNext() {
            if((mActive = mTasks.poll()) ! = null) { THREAD_POOL_EXECUTOR.execute(mActive); }}}Copy the code

SerialExecutor has a task queue, mTasks, whose execute method rewraps the passed Runnable into a new Runnable, and calls the original Runnable’s run method in the new Runnable’s run method. The scheduleNext method is called in the finally code block, and the new Runnable task is finally added to the mTasks task queue. The scheduleNext method is called if mActive is empty. The mActive is a Runnable object that is removed from the task queue in the scheduleNext method. The Runnable object is not empty when the task is removed from the scheduleNext method and is assigned to the THREAD_POOL_EXECUTOR thread pool. THREAD_POOL_EXECUTOR is the pool of threads initialized in a static code block and the pool of threads that ultimately process asynchronous tasks.

And then we go back to the executeOnExecutor method, the executeOnExecutor method called in the Execute method of AsyncTask.

 @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... Params) {// Check whether the status of AsyncTask is PENDINGif(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; // call onPreExecute onPreExecute(); // Pass the argument passed in the execute method to the WorkerRunnable created in the constructor. MParams = params; // Pass the FutureTask created in the constructorexecThe thread pool sDefaultExecutor thread pool handles exec.execute(mFuture);return this;
    }
Copy the code

Method to determine the status of AsyncTask. The status of AsyncTask can be described as follows.

Public enum Status {/** * Indicates that the task has not been executed */ PENDING, /** * Indicates that the task is being executed */ RUNNING, and /** * Indicates that the task is completed */ FINISHED,}Copy the code

AsyncTask does not throw the corresponding exception before the task is executed. Otherwise, onPreExecute is called before the asynchronous task is executed. The received parameters are then passed into the WorkerRunnable constructor, and the exec.execute method is called to hand the asynchronous task over to the exec thread pool, the sDefaultExecutor thread pool. Then, as we have seen before, the asynchronous task will first go to the SerialExecutor thread pool, where it will be put into the SerialExecutor queue, and finally handed over to the THREAD_POOL_EXECUTOR thread pool for execution.

The postResult or postResultIfNotInvoked method is invoked after the WorkRunnable task is executed.

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

The postResultIfNotInvoked method is still invoked in the postResult method, so go to the postResult method trace.

 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

The postResult method calls the getHandler method to get the mHandler in the member variable to send a message that encapsulates the return result of the asynchronous task as an AsyncTaskResult object into the obJ of the message. The AsyncTaskResult object encapsulates the result of the current AsyncTask and asynchronous task.

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

Remember where mHandler was created? Is created in the AsyncTask constructor using the getMainHandler method.

private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            returnsHandler; }}Copy the code

The getMainHandler method creates an InternalHandler using the main thread Looper. The message sent in the postResult method is handled by this Handler.

private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }
        @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

The handleMessage method in InternalHandler processes two kinds of messages, one is the message of the completion result of the asynchronous task and the other is the message of the update progress. Let’s first look at the task result message, which corresponds to MESSAGE_POST_RESULT. Call the result.mtask. finish method, which is the finish method of AsyncTask.

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

If the task is cancelled, the onCancelled method will be cancelled. If the AsyncTask is not cancelled, the onPostExecute method will be called. Then the AsyncTask status will be changed to completed. At this point, an AsyncTask execution process ends.

Finally, let’s look at the way asynchronous tasks update their progress. To make a progress update, we need to call the publishProgress(Percent) method in the doInBackground method to pass in the progress. So look at the publishProgress(Percent) method.

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

Method to check whether the task is not cancelled and get Handler to send the update progress message, progress data is also encapsulated in AsyncTaskResult type. Then we’ll see what to do with the message.

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

Processing the message is simple by calling the AsyncTask onProgressUpdate method and passing in progress data. At this point, the operation principle of AsyncTask is completely explained. Finally, the whole process is summarized through two diagrams.

Methods the process

4. AsyncTask

4.1 AsyncTask calls must be created on the main thread

Does AsyncTask have to create calls on the main thread? Practice is the sole criterion for testing truth.

new Thread(new Runnable() {
    @Override
    public void run() {
        MyAsyncTask myAsyncTask = new MyAsyncTask();
        myAsyncTask.execute("Test");
    }
}).start();
Copy the code

Running result log:

AsyncTask
onPreExecute
onPreExecute
executeOnExecutor
AsyncTask
InternalHandler

Is it different to create a child thread that uses AsyncTask just to run onPreExecute? Why do we have to create calls on the main thread?

In fact, this statement is based on the old version of AsyncTask. In the above operation principle, all the source code is intercepted from the API28 version of AsyncTask. Where the Handler is created by the Looper of the main thread that gets it.

    sHandler = new InternalHandler(Looper.getMainLooper());
Copy the code

In earlier versions, look at the code in the API10 version of AsyncTask.

    private static final InternalHandler sHandler = new InternalHandler();
Copy the code

Earlier versions didn’t use the main thread Looper to create a Handler so the AsyncTask was created on the same thread as the Handler, which would cause both onPostExecute and onProgressUpdate to run on that thread, Creating an AsyncTask outside the main thread will result in the onPostExecute and onProgressUpdate methods not running on the main thread and therefore not being able to update the UI directly in either method.

4.2 before Android3.0 AsyncTask is executed in parallel, after 3.0 it is executed in serial.

An AsyncTask calls the execute method to start a task and places the task into a SerialExecutor thread pool, which maintains a queue. Each time, one task is taken from this queue and handed over to the pool of threads that actually process the task. In earlier versions, there was no SerialExecutor thread pool, and tasks were executed directly into the task thread pool.

    public final AsyncTask<Params, Progress, Result> execute(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;
        sExecutor.execute(mFuture);
        return this;
    }

    private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
Copy the code

API10 AsyncTask execute method (Android2.3.7). You can see that tasks are added directly to the sExecutor thread pool.

4.3 AsyncTask Memory Leakage

If the AsyncTask is a non-static internal class, the time-consuming tasks in the AsyncTask are not completed when the Activity is destroyed. In this case, the AsyncTask will still hold the reference to the Activity, causing it to be unable to be reclaimed normally, resulting in memory leakage.

The solution is also very simple as long as the use of static class with WeakReference can be used. AsyncTask has a cancel method. If you call this method in the Activity destory method to cancel the task, can you solve the memory leak? The answer is no, specific or look at the source code.

    private final AtomicBoolean mCancelled = new AtomicBoolean();

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

See that the Cancel method of AsyncTask simply changes the mCancelled status to true and calls the mFuture.cancel method instead.

 public boolean cancel(boolean mayInterruptIfRunning) {
        if(! (state == NEW && U.compareAndSwapInt(this, STATE, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))return false;
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if(t ! = null) t.interrupt(); } finally { // final state U.putOrderedInt(this, STATE, INTERRUPTED); } } } finally { finishCompletion(); }return true;
    }
Copy the code

The mFuture.cancel () method ends up calling a Thread’s T.innterrupt () method, whereas threadInterrupt () won’t interrupt a Thread immediately. It will only change the rupt Thread’s flag state. You need to judge the processing in your code or wait for the thread to block before throwing an InterruptedException to exit the thread. So AsyncTask’s cancel method does not prevent memory leaks.

5. To summarize

This is all about AsyncTask. As an official encapsulation class for processing asynchronous tasks, AsyncTask is not so easy to use. Various problems may occur if you do not pay attention to it, but it is necessary to understand its operating principle.