Brief analysis of Android AsyncTask source code

AsyncTask

When beginners learn Android threads, the most contacted is AsyncTask. Until now, I have not carefully checked the source code and implementation of this class. I am ashamed. Needless to say, this article mainly provides a brief description based on the class structure of AsyncTask. The general introduction is officially written in the documentation or comments, and we need to study it carefully.

AysncTask is only suitable for operation scenarios of up to a few seconds. If there is a higher demand, the official strongly recommends using Executor / ThreadPoolExecutor / FutureTask, etc. AsyncTask is divided into several parts:

postResult

When the doInBackground() method is executed, the asynchronous execution result will be called back to postResult(), and postResult() will throw the data to the InternalHandler.

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

InternalHandler

private static class InternalHandler extends Handler {
    
    
    public InternalHandler() {
        super(Looper.getMainLooper());
    }

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

We see two cases of InternalHandler, the former calls finish() to pass data to the onPostExecute() method, and the latter calls the onProgressUpdate() method to update the progress bar. AsyncTask can perform UI operations actually through InternalHandler

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

Obtaining Handler internally in AsyncTask is an instance generated by singleton mode :

private static Handler getHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            return sHandler;
        }
    }

AsyncTaskResult

Act as the role of Entity. When onPostExecute is called, the data is first placed in the AsyncTaskResult object through the handler and then passed to the InternalHandler for UI operation

 @SuppressWarnings({
   
   "RawUseOfParameterizedType"})
    private static class AsyncTaskResult<Data> {
    
    
        final AsyncTask mTask;
        final Data[] mData;

        AsyncTaskResult(AsyncTask task, Data... data) {
            mTask = task;
            mData = data;
        }
    }

ThreadPoolExecutor

AsyncTask is actually an asynchronous queue implemented through a thread pool . You can continuously add tasks to the queue, fetching and releasing to achieve multi-threaded operation.

/**
 * An {@link Executor} that can be used to execute tasks in 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;
}

Status

Enumeration class, representing the status of the current Task

 /**
     * Indicates the current status of the task. Each status will be set only once
     * during the lifetime of a task.
     */
    public enum Status {
        /**
         * Indicates that the task has not been executed yet.
         */
        PENDING,
        /**
         * Indicates that the task is running.
         */
        RUNNING,
        /**
         * Indicates that {@link AsyncTask#onPostExecute} has finished.
         */
        FINISHED,
    }

Binder

The AsyncTask constructor uses the Binder method from the beginning:

/**
     * Flush any Binder commands pending in the current thread to the kernel
     * driver.  This can be
     * useful to call before performing an operation that may block for a long
     * time, to ensure that any pending object references have been released
     * in order to prevent the process from holding on to objects longer than
     * it needs to.
     */
    public static final native void flushPendingCommands();

WorkerRunnable

There are two parts when the constructor is initialized, one is mWorker and the other is mFuture. The former implements the Callable interface for the purpose of receiving external parameters. By implementing Callable, the user parameters are passed to doInBackground() in the call() method to get the Result.

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

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };

Future

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

executeOnExecutor

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

execute

To start AsyncTask, you must execute this method, which uses sDefaultExecutor

/**
 * Convenience version of {@link #execute(Object...)} for use with
 * a simple Runnable object. See {@link #execute(Object[])} for more
 * information on the order of execution.
 *
 * @see #execute(Object[])
 * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
 */
@MainThread
public static void execute(Runnable runnable) {
    sDefaultExecutor.execute(runnable);
}

SerialExecutor

The key part, the queue in AsyncTask is implemented by the ArrayDeque of this class, and finally execute executes the method in this class.

/**
 * 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();
 private static class SerialExecutor implements Executor {
    
    
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

Precautions for the use of AsyncTask

In the official performance optimization model (e) (Chinese stamp here ->) Android performance optimization model - Season 5 has been written very clearly, the following quote from the great god Hu Kai translations :

4)Good AsyncTask Hunting

AsyncTask is a component that people love and hate. It provides a simple asynchronous processing mechanism, but it also introduces some disgusting troubles. Once AsyncTask is used improperly, it is likely to have a negative impact on the performance of the program, and may also cause memory leaks.

For example, a typical usage scenario often encountered: the user switches to a certain interface, which triggers the loading operation of the image on the interface, because the image loading is relatively long, we need to process it in a sub-thread When the image is loaded, after the image is processed in the sub-thread, the processed image is returned to the main thread and handed over to the UI to update the screen.

img

The emergence of AsyncTask is to quickly implement the above usage scenarios. AsyncTask puts the preparation work in the main thread into the onPreExecute()method for execution, and the doInBackground()method is executed in the worker thread to handle those heavy tasks. Once the task is executed, it will Will call the onPostExecute()method to return to the main thread.

img

What are the issues that need to be paid attention to when using AsyncTask? Please pay attention to the following points:

  • First of all, by default, all AsyncTask tasks are executed by linear scheduling. They are in the same task queue and executed one by one in order. Suppose you start 20 AsyncTasks in sequence. Once one of the AsyncTasks takes too long to execute, other remaining AsyncTasks in the queue are in a blocked state. You must wait until the task is executed before you can have a chance to execute the next task. The situation is shown in the figure below:

img

In order to solve the linear queue waiting problem mentioned above, we can AsyncTask.executeOnExecutor()force the designated AsyncTask to use the thread pool to schedule tasks concurrently.

img

  • Secondly, how can we truly cancel the execution of an AsyncTask? We know that AsyncTaks provides cancel()a method, but what does this method actually do? The thread itself does not have the ability to abort the code being executed. In order to be able to destroy a thread earlier, we need doInBackground()to continuously add the judgment logic of whether the program is aborted in the code, as shown in the following figure:

img

Once the task is successfully terminated, AsyncTask will not continue to be called onPostExecute(), but onCancelled()the result of the cancellation of the task execution will be fed back through the called callback method. We can decide whether to update the UI normally or destroy the memory occupied by the corresponding task according to which method the task calls back to (whether it is onPostExecute or onCancelled).

  • Finally, using AsyncTask can easily lead to memory leaks. Once AsyncTask is written as an internal class of Activity, it is easy to cause Activity to leak due to the uncertainty of the AsyncTask life cycle.

img

In summary, although AsyncTask provides a simple and convenient asynchronous mechanism, we still need to pay special attention to its shortcomings to avoid serious system performance problems caused by incorrect use.

Guess you like

Origin blog.csdn.net/zxccxzzxz/article/details/54565165