AsyncTask使用及源码分析

Posted android的那点事

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AsyncTask使用及源码分析相关的知识,希望对你有一定的参考价值。

AsyncTask其实干嘛用的,大家都明白,这里我就不多说了,今天主要是分析它的源码,然后了解我们实际使用过程会有哪些坑.

先简单说一下AsyncTask如何使用的.

AsyncTask使用
  • 1.先写一个类继承AsyncTask,AsyncTask这里有三个泛型参数,分别是"执行任务输入的参数","后台执行任务的执行进度","后台执行最终结果参数";不是每个参数都要使用,不使用时用java.lang.Void类型代替。

    private class MyAsyncTask extends AsyncTask<String,Integer ,String>{
    ....}
  • 2.调用execute方法开始执行任务,execute方法输入执行任务输入参数

           MyAsyncTask mTask = new MyAsyncTask();  
           mTask.execute("http://www.jianshu.com");
  • 3.下面分析AsyncTask内部各个方法的使用

    private class MyAsyncTask extends AsyncTask<String,Integer ,String>{
    //用于执行后台任务前的一些UI操作,比如提示用户开始下载了,这个是一个主线程,可以直接更新UI
    @Override
    protected void onPreExecute() {
       super.onPreExecute();
    }
    //执行后台耗时任务,是在子线程进行,这里千万不能更新UI
    @Override
    protected String doInBackground(String... params) {
       //publishProgress(50);这个就是更新后台任务执行的百分比,调用这个方法,最后是调用了onProgressUpdate
       //用于更新后台任务进度信息
       return null;
    }
    //用于更新进度信息,这个是一个主线程,可以直接更新UI,比如提示用户下载了百分之多少
    @Override
    protected void onProgressUpdate(Integer... values) {
       super.onProgressUpdate(values);
    }
    //执行完后台任务后更新UI,是在主线程,可以直接更新UI显示结果
    @Override
    protected void onPostExecute(String s) {
       super.onPostExecute(s);
    }
    //取消执行任务时更新UI,是在主线程,可以直接更新UI
    @Override
    protected void onCancelled() {
       super.onCancelled();
    }
    }

1.execute(Params... params),执行一个异步任务,需要我们在代码中调用此方法,触发异步任务的执行。

2.onPreExecute(),在execute(Params... params)被调用后立即执行,用于执行后台任务前的一些UI操作,比如提示用户开始下载了,这个是一个主线程,可以直接更新UI

3.doInBackground(Params... params),在onPreExecute()完成后立即执行,用于执行较为费时的操作,此方法将接收输入参数和返回计算结果。在执行过程中可以调用publishProgress(Progress... values)来更新进度信息。这里千万不能更新UI

4.onProgressUpdate(Progress... values),在调用publishProgress(Progress... values)时,此方法被执行,直接将进度信息更新到UI组件上。

5.onPostExecute(Result result),当后台操作结束时,此方法将会被调用,计算结果将做为参数传递到此方法中,直接将结果显示到UI组件上。

6.onCancelled取消执行任务时更新UI,是在主线程,可以直接更新UI.

接下来我们从源码上分析,它是如何实现的,在使用过程中有什么缺点,又是如何优化的.

AsyncTask源码分析

先看一下AsyncTask的构造方法

 public AsyncTask() {
 //这个是实现Callable接口
   mWorker = new WorkerRunnable<Params, Result>() {
       public Result call() throws Exception {
           mTaskInvoked.set(true);
           Result result = null;
           try {
               Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
               //noinspection unchecked
               result = doInBackground(mParams);
               Binder.flushPendingCommands();
           } catch (Throwable tr) {
               mCancelled.set(true);
               throw tr;
           } finally {
               postResult(result);
           }
           return result;
       }
   };
 //FutureTask是实现Futrure接口,java异步任务方法
   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);
           }
       }
   };
}

看他的构造方法,一个是实现Callable接口的WorkerRunnable,另一个FutureTask,其实了解java的多线程机制,异步任务应该就很好明白,这个我简单讲一下,在WorkerRunnable类中call方法里面会执行doInBackground(mParams),就是这是我们前面提到的执行耗时任务的方法; FutureTask类中done方法是在耗时任务执行完成就会回调,回调调用了 postResultIfNotInvoked(get());get()方法就是返回执行结果的,这个方法一个阻塞方法,当耗时任务还没有执行完时,它不会返回结果,而阻塞的,只有当执行完成后,才会返回执行结果.postResultIfNotInvoked(get())这个方法后面再我讲,我先讲一下这个任务是如何开始执行的;

当我们调用execute(Params... params),开始执行一个异步任务;
先看一下源码:

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

这个只是调用了executeOnExecutor这个方法,这个方法有二个参数,第二个参数params是执行异步任务参数,这个不多讲,应该明白,是第一个sDefaultExecutor是一个线程池;

 private static volatile Executor sDefaultExecutor=SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
public static final Executor THREAD_POOL_EXECUTOR;
//这个是静态代码块,AsyncTask初始化时就先执行这一块代码
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;
}

