码迷,mamicode.com
首页 > 其他好文 > 详细

AsyncTask异步任务 源码解读

时间:2016-05-18 10:51:34      阅读:245      评论:0      收藏:0      [点我收藏+]

标签:

之前我们介绍了Handler的一些基本用法,也解读了Handler的源码。通过Handler我们可以简便的切换到主线程进行UI操作。而AsyncTask的出现使我们不用去关心线程管理和切换的一些细节,我们可以更轻松的去操作UI。

基本概念

AsyncTask异步任务的作用

AsyncTask,见名之意,异步任务。允许我们在后台做一些耗时操作,然后切换到主线程更新,而且这一过程变得非常简便。一提到异步任务,我们的第一反应就是多线程。假如我们现在需要去下载一张图片,然后在界面上显示,如果没有AsyncTask,我们的异步操作可能就会这么写:

  • 普通写法
   new Thread(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap=loadBitmap(url);//loadBitmap是封装好的工具类,用来下载图片,返回bitmap
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mImageView.setImageBitmap(bitmap);
                    }
                });
            }
        }).start();

那么,AsyncTask怎么写呢?

  • AsyncTask写法
//AsyncTask下载并展示图片
 new MyAsyncTask(mImageView).execute(url);

 //MyAsyncTask的实现
public class MyAsyncTask  extends AsyncTask<String,Void,Bitmap>{
    private ImageView mImageView;
    private ProgressDialog mProgressDialog;
    public MyAsyncTask(ImageView imageView){
        this.mImageView=imageView;

    }
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mProgressDialog=new ProgressDialog(mImageView.getContext());
        mProgressDialog.setMessage("正在下载");
        mProgressDialog.show();

    }

    @Override
    protected Bitmap doInBackground(String... params) {
          retrun loadBitmap(params[0])
      }

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
        if(bitmap!=null){
            mImageView.setImageBitmap(bitmap);
        }

    }
}

可以看出,AsyncTask的结构要清晰很多。有准备操作的方法onPreExecute,有专门执行后台操作的方法doInBackground,也有更新UI的方法onPostExecute,甚至还有个更新进度的方法onProgressUpdate,每个方法各司其职。
你可能会想,除了结构清晰,也没看出有其他什么优势啊。那么,我们现在来想象一个场景——列表滑动。假如我们在ListView中去请求图片,直接new线程显然不太合适,快速滑动会开启大量的线程,线程阻塞不说,还会消耗大量系统资源。而AsyncTask由于内部管理着一个线程池,就可以解决这些问题。

AsyncTask的工作流程

AsyncTask不仅使用起来非常简便,此外,工作流程也非常清晰。

  1. doInBackground编写耗时任务。
  2. 在主线程提交任务到线程池
  3. 线程池进行处理任务
  4. 处理完切换到主线程并返回结果。

AsyncTask异步任务疑问

在了解过AsyncTask的一些用法后,我们不难会产生一些疑问。

  • AsyncTask的线程切换是怎么完成的?
  • AsyncTask的execute方法的可变参数怎么用?
  • execute方法为什么只能调用一次?
  • execute方法为什么只能在主线程调用?
  • AsyncTask是串行执行还是并行执行?

让我们带着疑问,去源码里探索吧。

初识API

ThreadPoolExecutor(线程池执行者)

一看名字就知道,线程池,JAVA线程管理的一个api。通过下面的构造方法就可以建立一个线程池。

    /**
     * @param corePoolSize 核心线程数,即使空闲也一直存在,除非给核心线程也设了超时时间。
     * @param maximumPoolSize 线程池的最大线程数
     * @param keepAliveTime 除核心线程外的其他线程等待超时时间,超时就会杀死。
     * @param unit 时间单位
     * @param workQueue 工作队列,多余的任务就会放在这个队列中,等待被执行。
     * @param threadFactory 线程工厂,用来创建线程
     * @param handler 当工作队列满后,再添加任务时的处理策略,处理策略有如下几种,DiscardOldestPolicy:挤掉最旧的任务。DiscardPolicy:废弃当前被添加的任务,CallerRunsPolicy:直接在添加任务的那个线程执行,AbortPolicy:直接抛异常。默认的处理策略时AbortPolicy。
     */
  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

我们常用的Executors.newFixedThreadPool(int nThreads)等创建线程池的方法,其内部也是调用了这个构造方法。

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

Queue和Deque(队列和双端队列)

Queue队列,继承自集合(Collection),通常遵循着先进先出原则(FIFO)(除了优先级队列和后进先出队列)对应的api如下。

操作 抛出异常(已满或已空) 返回空值(已满或已空)
添加元素 add(e) offer(e)
取出一个元素 remove() poll()
查看一个元素 element() peek()

我们一般采用offer,poll,peek方法来操作元素,以免抛出异常。

Deque双端队列,继承自Queue,支持双端移除和插入。因继承自Queue,所以可以直接当作Queue队列来用,此时遵守FIFO原则,当然,可以显示指明操作队头还是队尾offerFirst(e),pollFirst(),offerLast(e),pollLast()..
Deque的api对照如下

