深入浅出AsyncTask的工作原理

前言

AsyncTask类一般是android开发人员接触的最早的异步处理方法,虽然现在流行的有很多新的异步任务类,如RxJava等,但是对于AsyncTask的底层实现原理还是有必要了解学习的,对于我们自己理解其他框架或者自己设计框架都有很大的帮助。它使用handler和线程池的方式打到异步操作,将结果返回到主线程处理。

数据结构

在讲解AsyncTask的原理前,我们需要先来复习或者学习下其用到的数据结构。这些是我们必须要了解的数据结构。

An object that executes submitted Runnable tasks. This interface provides a way of decoupling task submission from the mechanics of how each task will be run, including details of thread use, scheduling, etc. An Executor is normally used instead of explicitly creating threads. For example, rather than invoking new Thread(new(RunnableTask())).start() for each of a set of tasks, you might use:
//这是一个执行提交的Runnable类型的任务的接口,
//该接口提供了一种将任务提交与如何运行每个任务的机制分离的方法,
//包括线程使用、调度细节等。通常使用执行程序而不是显式地创建线程。
//即不需要为每个任务创建线程。
 Executor executor = anExecutor;
 executor.execute(new RunnableTask1());
 executor.execute(new RunnableTask2());
 //许多Executor实现对如何以及何时调度任务施加了某种限制。
 //即扩展类可以添加自己的调度方案,如顺序执行或者线程池并行执行。
 Many Executor implementations impose some sort of limitation on how and when tasks are scheduled. The executor below serializes the submission of tasks to a second executor, illustrating a composite executor. 
  • BlockingQueue
    阻塞队列,包名java.util.concurrent.BlockingQueue,和Excutor一样都是java jdk中并发相关的类。其大意是一个装载了Runnable任务的阻塞队列,阻塞队列即如果队列满,再次插入任务会阻塞住,直到有数据被取走。反之,如果队列空,再取任务时也会阻塞,直到有新任务插入。 当然这个BlockingQueue为泛型,<>其数据为插入的任何类型。在本例中是Runnable。如果还需要深入了解其原理请移步我的另一篇博客:BlockingQueue阻塞队列原理解析,还对其他实现类有描述。

  • AtomicInteger FutureTask
    java的JDK类中的原子类型的Integer,完整路径java.util.concurrent.atomic.AtomicInteger, atomic包代表的是原子类,其提供了incrementAndGet等函数,保证对象以原子方式进行改变。
    非原子性的解释, 比如变量 int a = 0;然后,
    a++; 这个a++其实就不是原子操作,比如其经过了获取值、值+1、赋值等三个操作(大概是,有待确认),如果在多线程中就会发生被其他线程更改的问题。

  • ArrayDeque Callable
    双端队列,区别于普通队列的FIFO特性,即先进先出,尾部进,头部出。双端队列是可以既在头部插入、尾部插入、头部取出、尾部取出的数据结构。

  • WorkerRunnable extends Callable
    本文的实现类是WorkerRunnable , Callable是同Runnable类似的数据结构。其中Runnable是一个没有参数和返回值的异步方法。那Callable那,既然是两个数据结构,这个肯定有不同,那就是其有返回值,而且是一个参数化的泛型类型,可以将要返回的数据类型传入,这里是Result。Runnable有一个run方法, Callable只有一个Call方法。

 public interface Callable<V>{
 	V call() throws Exception;
 }
  • FutureTask
    Future保存异步计算的结果,可以启动一个任务,将Future对象交给某个线程。Future对象的所有者在结果计算好之后就可以获得它。

从其类注释可以看出 其继承了Runnable,即可以提交到Executor中执行,并且其泛型结构体中可以传入Callable参数, 其还提供了get方法,以获取返回值,get方法会阻塞,知道执行成功。

A cancellable asynchronous computation. This class provides a base implementation of Future, with methods to start and cancel a computation, query to see if the computation is complete, and retrieve the result of the computation. The result can only be retrieved when the computation has completed; **the get methods will block if the computation has not yet completed**. 
//其get方法可能被阻塞,如果计算还未完成的话。

//FutureTask可以包装一个Callable或者Runnable类型。
A FutureTask can be used to wrap a Callable or Runnable object. Because FutureTask implements Runnable, a FutureTask can be submitted to an Executor for execution.

 public class FutureTask<V>
extends Object
implements RunnableFuture<V>

FutureTask(Callable<V> callable)
Creates a FutureTask that will, upon running, execute the given Callable.

 	get()
Waits if necessary for the computation to complete, and then retrieves its result.
  • InternalHandler extends Handler
    这个就相当简单了,Handler肯定大家都用过,这里是拿到了主线程的Looper的Handler将结果发送主线程使用。如果需要深入了解,请查阅我的另一篇博文:Handler通信机制源码解读

