preface

AsnycTask should be familiar to any Android developer; Using AsyncTask makes it easy to process time-consuming operations asynchronously; The AsyncTask encapsulates handlers and threads, simplifying the use of handlers and making it easy to use. Let’s take a look at an example of AsyncTask usage in the Android SDK:

 private class DownloadFilesTask extends AsyncTask<URL.Integer.Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }

     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }

     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes"); }}Copy the code

In onCreate, perform this task:

 new DownloadFilesTask().execute(url1, url2, url3);Copy the code

It’s really easy to use (though still a bit cumbersome compared to the popular Retrofit and Volley). But have you thought about the following questions; Why should doInBackground be used for time-consuming operations? Why can onPostExecute and onProgressUpdate do UI updates? Who dictates that these methods be on the UI thread? How does the publishProgress pass the parameter to the onProgressUpdate method? How do I cancel a time-consuming operation in progress? What would you do if you wrapped handlers and Threads yourself? Ok, with these questions in mind, let’s take a look at how AsyncTask works.

The implementation principle of AsyncTask can be said to be very clever, the whole code only has more than 300 lines excluding comments, but the function is very powerful, because it uses Java’s own concurrency toolkit java.util.concurrent. This package contains a set of classes that make concurrent programming in Java easier and easier. In order to facilitate the understanding of the implementation principle of AsyncTask, let’s first popularize some basic Java knowledge used in a wave of AsyncTask.

Basic knowledge of

Block queue BlockingQueue

BlockingQueue is usually used when one thread produces objects and another thread consumes them. Here is an illustration of this principle:

One thread puts a BlockingQueue into it and another thread takes a BlockingQueue from it. A thread will keep producing new objects and inserting them into the queue until the queue reaches its capacity. In other words, it is finite. If the blocking queue reaches its critical point, the production thread will block when inserting new objects into it. It blocks until the consuming thread removes an object from the queue. The consuming thread will always pull objects from the blocking queue. If a consuming thread tries to fetch an object from an empty queue, the consuming thread will block until a producing thread drops an object into the queue.

Chain blocking queue LinkedBlockingQueue

The LinkedBlockingQueue class implements the BlockingQueue interface. LinkedBlockingQueue stores its elements internally in a chained structure (linked nodes). The chain structure can select an upper limit if desired. If no upper limit is defined, integer.max_value is used as the upper limit. LinkedBlockingQueue internally stores elements in FIFO(first in, first out) order. The first element in the queue is the oldest of all elements, and the last element is the shortest.

ArrayDeque

Array queue features of ArrayDeque

  • A queue that grows in size
  • Internally, arrays are used to store data
  • Thread insecurity
  • The internal array length is 8, 16, 32… . Two to the n
  • The head pointer starts at the end of the internal array, and the tail pointer starts at 0. Head is subtracted by one when the head is inserted, and tail is incremented by one when the tail is inserted. If head==tail, the array capacity is insufficient. In this case, the array capacity needs to be doubled.

ExecutorService

Java. Util. Concurrent. The ExecutorService interface represents an asynchronous execution mechanism, enabled us to perform a task in the background. So an ExecutorService is a lot like a thread pool.

ExecutorService simple implementation

ExecutorService executorService = Executors.newFixedThreadPool(10);  

executorService.execute(new Runnable() {  
    public void run(a) {  
        System.out.println("Asynchronous task"); }}); executorService.shutdown();Copy the code

Start by creating an ExecutorService using the newFixedThreadPool() factory method. A pool of ten threads is created to perform tasks. An anonymous implementation class of the Runnable interface is then passed to the execute() method. This will cause a thread within the ExecutorService to execute the Runnable.

The ThreadPoolExecutor, ThreadPoolExecutor

Java. Util. Concurrent ThreadPoolExecutor is an implementation of the ExecutorService interface. ThreadPoolExecutor uses threads from its internal pool to perform a given task (Callable or Runnable).

