Android异步任务处理框架AsyncTask源码分析

转自:http://www.itnose.net/detail/6321673.html

引言

在平时项目开发中难免会遇到异步耗时的任务(比如最常见的网络请求)。遇到这种问题,我们可以自己通过Handler+Message+Thread/ThreadPool来构造一个异步耗时任务框架。当你下次项目中又遇到一个网络请求,你又不得不重写异步耗时任务处理框架。出于避免开发者重复搬砖工作,Google工程师给开发者搭建了一个通用的异步耗时任务处理框架?-AsyncTask。

AsyncTask简介

我把AsycnTask类称之为异步任务处理框架,为什么这么说?因为其内部通过Handler+Message+ThreadPool技术构建了一个异步耗时任务处理框架。所谓框架,无非就是封装复杂的逻辑操作,留给开发者少数接口或者方法来进行数据操作。AsyncTask类也一样,目的是让开发者很方便容易的在UI线程中处理一些异步耗时任务。AsyncTask类中将异步耗时任务处理放在ThreadPool线程池中处理,然后将处理结果通过Handler发送消息更新进度和结果,开发者只需要实现和重写AsyncTask类中的几个方法即可获得当前异步任务处理的进度和结果。

AsyncTask使用

由于AsyncTask是一个抽象类,所以开发者如果想使用AsyncTask类的话必须让子类去继承它。子类至少重写AsyncTask类中的 doInBackground方法,一般我们也会重写onPostExecute方法去获取异步任务处理结果。在使用AsyncTask类时,我们知道需要准备3个泛型参数分别是:

  • Params:异步任务执行时需要的参数类型
  • Progress:异步任务执行过程中进度更新类型
  • Result:异步任务执行结束返回的结果类型

    当你在使用AsyncTask无需相应的参数时可以将对应参数设置为 Void类型。

    AsyncTask使用的步骤顺序可分为如下:

    1. onPreExecute:该方法由系统在UI线程中调用,异步任务执行之前调用,做一些准备工作,比如初始化进度条。无需用户自己去调用,用户只需重写该方法即可,当然用户也可以不重写该方法。
    2. doInBackground:该方法由系统执行于后台线程中,当onPreExecute方法调用之后就调用该方法。所有异步耗时的任务都是在该方法中实现,同样用户无需自己去调用,用户只需重写该方法即可,且必须重写该方法。
    3. publishProgress:该方法在doInBackground方法中调用,用于发布当前异步任务执行的进度到UI线程中,该方法需要用户自己在onInBackground中调用。
    4. onProgressUpdate:该方法由系统在UI线程中调用,用于更新当前异步任务执行的进度,进而更新UI操作,该方法也无需用户自己调用,用户只需重写该方法即可。想要在UI线程中获得进度信息的前提是在doInBackground方法中调用了publishProgress方法。
    5. onPostExecute:该方法由系统在UI线程中调用,用于异步任务执行完成之后获取后台线程执行的结果。该方法同样无需用户自己调用,只需重写该方法即可。

    上面的解释可能会有点抽象,现在我们拿一个例子来说明AsyncTask的使用。

    public class MainActivity extends AppCompatActivity {
    
        private ProgressBar progressBar;
        private TextView value;
        private TextView result;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            result = (TextView) findViewById(R.id.result);
        }
    
        //启动一个任务
        public void startTask(View view) {
            String s1 = "task1";
            String s2 = "task2";
            String s3 = "task3";
            String s4 = "task4";
            String s5 = "task3";
            String s6 = "task3";
            String s7 = "task3";
            String s8 = "task3";
            new DownloadFilesTask().execute(s1, s2, s3, s4, s5, s6, s7, s8);
        }
    
        private class DownloadFilesTask extends AsyncTask<String, Integer, Long> {
    
            //该方法执行与后台线程中,所有异步耗时的任务都在此处操作 
            @Override
            protected Long doInBackground(String... urls) {
                int count = urls.length;
                long totalSize = 0;
                for (int i = 0; i < count; i++) {
                    try {
                        Thread.sleep(1 * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    totalSize += 800;
                    //发布进度信息到UI线程中
                    publishProgress((int) ((i / (float) (count - 1)) * 100));
                    //为了安全起见,每次循环都需检查当前任务是否被取消,如果被取消这退出循环
                    if (isCancelled()) break;
                }
                return totalSize;
            }
    
            //该方法在后台任务执行之前运行,在UI线程中执行,用于初始化一些信息
            @Override
            protected void onPreExecute() {
                value = (TextView) findViewById(R.id.progress_value);
                progressBar = (ProgressBar) findViewById(R.id.progress);
                progressBar.setMax(100);
            }
    
            //该方法在UI线程中执行,用于获取后台任务更新的进度信息
            @Override
            protected void onProgressUpdate(Integer... values) {
                value.setText(values[0] + "%");
                progressBar.setProgress(values[0]);
            }
    
            //该方法在UI线程中执行,用于获取后台任务执行完成之后的结果
            @Override
            protected void onPostExecute(Long aLong) {
                result.setText("the result is" + aLong);
                Toast.makeText(MainActivity.this, "the result is " + aLong, Toast.LENGTH_SHORT).show();
            }
        }
    }
    

    以上示例是一个最简单的模拟异步任务操作,我们主要的工作就是让子类DownloadFilesTask继承AsyncTask,然后重写相应的方法,其中只要是重写的方法都无需用户去控制其调用逻辑,只需重写里面的方法实现计算逻辑。言外之意就是:AsycnTask类中的那些方法的调用顺序是不需要用户去控制的,其内部已经控制好这些方法的调用逻辑。

    AsyncTask源码分析

    AsyncTask的构造方法

    public abstract class AsyncTask<Params, Progress, Result> {
        //获得当前运行状态的cup数
         private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
        //根据当前机器CUP的个数决定线程池中的线程个数
        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());
            }
        };
        //线程池中的缓存队列,此处为128个
        private static final BlockingQueue<Runnable> sPoolWorkQueue =
                new LinkedBlockingQueue<Runnable>(128);
    
        /** * 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 final int MESSAGE_POST_RESULT = 0x1;
        private static final int MESSAGE_POST_PROGRESS = 0x2;
        //内部类,消息的执行者handler对象
        private static final InternalHandler sHandler = new InternalHandler();
        //线程池中默认的执行器
        private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
        //异步任务回调接口
        private final WorkerRunnable<Params, Result> mWorker;
        private final FutureTask<Result> mFuture;
        //当前异步任务的状态,初始状态为“未执行”状态
        private volatile Status mStatus = Status.PENDING;
    
        private final AtomicBoolean mCancelled = new AtomicBoolean();
        private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
    
    ......................
    
      /** * 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
                    return postResult(doInBackground(mParams));
                }
            };
    
            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 occured while executing doInBackground()",
                                e.getCause());
                    } catch (CancellationException e) {
                        postResultIfNotInvoked(null);
                    }
                }
            };
        }
    ..................
    
    }

    分析:AsyncTask类中的所有成员变量的作用都已经添加注释,在AsyncTask类的成员变量中根据当前系统CPU的个数来构建一个固定大小的线程池THREAD_POOL_EXECUTOR成员变量。然后通SerialExecutor类创建了一个顺序执行的线程池成员变量SERIAL_EXECUTOR,这里我们暂且不讨论SerialExecutor类的具体实现,只需知道它是顺序执行的一个线程池执行器就可,感兴趣的童鞋可以深究。细心的你会发现AsyncTask的成员变量几乎都是静态的,也就是说:一个应用中的内存中只保存有一份这些成员变量的值。

    然后在构造方法中获得了mWorker对象,并且实现了里面的接口方法call,call方法里面调用了doInBackground方法。当后台线程任务被执行时,该call方法就会被调用。并且将mWorker作为参数传递给了FutureTask类来获取mFuture对象。因此在AsyncTask的构造方法中最后获得mFuture对象。FutureTask类也是继承自Runnable接口的。到此,我们可以理解成mFuture对象封装了一个后台的异步耗时任务,等待线程池执行器去处理该耗时任务。mFuture对象会作为一个线程接口在后面使用到。

    AsycnTask的执行方法execute

    由前面分析我们知道,AsyncTask处理异步任务的逻辑都在该类的内部实现了,我们只需要重写相应的方法即可。那么我们就从execute异步任务的执行方法开始跟踪AsyncTask类内部是怎么处理异步任务的逻辑的。

     /**
         * Executes the task with the specified parameters. The task returns
         * itself (this) so that the caller can keep a reference to it.
         * 
         * <p>Note: this function schedules the task on a queue for a single background
         * thread or pool of threads depending on the platform version.  When first
         * introduced, AsyncTasks were executed serially on a single background thread.
         * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
         * to a pool of threads allowing multiple tasks to operate in parallel. Starting
         * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being
         * executed on a single thread to avoid common application errors caused
         * by parallel execution.  If you truly want parallel execution, you can use
         * the {@link #executeOnExecutor} version of this method
         * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings
         * on its use.
         *
         * <p>This method must be invoked on the UI thread.
         *
         * @param params The parameters of the task.
         *
         * @return This instance of AsyncTask.
         *
         * @throws IllegalStateException If {@link #getStatus()} returns either
         *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
         *
         * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
         * @see #execute(Runnable)
         */
        public final AsyncTask<Params, Progress, Result> execute(Params... params) {
     return executeOnExecutor(sDefaultExecutor, params);
        }

    分析:该方法的注释比方法的实现还多,大概意思是:传指定参数执行异步任务,该方法的返回值是AsyncTask对象本身,目的是让调用者持有AsyncTask对象的一个引用。该方法的功能是:不同平台利用不同方式去处理队列中的任务,AsyncTask最初设计是单个后台线程去处理队列中的任务,到了Android1.6版本之后改为固定线程数的线程池去处理队列中的任务,在之后到了Android3.0之后,又改回到单个后台线程去处理队列中的任务,目的是为了解决Android1.6以后如果异步任务超过138个时AsyncTask会抛出异常。如果在Android3.0以后你想同时执行多个异步任务的话你可以使用AsyncTask类提供的executeOnExecutor方法实现。

    其实execute方法的实现也仅仅是调用了executeOnExecutor方法而已。那么我们跟踪代码进入executeOnExecutor方法

    /** <p>This method must be invoked on the UI thread. * * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a * convenient process-wide thread pool for tasks that are loosely coupled. * @param params The parameters of the task. * * @return This instance of AsyncTask. * * @throws IllegalStateException If {@link #getStatus()} returns either * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. * * @see #execute(Object[]) */
        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;
        }

    分析:注释解释,该方必须在UI线程中调用。第一个参数为线程池执行器Executor对象。第二个参数为异步任务执行所需参数Params。方法的返回值为AsyncTask实例对象。 
    1.代码第16-26行:判断当前异步任务状态,如果不是”PENDING“未执行状态,则会抛出相应的异常,如果是”RUNNING“,这抛出”Cannot execute task: the task is already running.”:当前任务正在执行;如果是”FINISHED”,则抛出Cannot execute task: the task has already been executed (a task can be executed only once)”:该任务已经被执行。由此可知AsyncTask同一个异步任务只能被执行一次。

    2.代码第28行:标记当前异步任务的状态为”RUNNING”表示任务正在执行。

    3.代码第30行:调用onPreExecute方法,该方法是个空方法,在子类中可以重写该方法做一些初始化的工作,比如初始化进度条信息等。该方法执行与UI线程中。

    4.代码第32-33行:调用线程池执行器去处理异步任务mFuture,此处开始执行异步耗时任务,在线程中操作。由AsyncTask构造方法知道,mFuture是一个异步任务的封装。来看看FutureTask类中的run方法吧

    ...............
    
     public FutureTask(Callable<V> callable) {
            if (callable == null)
                throw new NullPointerException();
            this.callable = callable;
            this.state = NEW;       // ensure visibility of callable
        }
    
    ........
    
    public void run() {
            if (state != NEW ||
                !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                             null, Thread.currentThread()))
                return;
            try {
                Callable<V> c = callable;
                if (c != null && state == NEW) {
                    V result;
                    boolean ran;
                    try {
                        result = c.call();
                        ran = true;
                    } catch (Throwable ex) {
                        result = null;
                        ran = false;
                        setException(ex);
                    }
                    if (ran)
                        set(result);
                }
            } 
            ...............
        }

    分析:在FutureTask的构造方法中我们看到,将AsyncTask构造方法中的mWorker变量赋值给了FutureTask类中的callable变量。我们看到代码第24行调用了callable的call方法,因此这里调用如下接口方法

     mWorker = new WorkerRunnable<Params, Result>() {
                public Result call() throws Exception {
                    mTaskInvoked.set(true);
    
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    return postResult(doInBackground(mParams));
                }
            };

    有上面代码可以看出,在call方法中doInBackground方法的返回值作为参数传递给postResult方法,然后doInBackground是一个抽象方法,真正执行异步耗时任务的方法,具体实现需要在AsyncTask子类中实现。那么我们来看看postResult方法做了什么?

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

    该方法先通过getHandler方法获得一个Handler对象,然后将异步任务doInBackground方法返回的结果Result封装成消息发送出去,由 从Handler+Message+Looper源码带你分析Android系统的消息处理机制这篇博客我们知道,Handler消息处理机制是谁发送消息谁处理消息。那么我们来看看getHandler方法吧

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

    加锁获得Handler对象,目的是防止多线程同时持有Handler对象导致异步任务处理混乱情况。

    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
                        result.mTask.finish(result.mData[0]);
                        break;
                    case MESSAGE_POST_PROGRESS:
                        result.mTask.onProgressUpdate(result.mData);
                        break;
                }
            }
        }

    有上面代码可以看出,InternalHandler是AsyncTask类的内部类,然而InternalHandler是通过Looper.getMainLooper()UI线程的Looper作为参数构造的,因此InternalHandler是在UI线程中处理消息的。我们看到,上面有两条消息分支。

    1.MESSAGE_POST_RESULT:也就是调用了postResult方法之后发送的消息才走这条分支,将异步任务处理结果推送到UI线程中。此处又调用了finish方法,进入看看其实现

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

    该方法表示异步任务处理结束,首先判断当前异步任务是否已经被取消,如果被取消则调用onCancelled方法,子类中如果想在异步任务取消时候做一些处理的话可以重写onCancelled方法。如果没有取消则调用onPostExecute方法,该方法的实现体是一个空,需要在子类中去实现拿到异步任务和处理结果。最后异步任务处理结束,将当前异步任务的状态置为FINISHED状态,以防止同一个任务被执行多次。

    2.MESSAGE_POST_PROGRESS:该条消息是用来发布异步任务进度信息的。最后会调用onProgressUpdate来发布进度信息到UI线程中,其中该方法是一个空的,需要子类去实现获得当前异步任务执行进度。那什么情况下发送了这条消息呢?我们查看代码发现,只有AsyncTask类中的publishProgress方法里面发送了这条信息。那进入该方法看看

     /** * This method can be invoked from {@link #doInBackground} to * publish updates on the UI thread while the background computation is * still running. Each call to this method will trigger the execution of * {@link #onProgressUpdate} on the UI thread. * * {@link #onProgressUpdate} will not be called if the task has been * canceled. * * @param values The progress values to update the UI with. * * @see #onProgressUpdate * @see #doInBackground */
        protected final void publishProgress(Progress... values) {
            if (!isCancelled()) {
                getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                        new AsyncTaskResult<Progress>(this, values)).sendToTarget();
            }
        }
    

    方法的注释解释:该方法需要在doInBackground方法中调用,目的是去更新当前异步任务执行进度,每调用一次该方法就会触发onProgressUpdate方法的调用,也就是每调用一次publishProgress方法就会发送一个消息码MESSAGE_POST_PROGRESS的消息到InternalHandler类处理。

    总结:由此可知,如果你想要在UI线程中获得当前异步任务执行的进度信息,就必须在onInBackground方法中调用publishProgress方法,否则就获取不到进度信息。

    AsyncTask陷阱

    很多人可能都知道在Android3.0之前的AsyncTask类是有一个缺陷的,也就是当你同时执行139个异步任务的时候就会出错了。为什么会是这样子呢?所有玄机都在AsyncTask的执行方法execute里面。我们不妨从Android2.3的AsyncTask源码去解答该答案,在该版本中的execute方法如下:

    public final AsyncTask<Params, Progress, Result> More ...execute(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;
            sExecutor.execute(mFuture);
    
            return this;
        }

    有上面代码可以发现,Android2.3版本的execute方法实现和Android3.0以后的不一样,也就是上面我们分析的。在2.3版本之前线程池执行器是这么构造的

    private static final int CORE_POOL_SIZE = 5;
       private static final int MAXIMUM_POOL_SIZE = 128;
        private static final int KEEP_ALIVE = 1;
    
        private static final BlockingQueue<Runnable> sWorkQueue =
                new LinkedBlockingQueue<Runnable>(10);
    
        private static final ThreadFactory sThreadFactory = new ThreadFactory() {
            private final AtomicInteger mCount = new AtomicInteger(1);
    
           public Thread More ...newThread(Runnable r) {
                return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
            }
        };
    
        private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
                MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);

    主要是构造一个固定线程为5,最大线程为128,缓存对了为10的一个线程池执行器。由此可以知道,当有第139个异步任务执行的时候就超出了最大线程数和缓存队列的总和。因此会报错。那么Android3.0以后的版本是怎么解决这个问题的呢?

    在Android3.0以后的版本AsyncTask类修改了execute方法的实现

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

    该方法又调用了executeOnExecutor方法去执行异步任务,此处使用的线程池执行器是sDefaultExecutor默认的执行器,有前面我们分析可得,默认的线程池执行器是一个顺序执行的,且缓存队列是无限大。也就是多个异步任务是顺序执行的,只有当第一个异步任务执行完之后才能执行第二个,这么一来不管你有多少个异步任务,都不会报错。那有人可能会问?如果我需要在Android3.0以后的版本上同时执行多个异步任务怎么办?哈哈!骚年,不用担心!Google Android工程师早已给你想好了。在Anddroid3.0以后的AsyncTask类给暴露出一个接口也就是上面的executeOnExecutor方法啦,我们只需要重新构造一个线程池执行器,比如说你可以调用newCachedThreadPool方法来创建一个无线大的缓存线程池,可以同时执行无线个任务。

    //构建一个缓存线程池,用于同时执行无限多个异步耗时任务
    ExecutorService executorService = Executors.newCachedThreadPool();
    asyncTask.executeOnExecutor(executorService,params);

    开发者可以根据项目需求选择自己合适的线程池执行器:

  • Single Thread Executor : 只有一个线程的线程池,因此所有提交的任务是顺序执行,代码: Executors.newSingleThreadExecutor()
  • Cached Thread Pool : 线程池里有很多线程需要同时执行,老的可用线程将被新的任务触发重新执行,如果线程超过60秒内没执行,那么将被终止并从池中删除,代码:Executors.newCachedThreadPool()
  • Fixed Thread Pool : 拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待,代码: Executors.newFixedThreadPool()
  • Scheduled Thread Pool : 用来调度即将执行的任务的线程池,代码:Executors.newScheduledThreadPool()
  • Single Thread Scheduled Pool : 只有一个线程,用来调度执行将来的任务,代码:Executors.newSingleThreadScheduledExecutor()

    总结: 在Android3.0之前的AsyncTask是有缺陷的,因为其内部使用了固定线程数和缓存大小的线程池来执行异步耗时任务,所以当同时有超过139个异步耗时任务时,AsyncTask就会报错。 然而在Android3.0以后的AsyncTask是没有缺陷的,因为其背部使用了一个顺序执行的线程池来执行异步耗时任务,不论有多少个异步任务每次都只能执行一个,所以不会报错。且Android3.0之后的AsyncTask提供了自定义线程池的方法,更加方便灵活的让开发者根据自己所需来选择不同的线程池执行器来处理耗时任务。

    值得注意的是:如果你要兼容Android2.3以及3.0使用AsyncTask同时处理139个异步耗时任务是不可能的,这个时候你只能自己利用 Handler+Message+ThreadPool+Exexutor自己构建一个异步任务处理框架了。考验你技术的时候到啦!其实你可以仿着3.0的AsyncTask写一个就好了。

    AsyncTask总结

    1.AsyncTask类只能在UI线程中使用,为什么?可以看 AsycnTask的执行方法execute小节,因为里面的Handler是有MainLooper构造的。

    2.初学者可能会有这个疑问?一个App应用中有很多个异步耗时任务,每个任务都去创建一个AsyncTask对象岂不是很耗费资源?如果这么想你就还不理解AsyncTask了。从AsyncTask的构造方法小节可以看到,里面的绝大多数成员变量都是静态static的。包括Executor执行器。因此整个应用都是共享且只有一个AsyncTask实例的。

    3.关于AsyncTask类中的方法调用逻辑无需用开发者去关注,开发者只需要重写相应的方法即可。当然如果你想在UI线程中获得进度信息,你就必须在doInBackground方法中调用publishProgress方法才可以触发UI线程中的onProgressUpdate方法来更新进度信息。

    4.值得一提的是:在你重写doInBackground方法执行异步耗时任务时,你最好在适当的地方调用

    if (isCancelled()){
    //做一些任务取消的处理
    }

    此方法调用的目的是检测在执行异步耗时任务时该任务是否已经被取消了?如果被取消了,当前正在执行的异步任务就没有必要继续执行了,节省资源。比如博客开头的例子:异步任务是一个循环任务,如果在执行到第2个循环的时候异步任务被取消了,此时就跳出循环结束此次的异步任务。

    5.在Android3.0之前如果你想要同时执行超过139个异步任务的时候,AsyncTask是会报错的,这时候你就不得不自己去重新构造一个多线程异步任务处理框架了,就不能直接使用AsyncTask框架了。但是在Android3.0之后改善了这个问题,3.0之后默认是顺序执行异步任务,无论你有多少个异步任务都不会报错,你也可以自定义一个符合需求的线程池执行器。