Queue方法 同等的Deque方法
add(e) addLast(e)
remove() removeFirst()
element() getFirst()
offer(e) offerLast(e)
poll() pollFirst()
peek() peekFirst()

Runable,Callable,Future,FutureTask四者的关系。

  • Runable: 一个接口,常用于执行异步任务,有一个抽象方法void run(),该方法没有返回值。
public interface Runnable {
    public void run();
}
  • Callable: 一个接口,常用于执行异步任务,有一个抽象方法V call(),该方法含有返回值。
public interface Callable<V> {
    V call() throws Exception;
}
  • Future: Runable和Callable就像脱缰的野马,提交给线程就没法进行管理了。而Future则是用来管理他们的,可以取消任务,获取结果等。
public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
  • FutureTask是Future的一个实现类。用来包装Runable和Callable,使其可控制。常规用法如下。
   //创建一个Callable对象,在实现方法中写耗时操作
   Callable callable= new Callable<Bitmap>() {
            @Override
            public Bitmap call() throws Exception {
                return loadBitmap(url);
            }
        };
    //使用FutureTask包装Callable,使其可控制
    FutureTask<Bitmap> futureTask=new FutureTask<Bitmap>(callable);

    //提交到线程中 
     new Thread(futureTask).start();

     //阻塞直到获取结果,如果不想阻塞,就重写done方法,在里面get()
     Bitmap bitmap= futureTask.get();

工作原理

编写任务(重写doInBackground方法)

这一步比较简单,就是在doInBackground里面写一些耗时操作,比如网络访问。这里不作演示。

提交任务到线程池(调用execute方法)

AsyncTask的使用比较简便,一般都是以new MyAsyncTask(..).execute(url)的形式调用。那么,execute到底做了什么呢?在介绍之前,我们先来看一下AsyncTask的构造方法。

AsyncTask的构造方法

    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);//设置任务已被调用的标识
                //...
                //省略了部分代码
                Result result = doInBackground(mParams);//耗时操作
                //...
                //省略了部分代码
                return postResult(result);
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                   //...
                  //省略了部分代码
            }
        };
    }

看完上面的代码,可能有点手足无措。不要慌张,慢慢来。
先来看看mWorker,里面的东西是不是非常眼熟,跟Callable的写法简直太像了。事实上,的确继承自Callable。而doInBackground只是Callable实现方法中的一部分。执行完过后调用了postResult来传递数据,postResult做了什么暂且不做研究,后面再做介绍。WorkerRunnable的代码如下,仅仅是定义了一个变量用于保存参数。

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

再来看看mFuture,是不是也非常眼熟,包装了一下mWorker使其可控制。
看完上面两段源码后,有没有发现跟我上面举的FutureTask的例子有点像,那么,接下来要干嘛相信你已经很清楚了,没错,就是提交到线程或者线程池中去执行这段代码。只要被执行就能获得结果完成使命了。
真是万事俱备,只欠线程池啊。

调用execute方法

现在回过来看一下execute方法,看它到底做了什么事。

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

可以看到内部经过了一次包装,且传入了一个默认的Executor,我们来看一下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;
    }

我们可以发现在这个方法中直接调用了onPreExecute,而onPreExecute是用来干嘛的呢?做一些提交之前的操作,比如显示一个进度框等UI操作,这也就暗示了,如果要在onPreExecute中做UI操作,则必须在主线程中调用execute方法。此外我们也能隐隐约约知道为什么execute只能提交一次,因为,一旦提交过任务,就会将状态设置为Status.RUNNING,完成后就变为Status.FINISHED,按照源码逻辑可知,再也回不到Status.PENDING状态,所以不能再提交第二次,否则会抛异常。那么,为什么这么设计呢?是因为FutureTask的缘故,FutureTask的计算是不能被重新调用的(除非调用的是runAndReset())。
Status是一个枚举类,如下。

    public enum Status {
        PENDING,//还未调用
        RUNNING,//调用中
        FINISHED,//完成
    }

继续阅读源码,发现了这一行exec.execute(mFuture);莫非就在这里提交给了线程池去执行?满怀期待的找到sDefaultExecutor的源码实现一看:

    private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {

            //将Runable对象添加到双端队列里
            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);
            }
        }
    }

What?这个线程池不是用来执行任务的?心中一万只草泥马奔腾而过。没办法,只能继续阅读源码。发现了ArrayDeque双端队列,一看到队列我们的第一反应就是要排队。可是排队干嘛呢?干嘛不直接提交到线程池里面去执行。从源码可以看出使用了ArrayDeque双端队列来存放Runnable对象,你可能会疑问,不是FutureTask吗,怎么变成Runnable了,那是因为FutureTask也实现了Runnable接口,莫急,接下来就是排队的核心代码,为了使看起来更直观,修改成如下形式:

new Runnable() {
    public void run() {

          try {
                r.run();
          } finally {

               //取出一个任务添加到线程池里执行。
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
          }
     }
 }

