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方法查询线程执行器是否忙,通过这个标示可以知道还能否继续往线程池中加任务。通过这项改进,应用程序便可通过不同的方式来实现优化了。一旦发现线程池满,剩下的事情都好说了,至于再怎么优化就不是难事了,这里不再深入讨论。