解决 Android 中 AsyncTask 的多线程阻塞问题
Android 开发中执行耗时操作并更新 UI 时,通常有三种方式:
直接调用
runOnUiThread(new Runnable(){})
,使用简单,但不能在Activity
之外的环境使用,如View
、Dialog
等;使用
AsyncTask
实现,通过onPreExecute()
、doInBackground()
、onPostExecute
三个方法能方便的分开 UI 操作和耗时操作,避免 UI 线程阻塞,并且支持参数传递;Handler
结合Message
实现,比较重量级,但还需要用到Looper
等,而且Message
封装的Bundle
对象不能太大,否则会抛异常。
几种方式各有利弊,实际开发中应根据需要选取合适方式, 如 Activity
中可采用方法 1 ,其他场合可使用方式 2 和 3 。
公司的项目中最近经常出现数据加载不出来的问题,而且切换网络环境后,出现概率会变小,因此起初一直以为是 HTTP 请求模块的问题,将 Socket 的 timeout 设置为一分钟后,一开始确实没有再重现,但过了几天问题又重现了,于是通过转包并请后台 API 相关开发人员查看日志分析,发现请求根本都没发出去!这样进一步确定是 APP 这边的问题,但究竟出在哪里一片茫然。
今天进行代码重构,涉及到 AsyncTask
这部分代码,通过 Debug 竟然发现 onPreExecute()
方法执行之后迟迟不进入 doInBackground()
方法!看来这就是问题所在。前一个 task 执行后,后一个 task 一直无法从 pending 状态进入 running 状态,说明线程阻塞了。
看了源码之后发现,AsyncTask
内部是通过 java.util.concurrent
下的 ThreadPoolExecutor
线程池实现的:
public static final Executor THREAD_POOL_EXECUTOR =
new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
不过 2.3 以后新的 API 中线程池容量设置改动了(活动线程数由 10 改为 1),所以很容易阻塞。
2.2.2_r1 :
private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 10;
2.3_r1 :
private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 1;
所幸 API 11 提供了一个 executeOnExecutor(Executor exec, Params... params)
方法,可以自定义线程池。
并且内部除了前面提到的 THREAD_POOL_EXECUTOR
,还另外提供了一个 Executor
类型的常量 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);
}
}
}
首先字面上就能看出是串行的,然后内部 scheduleNext()
方法的 synchronized
关键字也能看出来不能并发执行多任务。
所以要想比较好的支持并发,还得自己定制线程池:
private static final int corePoolSize = 15;
private static final int maximumPoolSize = 30;
private static final int keepAliveTime = 5;
private static final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(maximumPoolSize);
private static final Executor threadPoolExecutor = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue);
void addTaskInPool(AsyncTask asyncTask, Params... params) {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
asyncTask.executeOnExecutor(threadPoolExecutor, params);
}else{
asyncTask.execute(params);
}
}
其中 corePoolSize
和 maximumPoolSize
可以根据处理器数目确定:
int procNum = Runtime.getRuntime().availableProcessors();
这样同时跑多个 task 线程就毫无压力了。