从上面的源码可以看出,使用了Runnable包装了原来的FutureTaskrun方法,巧妙的使用finally来保证任务的串行执行。
看到这里恍然大悟SerialExecutor原来是一个用来排队的线程池。THREAD_POOL_EXECUTOR才是我们一直在苦苦寻找的用于执行任务的线程池。exec.execute(mFuture)先把任务保存到排队线程池的队列中,然后串行提交到执行任务的线程池。也就是说,每执行完一个任务后,才会从队列中取出下一个任务到线程池中。
看到这里终于可以松一口气了,算是看到了提交到线程池的代码,第二步任务终于完成了。

线程池处理任务( THREAD_POOL_EXECUTOR.execute(..);

线程池的内部工作逻辑这里就不深究了,我们只需知道目前为止我们已经成功将任务提交到线程池中,只需等待处理,便可获得结果。我们来看看线程池本尊是怎么实现的。源码如下。

//线程配置
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;
  //线程工厂
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());
        }
    };

  //工作队列
  private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

    /**
     *线程池的构造方法
     */
    public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

线程池的实现是一些很常规的代码,没有什么可介绍的,唯一需要关心的问题就是线程池开多大合适。核心线程数为N+1,最大线程数为2N+1。

处理完毕,返回结果(回调onPostExecute

经过线程执行完毕,终于可以获取结果呢。还记得AsyncTask的构造方法吗?不记得也不要紧,我们再看一遍。

    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);//设置任务已被调用
                //...
                //省略了部分代码
                Result result = doInBackground(mParams);//耗时操作
                //...
                //省略了部分代码
                return postResult(result);
            }
        };
                //...
                //省略了部分代码
    }

由源码可知,在处理完毕后会调用postResult,postResult的源码如下

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

AsyncTaskResult是一个包装类,里面保存了结果数据和AsyncTask对象。

   private static class AsyncTaskResult<Data> {
        final AsyncTask mTask;
        final Data[] mData;

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

此外,似乎又看到了熟悉的身影MessageHandler。看到这里终于明白了,内部通过Handler来进行切换线程,然后更新UI。如果我没猜错,在Handler的handleMessage方法内,一定直接或者间接的调用了onPostExecute
为了验证我的猜想是否正确,顺藤摸瓜找到了Handler的实现。

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

    //Handler的实现
   private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT://这个是结果
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS://这个是进度
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

找到我们刚刚发出的消息类型MESSAGE_POST_RESULT。发现没有直接调用onPostExecute而是调用了AsyncTask的finish方法,继续追溯源码。

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

看完源码才恍然大悟。原来有两种类型啊,一种是正常执行完毕的,回调onPostExecute,还有一种是被取消了,回调onCancelled。那么问题来了,怎么取消一个任务?
只需调用AsyncTask的cancel(boolean)方法即可。cancel会调用FutureTask的cancel方法去中断任务。

     //取消一个任务
    public final boolean cancel(boolean mayInterruptIfRunning) {
        mCancelled.set(true);//设置一个取消标识
        return mFuture.cancel(mayInterruptIfRunning);//取消一个任务
    }

    //获取取消的状态
   public final boolean isCancelled() {
        return mCancelled.get();//获取取消标识
    }

从源码可以看出,取消任务后是不会再走onPostExecute的,只走onCancelled方法。
看到这里。算是把AsyncTask的源码给看的差不多了。

最后

  • 怎么更新进度?更新进度在哪个线程进行?
    更新进度的源码如下。使用时,在doInBackground中进行调用。然后由Handler传递到主线程中,重写onProgressUpdate接受即可。
protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }


 private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                //省略了部分源码
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
  • AsyncTask的execute方法的可变参数怎么用?
    从源码来看,execute的可变参数全部传入进Result result = doInBackground(mParams);,也就是说,自己可以结合场景巧妙使用。可以传多个url,批量下载小文件等。

  • AsyncTask是串行执行还是并行执行?
    从以上源码分析(Android5.0)得出,是串行执行。但是官方api文档指出。Android1.6之前和Android3.0之后是串行执行。在这两个版本之间采用的是并行执行。于是找了份2.3的源码,果真如此,可以发现没有了排队线程池的踪影。源码如下:

//execute的源码如下
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
         if (mStatus != Status.PENDING) {
              //..
              //省略了部分源码
        }

        mStatus = Status.RUNNING;

        onPreExecute();

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

        return this;
  }

//sExecutor的实现如下,可以看出直接就是一个任务线程池,没有用于排队的线程池。
private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
        MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
  • Android3.0后AsyncTask怎么并行执行?
    其实很简单,想个办法跳过那个排队线程池即可。可以调用executeOnExecutor(Executor exec, Params... params),如上所示,直接传入一个线程池进行执行。

总结

经过解读了AsyncTask的源码,从侧面可以看出,Handler不可动摇的地位,毋容置疑。
如果没有看过Handler源码的,可以阅读这篇 Handler消息机制 源码解读加深理解。

AsyncTask异步任务 源码解读

标签:

原文地址:http://blog.csdn.net/maplejaw_/article/details/51441312

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!