Android 异步处理 AsyncTask实现

来源:互联网 发布:如何保存淘宝视频 编辑:程序博客网 时间:2024/05/19 06:36
       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方法查询线程执行器是否忙,通过这个标示可以知道还能否继续往线程池中加任务。通过这项改进,应用程序便可通过不同的方式来实现优化了。一旦发现线程池满,剩下的事情都好说了,至于再怎么优化就不是难事了,这里不再深入讨论。


       
    




0 0
原创粉丝点击