Construction method:

// constructor
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}Copy the code

Let’s look at the meanings and functions of several parameters

  • CorePoolSize – Number of core threads, that is, the number of threads allowed to be idle
  • MaximumPoolSize – The maximum number of threads, the capacity of this thread pool
  • KeepAliveTime – The idle lifetime of a non-core thread
  • Unit – The unit of the previous argument
  • WorkQueue – Task queue (blocking queue)
  • ThreadFacotry – Thread creation factory
  • Handler – Reject when the thread pool or task queue capacity is full

Callable&&Future

Callable may sound unfamiliar, but Runnable should be familiar; Let’s just say a Callable is a Runnable with a return value. Callable declares the following:

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(a) throws Exception;  
}Copy the code

The return value is the type of the generic parameter passed in by Callable. And that return value is retrieved by the Future.

FutureTask

FutureTask is a RunnableFuture, which implements Runnbale and Futrue

public class FutureTask<V> implements RunnableFuture<V>

public interface RunnableFuture<V> extends Runnable.Future<V> {  
    /** * Sets this Future to the result of its computation * unless it has been cancelled. */  
    void run(a);  
}Copy the code

It can also wrap Runnable and Callable, with constructors injecting dependencies.

public FutureTask(Callable<V> callable) {  
    if (callable == null)  
        throw new NullPointerException();  
    this.callable = callable;  
    this.state = NEW;       // ensure visibility of callable  
}Copy the code

Here is a simple demo to show the use of Callable and FutureTask

/** * defines a Callable task with the return type Integer */
public class CallableTask implements Callable<Integer> {
    @Override
    public Integer call(a) throws Exception {
        int hours=5;
        int amount = 0;
        while(hours>0){
            System.out.println("I'm working,rest is "+hours);
            amount++;
            hours--;
            Thread.sleep(1000);
        }
        returnamount; }}public class FutureTaskTest {

    public static void main(String args[]) throws ExecutionException {
        CallableTask worker = new CallableTask();
        FutureTask<Integer> mTasks = new FutureTask<>(worker);
        new Thread(mTasks).start();

        while(! mTasks.isDone()) {try {
                System.out.println("job has't finsished ..." + mTasks.get());
                Thread.sleep(1000);
            } catch(InterruptedException e) { e.printStackTrace(); }}int amount;
        try {
            amount = mTasks.get();
            System.out.println("Job finished commited " + amount);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch blocke.printStackTrace(); }}}Copy the code

Let’s look at the output log:

The main method starts to execute mTasks through a new thread, and mTasks will wait for the completion of the worker’s execution to get the result of its final execution.

Ok, so that’s about it for the basics.

AsyncTask properties

With some basics, let’s first look at the AsyncTask class declaration:

public abstract class AsyncTask<Params.Progress.Result> {
    private static final String LOG_TAG = "AsyncTask";
    // Get the current number of CPU cores
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE = 1;

