转载请标明出处:http://blog.csdn.net/EdisonChang/article/details/50283969
AsyncTask,顾名思义,异步任务。相信热衷Android的你一定不陌生,对于AsyncTask的用法或者源码的解析,也有各路大神做了比较详细的分析,博主也从中受益很多,感兴趣的朋友,可以阅读[Android AsyncTask完全解析,带你从源码的角度彻底理解]。
博文就不再做重复的解析介绍,这次博主分享的是使用AsyncTask过程中遇到的一些问题和解决方法,希望可以给有同样困惑的朋友带来解决思路。
从Android 3.0(HONEYCOMB_MR2 )开始,AsyncTask实现了SerialExecutor 这个类,SerialExecutor 所展示的是一个单一线程池的效果,即使我们快速的启动了很多异步任务,同一个时刻只会有一个线程处于运行状态,其他的线程都处于等待状态,源码如下:
private static class SerialExecutor implements Executor {
final ArrayDeque mTasks = new ArrayDeque();
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);
}
}
}
代码实现非常简洁,从代码我们也可以看出,首次加入队列的任务会立即得到执行,然后必须等待任务执行完毕,才能在finally块中执行下一个任务,显然在3.0以后的AsyncTask的execute方法,默认实现的是串行操作。
3.0之前的AsyncTask同时可以执行5个任务,而3.0之后默认却只能有一个任务在执行,似乎有点不合理?庆幸的是更新后的AsyncTask提供了executeOnExecutor方法可以灵活的选择使用的线程池,这样就可以解决android 3.0以上使用单一线程池的问题。
所以通常情况下,我们会这么做,
public static void executeAsyncTask(final AsyncTask task){
if(task != null){
if(Build.VERSION.SDK_INT >= AndroidVersionCodes.HONEYCOMB){
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}else{
task.execute();
}
}
}
这样确实能够解决兼容问题,但是细心的朋友会发现,AsyncTask 中所使用的SERIAL_EXECUTOR 和 THREAD_POOL_EXECUTOR都是一个static 对象,意味着进程中所有的new AsyncTask 对象都会运行在同一个线程池当中。倘若应用中的多个模块都用AsyncTask来执行异步任务,而且执行的任务都相对耗时,就会出现长时间排队的现象,这也是使用AsyncTask或者说是使用线程池处理异步任务的局限性。
无巧不成书,最近刚好碰到一个case ,拿出来和大家分享下,应用过程中一个偶现崩溃,堆栈如下,
java.util.concurrent.RejectedExecutionException: Task android.os.AsyncTask 3@424f6c90 rejected from java.util.concurrent.ThreadPoolExecutor@4248d7c8[Running, pool size = 9, active threads = 9, queued tasks = 128, completed tasks = 9]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2011)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:793)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1339)
at android.os.AsyncTask.executeOnExecutor(AsyncTask.java:590)
通常情况使用线程池过程中会有以下场景,
(1)如果运行的线程少于corePoolSize,则Executor始终首选添加新的线程,而不进行排队。
(2)如果运行的线程等于或多于corePoolSize,则Executor始终首选将请求加入队列,而不添加新的线程。
(3)如果无法将请求加入队列,则创建新的线程,当创建此线程超出maximumPoolSize时就会抛出RejectedExecutionException 异常信息。
从崩溃的堆栈信息也可以看出,在使用AsyncTask过程中由于排队异步任务过多,导致线程数超过了maximumPoolSize,从而导致异常。从这里可以看到使用AsyncTask的局限性,要求我们根据使用场景灵活应用,对于一些长时间耗时的任务,可以直接开辟一个线程运行,而不去占用进程中的公共线程池,避免导致阻塞。
当然,针对这一类的问题,也需要有亡羊补牢的办法,毕竟对于任务的耗时也依赖于各种上下文环境因素,并不是很容易预测的。
try {
Object objectExecutor = cls.getField("THREAD_POOL_EXECUTOR").get(null);
if (objectExecutor instanceof ThreadPoolExecutor) {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) objectExecutor;
threadPoolExecutor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
Executors.newSingleThreadExecutor().execute(r);
}
});
}
java.lang.reflect.Method setDefaultExecutor = cls.getMethod("setDefaultExecutor", Executor.class);
setDefaultExecutor.invoke(cls, objectExecutor);
} catch (Exception e) {
}
调用ThreadPoolExecutor 的setRejectedExecutionHandler 方法,当并发线程池满时,可以起一个单独的线程执行任务。
Executors.newSingleThreadExecutor().execute(r)
好了,到这里基本就介绍完了,AsyncTask虽然用起来很舒服,却不是万能的,一定要根据场景善用。有任何问题,欢迎补充指正。