源码解读

通过上面的数据结构解读,其实基本上源码就清晰了。
由于AsyncTask是一个抽象类,所以使用时需要实现这个类,并实现抽象方法doInBackground, 其他的三个方法可以根据需要选择是否重新实现。其中
doInBackground是在线程池中执行的,其他三个都是在主线程执行的。

public abstract class AsyncTask<Params, Progress, Result> {
    @WorkerThread
    protected abstract Result doInBackground(Params... params);
    
    @MainThread
    protected void onPreExecute() {}
    
    protected void onPostExecute(Result result) {
    }
    
    @MainThread
    protected void onProgressUpdate(Progress... values) {
    }

使用AsyncTask还有几个注意点:

  1. AsyncTask类必须在主线程加载。
  2. AsyncTask类对象必须在主线程创建。
  3. execute方法必须在UI线程调用。
  4. 一个AsyncTask只能执行一次,即一次execute方法。
  5. execute方法默认是串行执行的,如果需要并行执行需要调用executeOnExecutor方法。(曾经由于不知道这个特性犯过错误,设置网络请求超时3s,超时就不再需要这个请求,但是有时会添加多个任务时,如果那个任务7s才执行完毕,那么后边的任务也是要等待其执行完毕才开始执行的。)

然后看源码,首先,类中分别定义了两个Executor, 一个是线程池,可以并行执行,其大小参数可以在源码的变量中查看;一个就是串行的Executor。

 * An {@link Executor} that can be used to execute tasks in parallel.可并行执行任务的线程池
 */
public static final Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
                    /**
 * 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 volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private static class SerialExecutor implements Executor {
//双端队列,其中填充的是要执行的任务
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;

    public synchronized void execute(final Runnable r) {
    /**
    * 	offer(E e) 在双端队列的队尾插入一个元素,这里即Runnable,且当前Runnable在执行r.run后,即调用scheduleNext调度将下一个任务取出并添加到执行器执行。
Inserts the specified element at the end of this deque.
    */
        mTasks.offer(new Runnable() {
            public void run() {
                try {
                    r.run();
                } finally {
                    scheduleNext();
                }
            }
        });
        if (mActive == null) {
            scheduleNext();
        }
    }

    protected synchronized void scheduleNext() {
    //Retrieves and removes the head of the queue represented by this deque (in other words, the first element of this deque), or returns null if this deque is empty.
    //poll是获取队列头的任务并从队列头删除这个任务
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

之后我们查看AsyncTask的构造函数。
首先其创建了一个异步任务,并且要求在主线程创建。为何一定要在主线程创建呢,原因就在WorkerRunnable的返回值,即postResult(result),下面同样贴出了postResult的源码,其中发送消息的handler即InternalHandler,而InternalHandler初始化又是拿到主线程looper即getMainLooper创建的主线程的handler,所以由于初始化AsyncTask会通过getHandler创建一个Handler,并讲要执行任务的结果通过InternalHandler传到主线程执行,这个handler对象必须在主线程创建,而且由于InternalHandler是一个静态的类,而静态成员会在加载类的时候进行初始化,所以,AsyncTask的类必须在主线程中加载。

   /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.创建一个异步任务,其结构体必须在UI线程创建
     */
    public AsyncTask() {
        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);
            }
        };

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

然后是真正的执行方法executeOnExecutor,其参数一为Executor,即执行器是由外部传入的,如果调用AsyncTask调用Executor()方法不传入执行器,则会走默认的执行器sDefaultExecutor,即SerialExecutor 。而且会根据当前状态判断,如果当前已经在Running状态,则会抛出异常Cannot execute task:"" the task is already running. 即第4条注意点。

 @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到主线程,以便做异步任务前的准备工作
        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

测试程序

话说的太多的,下面上实例测试程序。
这里仿照书上的例子写的。MyAsyncTask作为AsyncTask的实现类,实现其doInBackground方法,任务是sleep 1s然后返回值是当前任务名,并重写onPostExecute方法,打印执行结束的时间。

private class MyAsynctask extends AsyncTask<String, Integer, String> {

private String name;

public myAsynctask(String name) {
    this.name = name;
}

@Override
protected String doInBackground(String... strings) {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return this.name;
}

@Override
protected void onPostExecute(String s) {
    super.onPostExecute(s);
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
    Log.e(TAG, s + " onPostExecute: time=" + simpleDateFormat.format(new Date()));
}

}
执行代码在MainActivity的onCreate方法执行,这里调用execute方法,参数为一个空的String。根据上边的分析,应该是串行执行的。(当前AsyncTask的历史版本不做考虑,只在新源码测试。)
之后我们调用并行执行的方法executoOnExecutor并传入AsyncTask内部的线程池。

        //串行执行
        new myAsynctask("task1").execute("");
        new myAsynctask("task2").execute("");
        new myAsynctask("task3").execute("");
        new myAsynctask("task4").execute("");
        new myAsynctask("task5").execute("");
        //并行执行
        //new myAsynctask("task6").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
        //new myAsynctask("task7").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
        //new myAsynctask("task8").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
        //new myAsynctask("task9").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
        //new myAsynctask("task10").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");

