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使用及源码分析》
image

    原文作者:java集合源码分析
    原文地址: https://juejin.im/entry/5a5325f8518825733d68e8b2
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