    //ThreadFactory ThreadFactory, using the factory method newThread to get a newThread
    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()); }};// Create a chained blocking queue with a default size of 128
    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

    /** * Assuming that the phone CPU currently in use is 4 cores, a thread pool (executor) with a core capacity of 5 * a maximum capacity of 9 * non-core threads idle time of 1 second * task queue size of 128 ---------- will be created to execute concurrent tasks */
    public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

    /**
     * An {@linkExecutor} that executes tasks one at a time in serial * order. This serialization is global to a particular process. * Serial task executor declaration */
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    // Message type: send result
    private static final int MESSAGE_POST_RESULT = 0x1;
    // Message type: sending process
    private static final int MESSAGE_POST_PROGRESS = 0x2;
    // Set the default executor to serial execution
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    // Internal Handler implementation
    private static InternalHandler sHandler;

    private final WorkerRunnable<Params, Result> mWorker;
    // Get the result of mWorker execution; End functions such as mWorker
    private final FutureTask<Result> mFuture;
    // The task is suspended by default
    private volatile Status mStatus = Status.PENDING;
    // Use atomic Boolean variables to indicate whether the current task has been canceled
    private final AtomicBoolean mCancelled = new AtomicBoolean();
    // Marks whether the current task has been executed
    private final AtomicBoolean mTaskInvoked = new AtomicBoolean();

    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(a) {
                    try {
                        r.run();
                    } finally{ scheduleNext(); }}});if (mActive == null) { scheduleNext(); }}protected synchronized void scheduleNext(a) {
            if((mActive = mTasks.poll()) ! =null) { THREAD_POOL_EXECUTOR.execute(mActive); }}}/** * 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,
    }
}Copy the code

Some of the properties have been marked with comments and should be easy to understand; Let’s look at the meanings of the following statements:

SerialExecutor implementation

SerialExecutor A serially executed thread pool in which a queue is first created to store AsyncTask tasks. The execute method adds the AsyncTask task to the end of the queue and executes it immediately. After the execution, scheduleNext is called to fetch the next task from the head of the queue if the task is not empty. Delegate the task to THREAD_POOL_EXECUTOR; In this way, if multiple AsyncTasks are executed at the same time, multiple tasks added to mTasks will be executed one by one through polling to complete the serial execution function.

WorkerRunnable

What is this WorkRunnable? Look at his statement:

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

This class inherits Callable and adds a parameter; We know that the Callable parameter type is the return type of its call method. What is the meaning of Params parameter? We will see this later.

Finally, enumeration is used to define several states of the task. PENDING, RUNNING, FINISHED As you can see from the previous code, the default status is PENDING.

Now that we’ve covered most of the properties of the AsyncTask class, let’s look at how it works.

Implementation principle of AsyncTask

Going back to the sample code we mentioned at the beginning, once we have defined our AsyncTask, it is very simple to start the task with just one line of code:

new DownloadFilesTask().execute(url1, url2, url3);Copy the code

So let’s start with this line of code and see what happens.

A constructor

First, new DownloadFileTask(), which executes the constructor of DownloadFileTask, and therefore must execute the constructor of DownloadFileTask’s parent AsyncTask, that is, AsyncTask() :

    /** * Creates a new asynchronous task. This constructor must be invoked on the UI thread. */
    public AsyncTask(a) {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call(a) throws Exception {
                // Set the current task to be executed
                mTaskInvoked.set(true);
                // Sets the priority of thread execution
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                returnpostResult(result); }}; mFuture =new FutureTask<Result>(mWorker) {
            @Override
            protected void done(a) {
                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 constructor’s job is simply to initialize the mWorker and mFuture, the Callable and Future, and associate them so that the mFuture can get the result of the mWorker’s execution or stop the mWorker’s execution.

There are two methods called () and done(). In general, the call() method is executed when the mFuture is executed, and the done() method is executed when the task is finished.

So when will this mFuture be implemented? Keep reading

The mission began in earnest

new DownloadFilesTask().execute(url1, url2, url3);Copy the code

Now that we’ve finished executing the constructor, let’s look at the execute() method:

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

executeOnExecutor

    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

This should make it clear that mStatus defaults to PENDING, so when the task starts to execute, it first changes its status to RUNNING; Meanwhile, we can also see from the exception judgment that the execute method of an AsyncTask cannot be executed twice at the same time.

Next, onPreExecute(), we’re starting AsyncTask in onCreate, so it’s still on the main thread, and onPreExecute() is going to work on the main thread, so we can do some preparatory things in this method, initialization.

MWorker, as mentioned earlier, implements the Callable interface and adds a parameter attribute to which we assign the parameters passed in from executor. Exec =sDefaultExecutor=SerialExecutor, where the task is actually executed; The mFuture task will be executed as described earlier, and therefore the mWorker’s call method will be executed.

        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call(a) throws Exception {
                // Set the current task to be executed
                mTaskInvoked.set(true);
                // Sets the priority of thread execution, which is set in the background
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }Copy the code

At this point, we finally see the familiar doInBackground, which is a method we have to implement to do time-consuming operations and return results. Since the priority of Process has been set, this method will be in the background Process. In doInBackground, we can also return the current execution progress:

     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }Copy the code

We called publishProgress to publish the progress of the time-consuming task in doInBackground. As you know, this progress is sent to onProgressUpdate(), where we can easily update the UI. For example, progress bar progress updates. So how did he do it? This depends on the implementation of the publishProgress method.

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

Let’s break down the implementation of this method again:

   Return an InternalHandler object
    private static Handler getHandler(a) {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            returnsHandler; }}Copy the code
