2019-08-22: 说一说对AsyncTask的理解,它是同步的还是异步的?有没有读过相关源码?
doInBackground()这个方法是异步的,其他几个方法是在主线程回调的。
内部维护了两个线程池。
1.执行任务的线程池,就是下面这个
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
// 注意这点,公有的,静态的
public static final Executor THREAD_POOL_EXECUTOR;
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
// 把这个值赋给了 THREAD_POOL_EXECUTOR
THREAD_POOL_EXECUTOR = threadPoolExecutor;
2.任务调度的线程池,姑且这么说吧,其实他是串行的
// 注意这点,公有的,静态的
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);
}
}
}
上边代码中注释标记注意的地方大家都看到了,静态的变量,也就是说,所有的AsyncTask都是用的这两个线程池。
1.AsyncTask不适合做耗时的任务,上边已经分析了,不然别人都得等你。当然,你可以自己指定线程池。这样就可以并行了。但是它默认不这么做了,是什么原因呢?这个还是没想的太明白。 2.使用的时候注意内存泄漏问题。主要说的是做为内部类(所有使用内部类的时候都要注意,因为会持有外部类的引用)使用的时候。
回调简单说一下,就是通过一个Handler,直接看代码好了。
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
AsyncTask
一.概念
AsyncTask 是一种轻量级的异步任务类,它可以在任务线程中执行后台任务,然后把执行的进度传递给主线程并在主线程中更新UI
二.组成
a.组织架构
AsyncTask = Handler + ThreadPoolExecutor
b.参数
abstract class AsyncTask<Params, Progress, Result>
- 泛型类,提供
Params,Progress和Result三个泛型参数.Params:参数类型Progress:后台任务执行类型Result:后台任务返回结果类型Void可以代替这三个参数, 免除参数必传因子
c.核心方法
public class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
@Override
protected Long doInBackground(URL... urls) {
final int length = urls.length;
long totalSize = 0;
for (int i = 0; i < length; i++) {
// 执行文件多线程下载操作
// TODO: totalSize += Downloader.downloadFile(url[i]);
publishProgress((int) ((i / (float) length) * 100));
if (isCancelled()) {
break;
}
}
return totalSize;
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// TODO: 获取下载进度,用Progress展示
}
@Override
protected void onPostExecute(Long aLong) {
// TODO: 显示现在结果操作
}
}
| onPreExecute | |
| doInBackground | publicProgress更新任务进度 |
| onProgressUpdate | |
| onPostExecute | |
| onCancle |
1.执行顺序:
onPreExecute -> doInBackground -> onProgressUpdate
2.开启任务方式:
new DownloadFilesTask().execute(url1,url2,url3);
三.作用
简化子线程访问 UI 的过程.
四.工作原理
源码分析,我们之前说过,首先看调用,AsyncTask执行操作是execute,我们进入execute查看一下:
@MainThread
public static void execute(Runnable runnable) {
// 串行线程池,一个进程中所有的 AsyncTask 全部在这个串行的线程池排队执行
sDefaultExecutor.execute(runnable);
}
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
// onPreExecute 最先执行
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
接下来我们分析一下线程池的执行操作:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
// AsyncTask 有两个线程池, SerialExecutor 使用于任务排列
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
// 2.FutureTask 会交给 SerialExecutor 的 execute 执行
public synchronized void execute(final Runnable r) {
// 同时当一个 AsyTask 任务执行完之后,AsyTask 会继续执行到所有任务被执行为止
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
// 4. 如果里面没有执行活动会调用 scheduleNext 方法
if (mActive == null) {
scheduleNext();
}
}
// 3. execute 会把 FutureTask 对象插入任务队列mTask中
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
onPreExecute();
// 1.AsyncTask 的 params 参数封装给 FutureTask,FutureTask是一个并发类,和Runnable等效
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
THREAD_POOL_EXECUTOR 是用于真正执行任务的,
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
五.注意事项
1. 不适合进行特别耗时的后台任务,如需,请使用:线程池。
2. 不要在程序中直接调用onPreExecute , doInBackground ,onProgressUpdate 和onProgressUpdate方法。
3.一个AsyncTask对象只能执行一次,即只能用一次execute,否则会抛异常。
4. 并行任务请使用excuteOnExecutor,excute会串行。
六.竞品
HandlerThreadIntentServiceThreadPoolExecutorThreadFactoryCallable 和 FutureRunnableThread
为什么AsyncTask只能执行一次,只能用一次execute?
我们点开execute(),可以看到调用了executeOnExecutor()
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
看到executeOnExecutor
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
可以看到这里有一个枚举状态的校验Status共有三种状态,对象初始化时默认为Status.PENDING,其他时候均没有赋值Status.PENDING
而executeOnExecutor一旦被调用一次mStatus状态就会被改变,之后再调用就会抛对应状态的异常了。
为什么AsyncTask默认是串行的?
前面看到默认状态下调用execute方法是带着sDefaultExecutor去调用executeOnExecutor,我们跟一下sDefaultExecutor这个线程池是什么
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
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);
}
}
}
可以看到,每次调用sDefaultExecutor#execute时
-
先把runnable包一层(装饰模式)入队
-
如果目前没有正在执行的任务,即mActive,就尝试从队列中取一个交给另一个线程池去执行
-
每次执行完实际的任务,顺便尝试从队列中取一个交给另一个线程池去执行
由此可见sDefaultExecutor这个静态的线程池就是负责保证任务顺序执行的。 接下来我们看下execute方法是否真的使用了sDefaultExecutor这个线程池去执行任务,上面代码可以看到executeOnExecutor方法在执行onPreExecute()之后就使用sDefaultExecutor去执行任务mFuture,我们看下mFuture是什么:
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
可以看到mFuture包着mWorker是在构造函数中创建的,看到mFuture的run()
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public void run() {
if (state != NEW ||
!U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
...省略代码...
mFuture#run()执行了callable的call方法,而callable正是通过构造带进去的mWorker,其call()方法中赫然就有result = doInBackground(mParams),至此,我们知道了默认情况下用sDefaultExecutor来实现串行,如果你想并行,那就要自己做一个线程池直接调用executeOnExecutor()传进去。
此外,也不难看到AsyncTask四个回调方法中的onPreExecute()是一开始就在调用线程中执行了onPreExecute中又可能做一下跟UI有关的事情,所以规范上execute()必须要主线程调用(如果你的onPreExecute中不需要做一些跟UI有关的事情那就不强求一定要在UI线程调用),而doInBackground()则是在子线程中调用的。
如何确保结果回调onPostExecute()以及进度回调onProgressUpdate()在主线程中执行?
看到mWorker执行完成时调用了postResult(result)
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
这里的Handler跟一下不难发现
private Handler getHandler() {
return mHandler;
}
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());
}
return sHandler;
}
}
除非你在AsyncTask创建时传入指定的Looper,不然都是默认Looper.getMainLooper()
看到具体实现InternalHandler
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
可以明显看到InternalHandler在这里的作用时用来切线程,不出意外都是切回主线程的,
AsyncTask真的必须在主线程中加载吗?为什么大家都这么说?
旧的AsyncTask源码中sHandler的默认创建是这样的
private static final InternalHandler sHandler=new InternalHandler();
不指定Looper的Handler用的是当先线程的Looper,所以如果你第一次使用在子线程中的话,类加载时,静态变量一并创建,那么sHandler的Looper就不在主线程了,后面的onPostExecute()以及onProgressUpdate()就不在主线程中执行了。
但是,我在28的源码中看到的已经不是这种默认创建的方法了,而是摆明车马指定了Looper.getMainLooper(),那么不管你第一次在哪里加载使用,都不影响切换回主线程了。
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());
}
return sHandler;
}
}
在Android中,由于主线程的诸多限制,像网络请求等一些耗时的操作我们必须在子线程中运行。我们往往会通过new Thread来开启一个子线程,待子线程操作完成以后通过Handler切换到主线程中运行。这么以来我们无法管理我们所创建的子线程,并且无限制的创建子线程,它们相互之间竞争,很有可能由于占用过多资源而导致死机或者OOM。所以在Java中为我们提供了线程池来管理我们所创建的线程。
而AsyncTask就是这么个环境下产生的。它本质上是一个静态的线程池(封装了THREAD_POOL_EXECUTOR异步线程池和SERIAL_EXECUTOR同步线程池和Handler),AsyncTask派生出的子类可以实现不同的异步任务,这些任务都是提交到静态的线程池中执行。线程池中的工作线程执行doInBackground(mParams)方法执行异步的任务。当任务状态改变后,工作线程向UI线程发送消息,AsyncTask内部的InternalHandler响应这些消息,并调用相关的回调函数。
AsyncTask使用不当的后果
1.)生命周期
AsyncTask不与任何组件绑定生命周期,所以在Activity/或者Fragment中创建执行AsyncTask时,最好在Activity/Fragment的onDestory()调用 cancel(boolean);
2.)内存泄漏
如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。
3.) 结果丢失
屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask(非静态的内部类)会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。
AsyncTask里面的两个线程池:
1AsyncTask里面有THREAD_POOL_EXECUTOR和SERIAL_EXECUTOR两种方式来异步执行任务;THREAD_POOL_EXECUTOR是异步的,而SERIAL_EXECUTOR任务是顺序执行的。 2. THREAD_POOL_EXECUTOR如果添加的任务过多,没有及时处理的话,会导致程序崩溃,它的队列size是128;它的调度规则是核心池大小,队列大小,以及最大线程数和异常处理Handler来决定的。 3. SERIAL_EXECUTOR本质是在THREAD_POOL_EXECUTOR的基础上添加一个mTasks的集合来保证任务的顺序执行。
大佬们都回答的差不多了,我也简单说一下吧。
AsyncTask主要是用来处理子线程和UI线程互相切换的情景。于执行耗时任务而言,它是执行在子线程中的(内部由一个线程池维护);于多个任务之间的,添加到任务队列而言,如果是直接执行execute方法,它则是同步的,就是说,每一个任务的执行,都必须等上一个任务完成。
当然,你也可以执行executeOnExecutor来接管从入队到到执行所有的实现,让他变成一个可以异步添加,并且异步执行的AsyncTask。
(未完待续...)
AsyncTask主要用来执行耗时操作,同时它把执行进度和结果传递给UI线程,因此很适合一些需要在执行完耗时操作后更新UI或者执行耗时操作过程在UI上显示进度的场景。
AsyncTask内部封装了Handler和线程池,只暴露出五个主要方法给使用者,使用者可以方便地在对应的方法内去做耗时操作或者更新UI。
主要有五个方法:
- onPreExecute:主线程中执行,可以用来做一些执行耗时操作之前的准备工作。
- doInBackground :在子线程中执行,必须实现的方法,用来做耗时操作。可以通过publishProgress方法调用onProgressUpdate显示下载进度。下载完成后会调用onPostExecute方法。
- onProgressUpdate:主线程中执行,调用publishProgress才会触发此方法。
- onPostExecute:主线程中执行,任务执行完成后会调用此方法。
- onCancelled:主线程中执行,任务取消时被调用。
AysncTask对象可以在主线程中创建,但是execute方法按照规范必须在主线程中执行。因为onPreExecute方法是在execute的线程中执行的。而构造函数内部的Handler则默认使用了主线程了Looper,所以AysncTask对象创建的线程不做要求。
AysncTask内部主要有两个线程池,SerialExecutor负责将任务同步加入队列中,ThreadPoolExecutor负责实际执行任务。
大佬们说了很多,我加一点: 任何时候调用cancel(boolean)方法,都可以取消任务。这个方法会产生的影响是:之后调用的iscancelled()会返回true。 这个方法调用之后,onCancelled(Object)方法会在doInBackground(Object[]) 方法返回后执行,而不是onProgressUpdate(Progress... values)方法 鉴于上述据说,为了确保任务是尽快取消,你应该定期在doInBackground()方法中始终检查iscancelled()方法的返回值。如果可以的话,放在一个循环当中。
好久没用过AsyncTask了,不过研究一下内部的代码还挺有意思的。 上边的小伙伴都分析的很厉害。 AsyncTask内部其实就是对线程池和Handler进行了封装。 下边我简单的分析一下代码加深下印象: 1.我们继承了AsyncTask的子类对象调用execute。
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
//下边这一段的意思就是说,一个asyncTask对象只能调用一次execute方法
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
//子线程执行前的事前准备
onPreExecute();
mWorker.mParams = params;
//这里进入正戏
exec.execute(mFuture);
return this;
}
上边的mFuture其实就是线程的执行体,内部在执行的时候放入callable对象进行回调,回调的结果重新传到AsyncTask进行操作。因为mFuture是子线程的线程实体,那结果传到AsyncTask也是在子线程的。这时候我们需要使用Handler将结果从子线程传到主线程。 2.那这个mFuture是怎样放到子线程执行的呢?
private static class SerialExecutor implements Executor {
//双向队列
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
//synchronized的修饰保证了列表中的任务被顺序的加入,也就是会被顺序的执行
public synchronized void execute(final Runnable r) {
//这里加入的就是mFuture,
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
//队列中的一个任务执行完毕之后,我继续执行下一个
scheduleNext();
}
}
});
//往队列里塞了一个任务之后,试着执行一下
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
//从队列中取任务,如果不为空,我就把这个任务放到线程池中
//THREAD_POOL_EXECUTOR是线程池对象,
if ((mActive = mTasks.poll()) != null) {
//至此mfuture被扔到子线程中执行,当然是大致这样说,其实后边还有操作,细节就不说了
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
上边我们看到一个AsyncTask对象只能被执行一次,为什么只能被执行一次呢?普遍的说法是AsyncTask是一个轻量级的执行后台任务的类,Google对它进行了限制,一个任务只能执行一次。
AsyncTask的缺点
不够灵活
我感觉最大的缺点就是不能和Activity的生命周期保持同步,即使在ondestory方法对任务进行cancel,doInBackground这个方法也不会立马停止,也就是说子线程还是有可能会执行结束。 下边是Future中的cancel方法:
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
U.compareAndSwapInt(this, STATE, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
//使用interrupt并不能中断线程的执行,只是改变了线程的中断状态
t.interrupt();
} finally { // final state
U.putOrderedInt(this, STATE, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
不够灵活,我感觉是它最大的缺点。 应付这样的问题,就是避免在AsyncTask做耗时较长的任务,它是一个轻量级的工具。
局限性
能在主线程和子线程执行,但是最后的执行结果都是传到主线程。也就是说只能给主线程服务。而且一个AsyncTask对象只能执行一次, 这个不算是缺点吧,他本来就是一个被定位为轻量级的东西,所以功能少点也正常。
其他
比如内存泄漏;屏幕旋转之后,AsyncTask内引用的Activity对象改变等
请教一下楼主。既然AsyncTask只能执行一次,为什么它还需要一个THREAD_POOL_EXECUTOR这个线程池呢?THREAD_POOL_EXECUTOR难道是所有AsyncTask共用的吗?谢谢。