AsyncTask缺陷

AsyncTask方便了我们在子线程中更新UI的操作,但是AsyncTask本身存在着很多的问题,如果使用的时候不注意的话,会产生意想不到的错误,下面就列举一下需要注意的地方:

1、生命周期问题与内存泄露

很多开发者会认为一个在Activity中创建的AsyncTask会随着Activity的销毁而销毁,然而事实并非如此。AsyncTask会一直执行,直到doInBackground()方法执行完毕。如果AsyncTask被声明为Activity的非静态内部类,那么AsyncTask会持有对Activity的引用,如果我们在销毁活动的时候没有取消正在运行的AsyncTask,这会导致Activity没法被回收,从而导致内存泄露。所以,我们必须确保在销毁活动之前取消任务。

另外,即使我们调用了cancel()方法也未必能真正地取消任务,一个原因是因为可能在doInBackgroud里有一个不可中断的操作,比如BitmapFactory.decodeStream(),另一个原因是cancel只是会设置标识位,并不会中断运行,需要我们自己在doInBacnkGround中进行判断,如果我们没有进行判断的话,单纯的调用cancel()方法是没有效果的。关于cancel方法的使用,请参考:AsyncTask的cancel方法解读

关于如何正确使用AsyncTask避免内存泄漏,请参考:AsyncTask内存泄露

2、并行还是串行容易混淆

在Android 1.6之前的版本,AsyncTask是串行的,在1.6至3.0的版本,改成了并行的。在3.0之后的版本又做了修改,可以支持并行和串行,当想要串行执行时,直接执行execute()方法,如果需要并行执行,则要执行executeOnExecutor(Executor)。

3、资源浪费(并行版本)

对于某些并行版本的AsyncTask,在 AsyncTask 全部执行完毕之后,进程中还是会常驻 corePoolSize 个线程

在后续的版本中做了优化:

1
2
3
4
5
6
7
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS,TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}

关于线程池中keepAliveTime中参数的作用,建议阅读:Java并发编程:线程池的使用

4、容易崩溃(并行版本)

Android 3.0之前(1.6之前的版本不再关注)规定线程池的核心线程数为5个(corePoolSize),线程池总大小为128(maximumPoolSize),还有一个缓冲队列(sWorkQueue,缓冲队列可以放10个任务),当我们尝试去添加第139个任务时,程序就会崩溃。当线程池中的数量大于corePoolSize,缓冲队列已满,并且线程池中的数量小于maximumPoolSize,将会创建新的线程来处理被添加的任务。如下图会出现第16个Task比第6-15个Task先执行的情况。

此处输入图片的描述

对于并行版本的AysncTask可以同时开启多个线程执行多个任务,因为同一个进程里所有用到AsyncTask的地方都是同一个线程池,导致任务缓存队列就容易满,一旦满了,再往里面添任务,就抛异常挂掉了。详情请阅读:AsyncTask的缺陷&ThreadPoolExecutor线程池解析与BlockingQueue的三种实现&AsyncTask一些注意的问题

5、处理不当容易导致任务阻塞(串行版本)

对于串行版本的AsyncTask,因为AsyncTask中负责排队的线程池是一个静态的成员变量,所以如果一个AsyncTask的doInBackGround方法中存在着一个无限循环没有正确处理,导致这个AsyncTask的doInBackGround方法一直运行,那么这会导致同一个进程中其他的AsyncTask的任务无法执行。这点从AsyncTask的中的负责排队的线程池SERIAL_EXECUTOR的源码就可以看出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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()方法从线程池中获取下一个等待执行的任务,如果一个任务一直运行,那么其他任务就必须等待。

6、结果丢失

屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。

参考链接:

Android AsyncTask 解析
AsyncTask内存泄露
内存泄露之Thread
Android 开发中使用AsyncTask
避免Android中Context引起的内存泄露
Android -> 如何避免Handler引起内存泄露
Android App 内存泄露之Thread
坑爹的AsyncTask之内存泄露
AsyncTask的缺陷和问题

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器