AsyncTaskResult

AsyncTaskResult is the result of AsyncTask, which is a static inner class with two properties mTask and mData.

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

Therefore, new AsyncTaskResult in publishProgress creates an AsyncTaskResult whose two properties are the current AsyncTask and the task execution progress.

The logic is clear: if the current task has not been canceled, fetch an instance of Message from the Message pool, At the same time, set msg.what=MESSAGE_POST_PROGRESS of the Message object,msg.obj as an AsyncTaskResult object, and finally execute sendToTarget method. We know that the sendXXX method works the same way. All it does is insert the Message object into MessageQueue and wait for Looper’s loop method to retrieve it. Since we start AsyncTask execution on the main thread, once we insert a message into the queue, Handler’s handleMessage method is executed. So let’s look at your implementation of InternalHandler.

    private static class InternalHandler extends Handler {
        public InternalHandler(a) {
            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; }}}Copy the code

Ha ha, that’s easy; The result is first fetched in the handleMessage and cast to AsyncTaskResult, msg.what=MESSAGE_POST_PROGRESS, The result should be executed. MTask. OnProgressUpdate (result. MData); MTask is the current AsyncTask, so the onProgressUpdate method declared in AsyncTask is executed. In this way, parameters are passed from a child thread to the UI thread, making it very easy for developers to use this for related business.

Back in mWorker’s call() method, postResult is finally executed after doInBackground is executed.

    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

This method, like the publishProgress logic, wraps result into an AsyncTaskResult object and inserts it into MessageQueue as an obJ property of the Message object. Just MSG. What = MESSAGE_POST_RESULT.

This brings us to InternalHandler’s handleMessage, this time msg.what=MESSAGE_POST_RESULT. When execution result. MTask. Finish (result. MData [0]).

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

The onPostExecute(result) method is executed when the task is not cancelled. So onPostExecute(result) is the last method that we’re going to execute, and that’s where we’re going to get the final result; And mark the task status as FINISHED.

This concludes the general usage of AsyncTask. Take a breath… If you go through it again, you’ll notice something that seems to have been forgotten. What? When mWorker’s call is executed to the end, we return the result to Handler through postResult method, which still completes the task. So what is the use of mFuture? Let’s look at another AsyncTask constructor

    public AsyncTask(a) {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call(a) throws Exception {
                // Set the current task to be executed
                mTaskInvoked.set(true); . Omit...returnpostResult(result); }}; mFuture =new FutureTask<Result>(mWorker) {
            @Override
            protected void done(a) {
                try{ postResultIfNotInvoked(get()); . Omit... }}; }private void postResultIfNotInvoked(Result result) {
        final boolean wasTaskInvoked = mTaskInvoked.get();
        if (!wasTaskInvoked) {
            postResult(result);
        }
    }Copy the code

But look at the code and see that the postResultIfNotInvoked internal if statement condition is not satisfied and does nothing, which means the DONE method of the mFuture does nothing. This code is really weird. I don’t know what it means. Or MAYBE I don’t get it. ╮ (╯ _ ╰) ╭. In this case, the mFuture seems to be useless, but it’s not. To return to our original question of how to terminate a time-consuming task in progress, the question is simply how to stop a thread. Stopping a running thread via an external method should be a delicate matter, and this is where mFuture comes in.

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