ThreadPoolExecutor是线程池,这里先简单讲解一下这个类;

  • corePoolSize:线程池的核心线程数,默认情况下,核心线程数会一直在线程池中存活,即使它们处理闲置状态。如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会执行超时策略,这个时间间隔由keepAliveTime所指定,当等待时间超过keepAliveTime所指定的时长后,核心线程就会被终止。

  • maximumPoolSize:线程池所能容纳的最大线程数量,当活动线程数到达这个数值后,后续的新任务将会被阻塞。

  • keepAliveTime:非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程。

  • unit:用于指定keepAliveTime参数的时间单位,这是一个枚举,常用的有TimeUnit.MILLISECONDS(毫秒),TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分钟)等。

  • workQueue:线程池中的任务队列,通过线程池的execute方法提交Runnable对象会存储在这个队列中。

  • threadFactory:线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法:Thread newThread(Runnable r)。
    除了上面的参数外还有个不常用的参数,RejectExecutionHandler,这个参数表示当ThreadPoolExecutor已经关闭或者ThreadPoolExecutor已经饱和时(达到了最大线程池大小而且工作队列已经满),execute方法将会调用Handler的rejectExecution方法来通知调用者,默认情况 下是抛出一个RejectExecutionException异常。了解完相关构造函数的参数,我们再来看看ThreadPoolExecutor执行任务时的大致规则:
    (1)如果线程池的数量还未达到核心线程的数量,那么会直接启动一个核心线程来执行任务
    (2)如果线程池中的线程数量已经达到或者超出核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
    (3)如果在步骤(2)中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
    (4)如果在步骤(3)中线程数量已经达到线程池规定的最大值,那么就会拒绝执行此任务,ThreadPoolExecutor会调用RejectExecutionHandler的rejectExecution方法来通知调用者。
    下面我们就先介绍ThreadPoolExecutor的构造方法中各个参数的含义。

我们再进去看一下这个方法

@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
       Params... params
)
{
   mStatus = Status.RUNNING;
 //开始执行任务时回调的方法.这个前面介绍如何使用时讲过
   onPreExecute();
   mWorker.mParams = params;
   exec.execute(mFuture);
   return this;
}

通过调用execute(Params... params),然后调用executeOnExecutor方法,executeOnExecutor方法中,首先调用onPreExecute();这个方法执行任务前的回调,做一些简单的工作,比如提示用户开始下载了等等; exec.execute(mFuture);这个方法是关键,把我们前面AsyncTask构造方法中的mFuture提交给 exec线程池,开始执行任务,这就是把耗时任务封装起来丢给线程池,线程池里的子线程根据任务队列里的任务开始处理任务,然后处理完成后FutureTask中done方法会回调;我们回到postResultIfNotInvoked(get())这个方法;

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

这个方法中,result就是get()返回来的结果,就是耗时任务执行的结果
然后再调了 postResult(result)方法;

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

一看这源码,不就发一个消息,hander接收消息处理

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
             //这里接收耗时任务执行完成结束,调finish方法
               result.mTask.finish(result.mData[0]);
               break;
           case MESSAGE_POST_PROGRESS:
               result.mTask.onProgressUpdate(result.mData);
               break;
       }
   }
}

hander接收耗时任务执行完成结束,调finish方法;

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

finish方法调用 onPostExecute(result);到这里应该明白了,为什么 onPostExecute(result)会回调任务处理完的的结束,并且可以在onPostExecute(result)方法中直接更新UI了.

好了,AsyncTask的源码差不多也就这些;在这里总结一下使用AsyncTask有哪些坑;

  • AsyncTask中维护着一个长度为128的缓冲队列,缓冲队列已满时,如果此时向线程提交任务,将会抛出RejectedExecutionException。

    private static final BlockingQueue<Runnable> sPoolWorkQueue =
       new LinkedBlockingQueue<Runnable>(128);
  • 生命周期
    很多开发者会认为一个在Activity中创建的AsyncTask会随着Activity的销毁而销毁。然而事实并非如此。AsyncTask会一直执行, 直到doInBackground()方法执行完毕。然后,如果 cancel(boolean)被调用, 那么onCancelled(Result result) 方法会被执行;否则,执行onPostExecute(Result result) 方法。如果我们的Activity销毁之前,没有取消 AsyncTask,这有可能让我们的AsyncTask崩溃(crash)。因为它想要处理的view已经不存在了。所以,我们总是必须确保在销毁活动之前取消任务。总之,我们使用AsyncTask需要确保AsyncTask正确地取消。

另外,即使我们正确地调用了cancle() 也未必能真正地取消任务。因为如果在doInBackgroud里有一个不可中断的操作,比如BitmapFactory.decodeStream(),那么这个操作会继续下去。

  • 内存泄漏
    如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。

  • 结果丢失
    屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。

本人做android开发多年,以后会陆续更新关于android高级UI,NDK开发,性能优化等文章,更多请关注我的微信公众号:谢谢!


以上是关于AsyncTask使用及源码分析的主要内容,如果未能解决你的问题,请参考以下文章

Asynctask结果显示重新创建片段后

Android中AsyncTask基本用法与源码分析(API 23)

AsyncTask源码分析

片段中的 Asynctask 未到达 onPostExecute

从源码角度一步步分析AsyncTask的用法与原理

你真的了解AsyncTask吗?AsyncTask源码分析