串行执行结果如下,可以看到

03-07 05:57:58.547 7099-7099/com.example.demo E/MainActivity: task1 onPostExecute: time=2020-03-07 05:57:58:547
03-07 05:57:59.506 7099-7099/com.example.demo E/MainActivity: task2 onPostExecute: time=2020-03-07 05:57:59:506
03-07 05:58:00.507 7099-7099/com.example.demo E/MainActivity: task3 onPostExecute: time=2020-03-07 05:58:00:507
03-07 05:58:01.508 7099-7099/com.example.demo E/MainActivity: task4 onPostExecute: time=2020-03-07 05:58:01:508
03-07 05:58:02.509 7099-7099/com.example.demo E/MainActivity: task5 onPostExecute: time=2020-03-07 05:58:02:509

并行执行结果如下

03-07 06:00:42.716 7352-7352/com.example.demo E/MainActivity: task8 onPostExecute: time=2020-03-07 06:00:42:716
03-07 06:00:42.719 7352-7352/com.example.demo E/MainActivity: task6 onPostExecute: time=2020-03-07 06:00:42:719
03-07 06:00:42.720 7352-7352/com.example.demo E/MainActivity: task7 onPostExecute: time=2020-03-07 06:00:42:720
03-07 06:00:43.717 7352-7352/com.example.demo E/MainActivity: task9 onPostExecute: time=2020-03-07 06:00:43:717
03-07 06:00:43.720 7352-7352/com.example.demo E/MainActivity: task10 onPostExecute: time=2020-03-07 06:00:43:720

咦?什么鬼,,,task1 2 3 4 5确实是串行执行的,但是task 6 7 8 9 10却是6 7 8并行,9 10 再并行。这是什么原因呢?看源码,打开刚才的demo程序,在AsyncTask中点进去查看当前demo的源码。这里主要就是要讲线程池了。

 // We keep only a single pool thread around all the time.
 //我们仅维持一个线程池,通过这个static块实现
    // We let the pool grow to a fairly large number of threads if necessary,
    //如果必要的话,我们允许线程池中的线程数量增长到一个相当大的数字
    // but let them time out quickly. In the unlikely case that we run out of threads,
    //
    // we fall back to a simple unbounded-queue executor.
    // This combination ensures that:
    // 1. We normally keep few threads (1) around.
    //我们一般只维持较少的线程
    // 2. We queue only after launching a significantly larger, but still bounded, set of threads.
    //我们的队列会增大,但是是有限度的增加线程数
    // 3. We keep the total number of threads bounded, but still allow an unbounded set
    //    of tasks to be queued.

       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;
         /**
     * An {@link Executor} that can be used to execute tasks in parallel.
     */
    public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

通过查看上述源码,可以知道这主要是对线程池的定义。
CPU_COUNT 代表当前环境的cpu核心数, 这里使用的模拟器,cpu参数为2
CORE_POOL_SIZE 代表核心线程数,计算后是3
KEEP_ALIVE 代表保持线程时间1s
MAXIMUM_POOL_SIZE 代表最大线程数,即任务多时,扩展到的最大线程数这里为5.

由于其他部分源码是查看的android 6.1的源码,但是由于本人的模拟器是在android5.1创建的,即AsyncTask的关于线程池的代码均应看5.1的源码的解释和定义,而cpu核心数也跟具体的模拟器或者真机相关。 由于我这边的电脑核心数是2, 故程序中线程数维持的为3个,这也就解释了刚才的执行结果。
当然随着Android和手机硬件技术的发展,目前的新版本的AsyncTask都已经改变了对这里的定义,如Android API 29的AsyncTask关于线程池的变量定义,都已经发生了很大变化,不再与cpu相关。
private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 20;
private static final int BACKUP_POOL_SIZE = 5;
private static final int KEEP_ALIVE_SECONDS = 3;
所以需要根据具体的执行Android系统和硬件具体分析。有兴趣的童鞋可以在不同平台的模拟器或者真机上运行查看效果。

总结

虽然目前已经出现了很多新的可以处理异步任务的框架,但是AsyncTask在一般较小的异步项目中还是经常适应的,而且其涉及的Android技术及数据结构技术还是值得我们学习的,所有的技术框架都是在前人的基础上根据对应的优缺点进行扩展。
如果对你有帮助,麻烦点个赞吧。 如有问题,可以回复沟通。

发布了19 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/caizehui/article/details/104712045