The implementation of AsyncTask Cancel is done by mFuture.

In this way, we know how to implement several common methods when using AsyncTask.

Play AsyncTask

Through the above content, we have understood the implementation mechanism of AsyncTask. Here is a demo to feel the mystery of AsyncTask.

    class MyAsyncTask extends AsyncTask<String.Integer.String> {

        @Override
        protected String doInBackground(String... params) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            publishProgress();
            return params[0];


        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss", Locale.CHINA);
            Log.e(TAG, s + "execute finish at " + df.format(newDate())); }}Copy the code

An AsyncTask task is defined here. The implementation logic is very simple. The parameters passed in during the execution of the task are returned every second.

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_at);
        result = (TextView) findViewById(R.id.result);
        findViewById(R.id.execute).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TAG, "onClick: " + Runtime.getRuntime().availableProcessors());
                for (int i = 0; i < 127; i++) {
                    new MyAsyncTask().execute("Task#"+ i); }}}); }Copy the code

We execute 127 MyAsyncTask tasks through a for loop. Look at the log:

Obviously, one per second, the default is serial execution. As mentioned earlier, an ArrayDeque is a self-growing queue. Therefore, by default, an infinite number of AsyncTask tasks can be created.

I don’t want to wait. I want to do it in parallel. That’s not hard.

                for (int i = 0; i < 127; i++) {
                   new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "AsyncTask#" + i);
                }Copy the code

THREAD_POOL_EXECUTOR instead of the default sDefaultExecutor asynctask.thread_pool_executor.

You can see that five threads are executing simultaneously every second, so 127 threads can execute in 26 seconds, which is almost five times faster than serial execution. Why five times? This comes back to the AsyncTask property definition. CORE_POOL_SIZE = CPU_COUNT + 1 when defining thread pools for parallel execution; The number of thread pool cores is CPU cores +1, so the maximum number of threads on my current 4-core phone is 5.

In serial execution, thanks to ArrayDeque, you can execute many tasks in sequence. What about parallel execution? To conclude, a mobile phone with four CPU cores can perform up to 137 tasks at a time. This also makes sense. The maximum capacity of the thread pool is 9, and the blocking queue is 128, which means that in parallel, 137 tasks can be blocked, but no more.

Modify the previous code:

                for (int i = 0; i < 138; i++) {
                   new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "AsyncTask#" + i);
                }Copy the code

When the program is run, an exception is thrown:

java.util.concurrent.RejectedExecutionException: Task android.os.AsyncTask$3@1429681 rejected from java.util.concurrent.ThreadPoolExecutor@bfb9326[Running, pool size = 9, active threads = 9, queued tasks = 128, completed tasks = 0]Copy the code
public interface RejectedExecutionHandler {

    /** * Method that may be invoked by a {@link ThreadPoolExecutor} when * {@link ThreadPoolExecutor#execute execute} cannot accept a * task. This may occur when no more threads or queue slots are * available because their bounds would be  exceeded, or upon * shutdown of the Executor. * * 

In the absence of other alternatives, the method may throw * an unchecked {@link RejectedExecutionException}, which will be * propagated to the caller of {@code execute}. * * @param r the runnable task requested to be executed * @param executor the executor attempting to execute this task * @throws RejectedExecutionException if there is no remedy * /

void rejectedExecution(Runnable r, ThreadPoolExecutor executor); }Copy the code

This time, obviously, the exception was thrown because we had more tasks to perform than the thread pool could handle.

The last

The implementation of AsyncTask is simple enough to say, that is, it encapsulates Handler+Thread, but there are a lot of details to be studied. The idea of writing code is worth learning.

The code of AsyncTask is slightly different in different SDK versions. The above analysis is based on Android 6.0, also known as Android SDK 23.