Android 实现异步任务机制有两种方式Handler和AsyncTask。AsyncTask的本质是一个线程池,所有提交的异步任务都会在这个线程池中的工作线程内执行,本文介绍AsyncTask的使用方法,同时介绍AsyncTask的执行原理,分析其存在的缺陷,并给出在实际使用中可以重写Executor的来解决。

一、AsyncTask介绍:

       先看AyncTask的定义:

public abstract class AsyncTask<Params, Progress, Result> {<span style="font-family:SimSun;font-size:12px;">  
</span>


    三种泛型类型分别代表“启动任务执行的输入参数”、“后台任务执行的进度”、“后台计算结果的类型”。在特定场合下,并不是所有类型都被使用,如果没有被使用,可以用void 类型代替。


        一个异步任务的执行一般包括以下几个步骤:


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


2.onPreExecute(),在execute(Params... params)被调用后立即执行,一般用来在执行后台任务前对UI做一些标记。


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


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


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


        在使用的时候,有几点需要格外注意:


1.异步任务的实例必须在UI线程中创建。


2.execute(Params... params)方法必须在UI线程中调用。


3.不要手动调用onPreExecute(),doInBackground(Params... params),onProgressUpdate(Progress... values),onPostExecute(Result result)这几个方法。


4.不能在doInBackground(Params... params)中更改UI组件的信息。


5.一个任务实例只能执行一次,如果执行第二次将会抛出异常。


二、AsyncTask 原理介绍

        AsyncTask 本质上通过线程池来执行,在AsyncTask中存在全局静态线程池,如下:       

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 ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };
    /**
     * 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);

CORE_POOL_SIZE: 线程池维护线程的最少数量是5
MAXIMUM_POOL_SIZE:线程池维护线程的最大数量是128
KEEP_ALIVE: 线程池维护线程所允许的空闲时间是1,单位是秒
TimeUnit.SECONDS: 线程池维护线程所允许的空闲时间的单位为秒
sPoolWorkQueue: 线程池所使用的缓冲队列
sThreadFactory: 任务执行线程

        AsyncTask 提供的执行有两种方式如下:

1.  public static void execute(Runnable runnable) 
2.  public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {

        第2种方法的参数exec 需要我们自定义一个线程执行器。如果使用第1种方法执行任务,AsyncTask将使用默认的执行器,AsyncTask创建默认执行器代码如下:

       

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) {
            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);
            }
        }
    }

       一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是Runnable类型对象的run()方法。当一个任务通过execute(Runnable) 方法欲添加到线程池时:


1.  如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。


2.  如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。


3.  如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。


4. 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,将会抛出RejectedExecutionException。


5.  当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。


       从上面的分析可知,但出现情况4时会出现异常,如果AsyncTask可能存在新开大量线程消耗系统资源和导致应用FC的风险,在多并发网络数据下载时可能会出现这样情况,如在浏览gallery浏览海报时,当划屏速度快而网络环境不好时,将出现创建异步任务过多的情况。


三、AsyncTask的一些优化

      这里结合一个电影海报浏览的例子来介绍几个AsynTask在实际使用中的优化,海报浏览通过gallery来实现,每个gallery item为1张海报,gallery选中项通过AsyncTask从服务器下载海报。为了避免在网络环境差出现创建AsyncTask创建任务过多的问题,本实例中采用了如下优化方式:

     1. 保障不重复下载

     2. 如果存在一下载任务,下载url与新的下载任务url不一致但显示位置一致时,cancel掉之前的AsyncTask。

     3. 修改ThreadPoolExecutor,这是优化的重点。

      从上文描述中可知,当我们通过AsyncTask 的executeOnExecutor来执行异步任务时,携带第一个参数是自定义的Executor,可以通过自定义Executor来优化。参照AsyncTask中提供的默认SerialExecutor,重写一Executor如下:

public class NewExecutor implements Executor {

    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 ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };

    private final BlockingQueue<Runnable> mPoolWorkQueue = new LinkedBlockingQueue<Runnable>(10);
    private final ThreadPoolExecutor mThreadPoolExecutor;

    public NewExecutor() {
        this(CORE_POOL_SIZE);
    }

    public NewExecutor(int poolSize) {
        mThreadPoolExecutor = new ThreadPoolExecutor(
                poolSize,
                MAXIMUM_POOL_SIZE,
                KEEP_ALIVE,
                TimeUnit.SECONDS,
                mPoolWorkQueue,
                sThreadFactory);
    }

    public int getPoolSize() {
        return mThreadPoolExecutor.getCorePoolSize();
    }

    public void setPoolSize(int poolSize) {
        if (poolSize > 0) {
            mThreadPoolExecutor.setCorePoolSize(poolSize);
        }
    }

    public boolean isFull() {
        return mThreadPoolExecutor.getActiveCount() >= mThreadPoolExecutor.getCorePoolSize();
    }

    @Override
    public void execute(final Runnable r) {
        mThreadPoolExecutor.execute(r);
    }
}

       以上执行器与AsyncTask默认的Executor存在两个差别,1是可以通过构造函数来修改corePoolSize,2是可以isFull方法查询线程执行器是否忙,通过这个标示可以知道还能否继续往线程池中加任务。通过这项改进,应用程序便可通过不同的方式来实现优化了。一旦发现线程池满,剩下的事情都好说了,至于再怎么优化就不是难事了,这里不再深入讨论。