版权声明:本文为博主原创文章,未经博主允许不得转载。

引言

在平时项目开发中难免会遇到异步耗时的任务(比如最常见的网络请求)。遇到这种问题,我们可以自己通过Handler+Message+Thread/ThreadPool来构造一个异步耗时任务框架。当你下次项目中又遇到一个网络请求,你又不得不重写异步耗时任务处理框架。出于避免开发者重复搬砖工作,Google工程师给开发者搭建了一个通用的异步耗时任务处理框架?-AsyncTask。

AsyncTask简介

我把AsycnTask类称之为异步任务处理框架,为什么这么说?因为其内部通过Handler+Message+ThreadPool技术构建了一个异步耗时任务处理框架。所谓框架,无非就是封装复杂的逻辑操作,留给开发者少数接口或者方法来进行数据操作。AsyncTask类也一样,目的是让开发者很方便容易的在UI线程中处理一些异步耗时任务。AsyncTask类中将异步耗时任务处理放在ThreadPool线程池中处理,然后将处理结果通过Handler发送消息更新进度和结果,开发者只需要实现和重写AsyncTask类中的几个方法即可获得当前异步任务处理的进度和结果。

AsyncTask使用

由于AsyncTask是一个抽象类,所以开发者如果想使用AsyncTask类的话必须让子类去继承它。子类至少重写AsyncTask类中的 doInBackground方法,一般我们也会重写onPostExecute方法去获取异步任务处理结果。在使用AsyncTask类时,我们知道需要准备3个泛型参数分别是:

  • Params:异步任务执行时需要的参数类型
  • Progress:异步任务执行过程中进度更新类型
  • Result:异步任务执行结束返回的结果类型

    当你在使用AsyncTask无需相应的参数时可以将对应参数设置为 Void类型。

    AsyncTask使用的步骤顺序可分为如下:

    1. onPreExecute:该方法由系统在UI线程中调用,异步任务执行之前调用,做一些准备工作,比如初始化进度条。无需用户自己去调用,用户只需重写该方法即可,当然用户也可以不重写该方法。
    2. doInBackground:该方法由系统执行于后台线程中,当onPreExecute方法调用之后就调用该方法。所有异步耗时的任务都是在该方法中实现,同样用户无需自己去调用,用户只需重写该方法即可,且必须重写该方法。
    3. publishProgress:该方法在doInBackground方法中调用,用于发布当前异步任务执行的进度到UI线程中,该方法需要用户自己在onInBackground中调用。
    4. onProgressUpdate:该方法由系统在UI线程中调用,用于更新当前异步任务执行的进度,进而更新UI操作,该方法也无需用户自己调用,用户只需重写该方法即可。想要在UI线程中获得进度信息的前提是在doInBackground方法中调用了publishProgress方法。
    5. onPostExecute:该方法由系统在UI线程中调用,用于异步任务执行完成之后获取后台线程执行的结果。该方法同样无需用户自己调用,只需重写该方法即可。

    上面的解释可能会有点抽象,现在我们拿一个例子来说明AsyncTask的使用。

    public class MainActivity extends AppCompatActivity {
    
        private ProgressBar progressBar;
        private TextView value;
        private TextView result;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            result = (TextView) findViewById(R.id.result);
        }
    
        //启动一个任务
        public void startTask(View view) {
            String s1 = "task1";
            String s2 = "task2";
            String s3 = "task3";
            String s4 = "task4";
            String s5 = "task3";
            String s6 = "task3";
            String s7 = "task3";
            String s8 = "task3";
            new DownloadFilesTask().execute(s1, s2, s3, s4, s5, s6, s7, s8);
        }
    
        private class DownloadFilesTask extends AsyncTask<String, Integer, Long> {
    
            //该方法执行与后台线程中,所有异步耗时的任务都在此处操作 
            @Override
            protected Long doInBackground(String... urls) {
                int count = urls.length;
                long totalSize = 0;
                for (int i = 0; i < count; i++) {
                    try {
                        Thread.sleep(1 * 1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    totalSize += 800;
                    //发布进度信息到UI线程中
                    publishProgress((int) ((i / (float) (count - 1)) * 100));
                    //为了安全起见,每次循环都需检查当前任务是否被取消,如果被取消这退出循环
                    if (isCancelled()) break;
                }
                return totalSize;
            }
    
            //该方法在后台任务执行之前运行,在UI线程中执行,用于初始化一些信息
            @Override
            protected void onPreExecute() {
                value = (TextView) findViewById(R.id.progress_value);
                progressBar = (ProgressBar) findViewById(R.id.progress);
                progressBar.setMax(100);
            }
    
            //该方法在UI线程中执行,用于获取后台任务更新的进度信息
            @Override
            protected void onProgressUpdate(Integer... values) {
                value.setText(values[0] + "%");
                progressBar.setProgress(values[0]);
            }
    
            //该方法在UI线程中执行,用于获取后台任务执行完成之后的结果
            @Override
            protected void onPostExecute(Long aLong) {
                result.setText("the result is" + aLong);
                Toast.makeText(MainActivity.this, "the result is " + aLong, Toast.LENGTH_SHORT).show();
            }
        }
    }
    

    以上示例是一个最简单的模拟异步任务操作,我们主要的工作就是让子类DownloadFilesTask继承AsyncTask,然后重写相应的方法,其中只要是重写的方法都无需用户去控制其调用逻辑,只需重写里面的方法实现计算逻辑。言外之意就是:AsycnTask类中的那些方法的调用顺序是不需要用户去控制的,其内部已经控制好这些方法的调用逻辑。

    AsyncTask源码分析

    AsyncTask的构造方法

    public abstract class AsyncTask<Params, Progress, Result> {
        //获得当前运行状态的cup数
         private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
        //根据当前机器CUP的个数决定线程池中的线程个数
        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());
            }
        };
        //线程池中的缓存队列,此处为128个
        private static final BlockingQueue<Runnable> sPoolWorkQueue =
                new LinkedBlockingQueue<Runnable>(128);
    
        /** * 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 final int MESSAGE_POST_RESULT = 0x1;
        private static final int MESSAGE_POST_PROGRESS = 0x2;
        //内部类,消息的执行者handler对象
        private static final InternalHandler sHandler = new InternalHandler();
        //线程池中默认的执行器
        private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
        //异步任务回调接口
        private final WorkerRunnable<Params, Result> mWorker;
        private final FutureTask<Result> mFuture;
        //当前异步任务的状态,初始状态为“未执行”状态
        private volatile Status mStatus = Status.PENDING;
    
        private final AtomicBoolean mCancelled = new AtomicBoolean();
        private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
    
    ......................
    
      /** * 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
                    return postResult(doInBackground(mParams));
                }
            };
    
            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 occured while executing doInBackground()",
                                e.getCause());
                    } catch (CancellationException e) {
                        postResultIfNotInvoked(null);
                    }
                }
            };
        }
    ..................
    
    }

    分析:AsyncTask类中的所有成员变量的作用都已经添加注释,在AsyncTask类的成员变量中根据当前系统CPU的个数来构建一个固定大小的线程池THREAD_POOL_EXECUTOR成员变量。然后通SerialExecutor类创建了一个顺序执行的线程池成员变量SERIAL_EXECUTOR,这里我们暂且不讨论SerialExecutor类的具体实现,只需知道它是顺序执行的一个线程池执行器就可,感兴趣的童鞋可以深究。细心的你会发现AsyncTask的成员变量几乎都是静态的,也就是说:一个应用中的内存中只保存有一份这些成员变量的值。

    然后在构造方法中获得了mWorker对象,并且实现了里面的接口方法call,call方法里面调用了doInBackground方法。当后台线程任务被执行时,该call方法就会被调用。并且将mWorker作为参数传递给了FutureTask类来获取mFuture对象。因此在AsyncTask的构造方法中最后获得mFuture对象。FutureTask类也是继承自Runnable接口的。到此,我们可以理解成mFuture对象封装了一个后台的异步耗时任务,等待线程池执行器去处理该耗时任务。mFuture对象会作为一个线程接口在后面使用到。

    AsycnTask的执行方法execute

    由前面分析我们知道,AsyncTask处理异步任务的逻辑都在该类的内部实现了,我们只需要重写相应的方法即可。那么我们就从execute异步任务的执行方法开始跟踪AsyncTask类内部是怎么处理异步任务的逻辑的。

     /**
         * Executes the task with the specified parameters. The task returns
         * itself (this) so that the caller can keep a reference to it.
         * 
         * <p>Note: this function schedules the task on a queue for a single background
         * thread or pool of threads depending on the platform version.  When first
         * introduced, AsyncTasks were executed serially on a single background thread.
         * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
         * to a pool of threads allowing multiple tasks to operate in parallel. Starting
         * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being
         * executed on a single thread to avoid common application errors caused
         * by parallel execution.  If you truly want parallel execution, you can use
         * the {@link #executeOnExecutor} version of this method
         * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings
         * on its use.
         *
         * <p>This method must be invoked on the UI thread.
         *
         * @param params The parameters of the task.
         *
         * @return This instance of AsyncTask.
         *
         * @throws IllegalStateException If {@link #getStatus()} returns either
         *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
         *
         * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
         * @see #execute(Runnable)
         */
        public final AsyncTask<Params, Progress, Result> execute(Params... params) {
     return executeOnExecutor(sDefaultExecutor, params);
        }

    分析:该方法的注释比方法的实现还多,大概意思是:传指定参数执行异步任务,该方法的返回值是AsyncTask对象本身,目的是让调用者持有AsyncTask对象的一个引用。该方法的功能是:不同平台利用不同方式去处理队列中的任务,AsyncTask最初设计是单个后台线程去处理队列中的任务,到了Android1.6版本之后改为固定线程数的线程池去处理队列中的任务,在之后到了Android3.0之后,又改回到单个后台线程去处理队列中的任务,目的是为了解决Android1.6以后如果异步任务超过138个时AsyncTask会抛出异常。如果在Android3.0以后你想同时执行多个异步任务的话你可以使用AsyncTask类提供的executeOnExecutor方法实现。

    其实execute方法的实现也仅仅是调用了executeOnExecutor方法而已。那么我们跟踪代码进入executeOnExecutor方法

    /** <p>This method must be invoked on the UI thread. * * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a * convenient process-wide thread pool for tasks that are loosely coupled. * @param params The parameters of the task. * * @return This instance of AsyncTask. * * @throws IllegalStateException If {@link #getStatus()} returns either * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}. * * @see #execute(Object[]) */
        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;
        }

    分析:注释解释,该方必须在UI线程中调用。第一个参数为线程池执行器Executor对象。第二个参数为异步任务执行所需参数Params。方法的返回值为AsyncTask实例对象。 
    1.代码第16-26行:判断当前异步任务状态,如果不是”PENDING“未执行状态,则会抛出相应的异常,如果是”RUNNING“,这抛出”Cannot execute task: the task is already running.”:当前任务正在执行;如果是”FINISHED”,则抛出Cannot execute task: the task has already been executed (a task can be executed only once)”:该任务已经被执行。由此可知AsyncTask同一个异步任务只能被执行一次。

    2.代码第28行:标记当前异步任务的状态为”RUNNING”表示任务正在执行。

    3.代码第30行:调用onPreExecute方法,该方法是个空方法,在子类中可以重写该方法做一些初始化的工作,比如初始化进度条信息等。该方法执行与UI线程中。

    4.代码第32-33行:调用线程池执行器去处理异步任务mFuture,此处开始执行异步耗时任务,在线程中操作。由AsyncTask构造方法知道,mFuture是一个异步任务的封装。来看看FutureTask类中的run方法吧

    ...............
    
     public FutureTask(Callable<V> callable) {
            if (callable == null)
                throw new NullPointerException();
            this.callable = callable;
            this.state = NEW;       // ensure visibility of callable
        }
    
    ........
    
    public void run() {
            if (state != NEW ||
                !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                             null, Thread.currentThread()))
                return;
            try {
                Callable<V> c = callable;
                if (c != null && state == NEW) {
                    V result;
                    boolean ran;
                    try {
                        result = c.call();
                        ran = true;
                    } catch (Throwable ex) {
                        result = null;
                        ran = false;
                        setException(ex);
                    }
                    if (ran)
                        set(result);
                }
            } 
            ...............
        }

    分析:在FutureTask的构造方法中我们看到,将AsyncTask构造方法中的mWorker变量赋值给了FutureTask类中的callable变量。我们看到代码第24行调用了callable的call方法,因此这里调用如下接口方法

     mWorker = new WorkerRunnable<Params, Result>() {
                public Result call() throws Exception {
                    mTaskInvoked.set(true);
    
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    return postResult(doInBackground(mParams));
                }
            };

    有上面代码可以看出,在call方法中doInBackground方法的返回值作为参数传递给postResult方法,然后doInBackground是一个抽象方法,真正执行异步耗时任务的方法,具体实现需要在AsyncTask子类中实现。那么我们来看看postResult方法做了什么?

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

    该方法先通过getHandler方法获得一个Handler对象,然后将异步任务doInBackground方法返回的结果Result封装成消息发送出去,由 从Handler+Message+Looper源码带你分析Android系统的消息处理机制这篇博客我们知道,Handler消息处理机制是谁发送消息谁处理消息。那么我们来看看getHandler方法吧

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

    加锁获得Handler对象,目的是防止多线程同时持有Handler对象导致异步任务处理混乱情况。

    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
                        result.mTask.finish(result.mData[0]);
                        break;
                    case MESSAGE_POST_PROGRESS:
                        result.mTask.onProgressUpdate(result.mData);
                        break;
                }
            }
        }

    有上面代码可以看出,InternalHandler是AsyncTask类的内部类,然而InternalHandler是通过Looper.getMainLooper()UI线程的Looper作为参数构造的,因此InternalHandler是在UI线程中处理消息的。我们看到,上面有两条消息分支。

    1.MESSAGE_POST_RESULT:也就是调用了postResult方法之后发送的消息才走这条分支,将异步任务处理结果推送到UI线程中。此处又调用了finish方法,进入看看其实现

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

    该方法表示异步任务处理结束,首先判断当前异步任务是否已经被取消,如果被取消则调用onCancelled方法,子类中如果想在异步任务取消时候做一些处理的话可以重写onCancelled方法。如果没有取消则调用onPostExecute方法,该方法的实现体是一个空,需要在子类中去实现拿到异步任务和处理结果。最后异步任务处理结束,将当前异步任务的状态置为FINISHED状态,以防止同一个任务被执行多次。

    2.MESSAGE_POST_PROGRESS:该条消息是用来发布异步任务进度信息的。最后会调用onProgressUpdate来发布进度信息到UI线程中,其中该方法是一个空的,需要子类去实现获得当前异步任务执行进度。那什么情况下发送了这条消息呢?我们查看代码发现,只有AsyncTask类中的publishProgress方法里面发送了这条信息。那进入该方法看看

     /** * This method can be invoked from {@link #doInBackground} to * publish updates on the UI thread while the background computation is * still running. Each call to this method will trigger the execution of * {@link #onProgressUpdate} on the UI thread. * * {@link #onProgressUpdate} will not be called if the task has been * canceled. * * @param values The progress values to update the UI with. * * @see #onProgressUpdate * @see #doInBackground */
        protected final void publishProgress(Progress... values) {
            if (!isCancelled()) {
                getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                        new AsyncTaskResult<Progress>(this, values)).sendToTarget();
            }
        }
    

    方法的注释解释:该方法需要在doInBackground方法中调用,目的是去更新当前异步任务执行进度,每调用一次该方法就会触发onProgressUpdate方法的调用,也就是每调用一次publishProgress方法就会发送一个消息码MESSAGE_POST_PROGRESS的消息到InternalHandler类处理。

    总结:由此可知,如果你想要在UI线程中获得当前异步任务执行的进度信息,就必须在onInBackground方法中调用publishProgress方法,否则就获取不到进度信息。

    AsyncTask陷阱

    很多人可能都知道在Android3.0之前的AsyncTask类是有一个缺陷的,也就是当你同时执行139个异步任务的时候就会出错了。为什么会是这样子呢?所有玄机都在AsyncTask的执行方法execute里面。我们不妨从Android2.3的AsyncTask源码去解答该答案,在该版本中的execute方法如下:

    public final AsyncTask<Params, Progress, Result> More ...execute(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;
            sExecutor.execute(mFuture);
    
            return this;
        }

    有上面代码可以发现,Android2.3版本的execute方法实现和Android3.0以后的不一样,也就是上面我们分析的。在2.3版本之前线程池执行器是这么构造的

    private static final int CORE_POOL_SIZE = 5;
       private static final int MAXIMUM_POOL_SIZE = 128;
        private static final int KEEP_ALIVE = 1;
    
        private static final BlockingQueue<Runnable> sWorkQueue =
                new LinkedBlockingQueue<Runnable>(10);
    
        private static final ThreadFactory sThreadFactory = new ThreadFactory() {
            private final AtomicInteger mCount = new AtomicInteger(1);
    
           public Thread More ...newThread(Runnable r) {
                return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
            }
        };
    
        private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
                MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);

    主要是构造一个固定线程为5,最大线程为128,缓存对了为10的一个线程池执行器。由此可以知道,当有第139个异步任务执行的时候就超出了最大线程数和缓存队列的总和。因此会报错。那么Android3.0以后的版本是怎么解决这个问题的呢?

    在Android3.0以后的版本AsyncTask类修改了execute方法的实现

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

    该方法又调用了executeOnExecutor方法去执行异步任务,此处使用的线程池执行器是sDefaultExecutor默认的执行器,有前面我们分析可得,默认的线程池执行器是一个顺序执行的,且缓存队列是无限大。也就是多个异步任务是顺序执行的,只有当第一个异步任务执行完之后才能执行第二个,这么一来不管你有多少个异步任务,都不会报错。那有人可能会问?如果我需要在Android3.0以后的版本上同时执行多个异步任务怎么办?哈哈!骚年,不用担心!Google Android工程师早已给你想好了。在Anddroid3.0以后的AsyncTask类给暴露出一个接口也就是上面的executeOnExecutor方法啦,我们只需要重新构造一个线程池执行器,比如说你可以调用newCachedThreadPool方法来创建一个无线大的缓存线程池,可以同时执行无线个任务。

    //构建一个缓存线程池,用于同时执行无限多个异步耗时任务
    ExecutorService executorService = Executors.newCachedThreadPool();
    asyncTask.executeOnExecutor(executorService,params);

    开发者可以根据项目需求选择自己合适的线程池执行器:

  • Single Thread Executor : 只有一个线程的线程池,因此所有提交的任务是顺序执行,代码: Executors.newSingleThreadExecutor()
  • Cached Thread Pool : 线程池里有很多线程需要同时执行,老的可用线程将被新的任务触发重新执行,如果线程超过60秒内没执行,那么将被终止并从池中删除,代码:Executors.newCachedThreadPool()
  • Fixed Thread Pool : 拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待,代码: Executors.newFixedThreadPool()
  • Scheduled Thread Pool : 用来调度即将执行的任务的线程池,代码:Executors.newScheduledThreadPool()
  • Single Thread Scheduled Pool : 只有一个线程,用来调度执行将来的任务,代码:Executors.newSingleThreadScheduledExecutor()

    总结: 在Android3.0之前的AsyncTask是有缺陷的,因为其内部使用了固定线程数和缓存大小的线程池来执行异步耗时任务,所以当同时有超过139个异步耗时任务时,AsyncTask就会报错。 然而在Android3.0以后的AsyncTask是没有缺陷的,因为其背部使用了一个顺序执行的线程池来执行异步耗时任务,不论有多少个异步任务每次都只能执行一个,所以不会报错。且Android3.0之后的AsyncTask提供了自定义线程池的方法,更加方便灵活的让开发者根据自己所需来选择不同的线程池执行器来处理耗时任务。

    值得注意的是:如果你要兼容Android2.3以及3.0使用AsyncTask同时处理139个异步耗时任务是不可能的,这个时候你只能自己利用 Handler+Message+ThreadPool+Exexutor自己构建一个异步任务处理框架了。考验你技术的时候到啦!其实你可以仿着3.0的AsyncTask写一个就好了。

    AsyncTask总结

    1.AsyncTask类只能在UI线程中使用,为什么?可以看 AsycnTask的执行方法execute小节,因为里面的Handler是有MainLooper构造的。

    2.初学者可能会有这个疑问?一个App应用中有很多个异步耗时任务,每个任务都去创建一个AsyncTask对象岂不是很耗费资源?如果这么想你就还不理解AsyncTask了。从AsyncTask的构造方法小节可以看到,里面的绝大多数成员变量都是静态static的。包括Executor执行器。因此整个应用都是共享且只有一个AsyncTask实例的。

    3.关于AsyncTask类中的方法调用逻辑无需用开发者去关注,开发者只需要重写相应的方法即可。当然如果你想在UI线程中获得进度信息,你就必须在doInBackground方法中调用publishProgress方法才可以触发UI线程中的onProgressUpdate方法来更新进度信息。

    4.值得一提的是:在你重写doInBackground方法执行异步耗时任务时,你最好在适当的地方调用

    if (isCancelled()){
    //做一些任务取消的处理
    }

    此方法调用的目的是检测在执行异步耗时任务时该任务是否已经被取消了?如果被取消了,当前正在执行的异步任务就没有必要继续执行了,节省资源。比如博客开头的例子:异步任务是一个循环任务,如果在执行到第2个循环的时候异步任务被取消了,此时就跳出循环结束此次的异步任务。

    5.在Android3.0之前如果你想要同时执行超过139个异步任务的时候,AsyncTask是会报错的,这时候你就不得不自己去重新构造一个多线程异步任务处理框架了,就不能直接使用AsyncTask框架了。但是在Android3.0之后改善了这个问题,3.0之后默认是顺序执行异步任务,无论你有多少个异步任务都不会报错,你也可以自定义一个符合需求的线程池执行器。

版权声明:本文为博主原创文章,未经博主允许不得转载。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值