AsyncTask
实现原理并不复杂,无非是使用 Thread
和 Handler
,将耗时操作从UI线程中转移到后台线程中,同时便于后台线程向UI线程返回执行结果。但具体实现细节却有不少技巧。下面我们从一个简单的 AsyncTask
原型,逐步完善、丰富,最后对比下它跟Android SDK 中的 AsyncTask
有什么不足,学习 AsyncTask
中的各种技巧。
BgTask
BgTask v0.1
下面是最初的实现。我们有时会直接在 Activity
中使用 Thread
和 Handler
来完成一些耗时操作(但通常执行时间最多十几秒而已),如果将这部分代码简单封装下,它大概是这个样子:
/**
* v0.1 最原始的AsyncTask, 初步简化异步任务, 避免耗时操作阻塞UI.
*
* 主要问题: 未实现 publishProgress, 无法向UI发布后台线程进度
*
* @param <T>
*/
public abstract class BgTask<T> {
protected static final int FINISH = 1000;
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case FINISH:
onPostExecute();
break;
default:
break;
}
};
};
public BgTask<T> execute(final T param) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
doInBackground(param);
mHandler.obtainMessage(FINISH).sendToTarget();
}
});
onPreExecute();
thread.start();
return this;
}
protected void onPreExecute() {
}
protected void onPostExecute() {
}
protected abstract void doInBackground(T param);
}
使用一个简单的 Activity
不难验证这段代码的确可以避免耗时操作阻塞UI线程。它的用法跟 AsyncTask
很像:
- 继承
BgTask
,至少要实现doInBackground
方法,该方法在一个新建的后台线程中运行 onPreExecute
用于耗时操作开始前的准备工作,onPostExecute
用于耗时操作结束后的处理工作, 这两个方法都在 UI 线程运行
考虑这样一种情形,使用 BgTask
从网络下载一个文件,需要向用户展示下载进度。很明显我们没法实现这一需求,所以 BgTask
的主要问题之一是: 未实现 publishProgress, 无法向UI发布后台线程进度。
BgTask v0.2
这个版本中我们要解决”无法向UI线程发布后台线程进度”的问题。解决的办法其实很简单,增加如下两个方法:
protected void onProgress(K progress) {
}
protected void publicProgress(K progress) {
Message msg = mHandler.obtainMessage(PROGRESS);
msg.obj = progress;
msg.sendToTarget();
}
完整的代码如下:
/**
* v0.2 增加 publicProgress 方法和 onProgress 回调
*
* 主要问题: 后台线程结束后没有返回结果, 考虑这个情形:后台线程发一个http请求, 完成后该线程居然没法将http响应结果返回给UI
*
* ---
*
* v0.1 最原始的AsyncTask, 初步简化异步任务, 避免耗时操作阻塞UI.
*
* 主要问题: 未实现 publishProgress, 无法向UI发布后台线程进度
*
* @param <T>
*/
public abstract class BgTask2<T, K> {
private static final int FINISH = 1000;
private static final int PROGRESS = 1001;
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case FINISH:
onPostExecute();
break;
case PROGRESS:
onProgress((K)msg.obj);
break;
default:
break;
}
};
};
public BgTask2<T, K> execute(final T param) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
doInBackground(param);
mHandler.obtainMessage(FINISH).sendToTarget();
}
});
onPreExecute();
thread.start();
return this;
}
protected void onPreExecute() {
}
protected void onPostExecute() {
}
protected void onProgress(K progress) {
}
protected void publicProgress(K progress) {
Message msg = mHandler.obtainMessage(PROGRESS);
msg.obj = progress;
msg.sendToTarget();
}
protected abstract void doInBackground(T param);
}
onProgress
跟其它 onXXX
回调类似,它在这里用于UI线程接收后台线程进度通知。而 publishProgress
则向 Handler
发送 Message
,用于通知当前进度。 Handler
是 publishProgress
和 onProgress
之间的纽带。有人会问,这真的实现后台线程进度问题了吗?其实还没完全解决,这里有一个隐含的假设:就是继承 BgTask
实现其 doInBackground
方法时你需要在恰当的时机调用 publishProgress
,不然 onProgress
不会被回调,UI 线程也无法接收到进度通知。
下面演示了该如何调用 publishProgress
:
private void runTask() {
mTask = new BgTask2<String, Integer>() {
private int mProgress = 0;
@Override
protected void doInBackground(String param) {
int timeout = 5;
while (timeout > 0) {
try {
Thread.sleep(1000);
publicProgress(mProgress + 20 * (5 - timeout));
} catch (InterruptedException e) {
e.printStackTrace();
}
timeout--;
}
}
@Override
protected void onPreExecute() {
super.onPreExecute();
mStatus.setText("onPreExecute\n");
}
@Override
protected void onPostExecute() {
super.onPostExecute();
mStatus.append("onPostExecute");
}
@Override
protected void onProgress(Integer progress) {
super.onProgress(progress);
mStatus.append("Progress: " + progress);
mStatus.append("\n");
}
};
mTask.execute("hello, AsyncTask");
}
但新的问题又来了,虽然我可以知道进度了,比如进度为100代表执行完成,那我怎么知道这时的执行结果呢?显然,这里还存在问题, 后台线程结束后无法向UI线程返回结果。
BgTask v0.3
onPostExecute
用于向UI线程通知结果最合理,所以我们的做法是给它加上一个泛型参数。修改后的代码如下:
/**
* v0.3 doInBackground 方法增加返回值, 可以将后台线程的最终结果返回给UI线程
*
* 主要问题: 无法暂停, 无法在运行过程中获取结果
*
* ---
*
* v0.2 增加 publicProgress 方法和 onProgress 回调
*
* 主要问题: 后台线程结束后没有返回结果, 考虑这个情形:后台线程发一个http请求, 完成后该线程居然没法将http响应结果返回给UI
*
* ---
*
* v0.1 最原始的AsyncTask, 初步简化异步任务, 避免耗时操作阻塞UI.
*
* 主要问题: 未实现 publishProgress, 无法向UI发布后台线程进度
*
* @param <T>
*/
public abstract class BgTask3<T, K, V> {
private static final int FINISH = 1000;
private static final int PROGRESS = 1001;
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case FINISH:
onPostExecute((V) msg.obj);
break;
case PROGRESS:
onProgress((K) msg.obj);
break;
default:
break;
}
};
};
public BgTask3<T, K, V> execute(final T param) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
V result = doInBackground(param);
Message msg = mHandler.obtainMessage(FINISH);
msg.obj = result;
msg.sendToTarget();
}
});
onPreExecute();
thread.start();
return this;
}
protected void onPreExecute() {
}
protected void onPostExecute(V result) {
}
protected void onProgress(K progress) {
}
protected void publicProgress(K progress) {
Message msg = mHandler.obtainMessage(PROGRESS);
msg.obj = progress;
msg.sendToTarget();
}
protected abstract V doInBackground(T param);
}
看看还有什么问题。哦,貌似只能调用 BgTask3.execute
来开始执行,但如果突然想取消执行呢?
BgTask v0.4
考虑如何实现 cancel
方法。 我们可以添加一个 boolean 成员变量,cancel
后将其置为 true
。但这种简单的做法无法 interrupt 一些可中断的 Thread
,比如处于 sleep 状态的 Thread
(线程的几种状态)。所以倒不如使用 Future
。当然,如果使用 Future
,得引入线程池了。代码如下:
private static ExecutorService SERVICE = Executors.newFixedThreadPool(3);
private Future<V> mFuture;
public BgTask4<T, K, V> execute(final T ...param) {
// Thread thread = new Thread(new Runnable() {
//
// @Override
// public void run() {
// V result = doInBackground(param);
// Message msg = mHandler.obtainMessage(FINISH);
// msg.obj = result;
// msg.sendToTarget();
// }
// });
Callable<V> c = new Callable<V>() {
@Override
public V call() throws Exception {
V result = doInBackground(param);
Message msg = mHandler.obtainMessage(FINISH);
msg.obj = result;
msg.sendToTarget();
return result;
}
};
onPreExecute();
// thread.start();
mFuture = SERVICE.submit(c);
return this;
}
public void cancel() {
if (mFuture != null) {
boolean res = mFuture.cancel(true);
Log.i("BgTask4", "Cancel future: " + res);
}
}
public V get() throws InterruptedException, ExecutionException {
if (mFuture != null) {
return mFuture.get();
}
return null;
}
AsyncTask
我们实现了一个基本的 BgTask
,功能类似于 AsyncTask
,完成耗时操作时不必直接操作 Thread
和 Handler
。 但它还有很多不完善的地方。
cancel
方法线程不安全- 无法区分是正常结束还是 cancel
- 无法知道
AsyncTask
当前执行状态 - 泛型参数定义得很不清晰
看看 Android SDK 中的 AsyncTask
是怎么实现的吧。 注意:不同 Android 版本中,AsyncTask
源码有不小变化,这里以 android 4.4 (android-19) 为准。
线程池
AsyncTask中的线程池在不同版本的Android中并不相同,是个不小的坑。
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
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());
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
/**
* 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);
/**
* An {@link Executor} that executes tasks one at a time in serial
* order. This serialization is global to a particular process.
*/
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
我们逐一分析:
- CPU_COUNT - CPU核心数
- CORE_POOL_SIZE - the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set。这里的大小是 CPU_COUNT 加 1
- MAXIMUM_POOL_SIZE - the maximum number of threads to allow in the pool. 线程池中允许的最大线程数, 这里的大小是 CPU_COUNT 的两倍加 1
- KEEP_ALIVE - when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
- sThreadFactory - 这个 ThreadFactory 没什么特别,仅用于保持线池名字以 “AsyncTask #” 的形式有序增加
- sPoolWorkQueue - the queue to use for holding tasks before they are executed. This queue will hold only the Runnable tasks submitted by the execute method. 用于构建 THREAD_POOL_EXECUTOR
- THREAD_POOL_EXECUTOR - 用于 AsyncTask 并发执行的线程池
比较有意思的是这里的 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);
}
}
}
一开始会感觉这种用法有点奇怪,弄这么麻烦,不如直接使用一个 Executors.newSingleThreadExecutor()
来得直接。仔细分析,这里使用了装饰器模式 (也可能认为代理模式,它们太像了,装饰模式与代理模式的区别),使用 ArrayDeque
将原来的可用于并发执行的线程池装饰成一个仅能顺序执行的线程池。
如果还是觉得奇怪,不妨看看 Executor
的Java文档中的示例,官方的,绝对权威。它是这么写的(这回该能确定它是装饰器模式了吧):
class SerialExecutor implements Executor {
final Queue<Runnable> tasks = new LinkedBlockingQueue<Runnable>();
final Executor executor;
Runnable active;
SerialExecutor(Executor executor) {
this.executor = executor;
}
public synchronized void execute(final Runnable r) {
tasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (active == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((active = tasks.poll()) != null) {
executor.execute(active);
}
}
}
WorkerRunnable 与 FutureTask
WorkerRunnable
不过实现了 Callable
接口。我们知道 Callable
接口基本可认为是 Runnable
接口的带参数版本。除此之外,WorkerRunnable
还有一个 Params[]
类型的成员变量,方便保存 execute
传进来的参数。
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
...
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
FutureTask
是什么?不好理解,那还是看代码吧。
public class FutureTask<V> implements RunnableFuture<V> {}
public interface RunnableFuture<V> extends Runnable, Future<V> {}
原来 FutureTask
同时实现了 Runnable
接口和 Future
接口,AsyncTask.execute
其实是将 FutureTask
提交给 Executor
。
分析代码可知:
- mTaskInvoked - 用于标识任务是否已执行,它是 AtomicBoolean 类型的,不存在线程安全问题
- WorkerRunnable.call - 在新的线程中运行,doInBackground 正常结束后其返回值通过
postResult
发送到UI线程 - FutureTask.done - 在新的线程中运行,如果
AsyncTask
不能正常结束,通过postResultIfNotInvoked
将FutureTask.get
的结果发送到UI线程
以上第2条和第3条保证无论 AsyncTask
是否正常结束,都会有一个唯一确定的结果由 InternalHandler
处理:
private static class InternalHandler extends Handler {
@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.get()
,会发生什么情况
AsyncTask.get()
直接调用 FutureTask.get()
,所以很容易这样验证:
public static void main(String[] args) {
FutureTask<String> f = new FutureTask<String>(new Callable<String>() {
@Override
public String call() throws Exception {
return "test";
}
});
// Executors.newSingleThreadScheduledExecutor().execute(f);
try {
System.out.println(f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
如果我们将 Executors.newSingleThreadScheduledExecutor().execute(f);
注释掉,这个名为 f 的 FutureTask
就不会提交给线程池执行,f.get()
方法就一直阻塞在那里,等待返回结果。以下Android代码执行结果就不用解释了吧,后果你懂的。所以这种用法是错误的。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
runAsyncTaskManually();
}
private void runAsyncTaskManually() {
AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {
@Override
protected String doInBackground(Void... params) {
return "test";
}
};
try {
String result = task.get();
Log.i("MainActivity", result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
AsyncTask的状态
AsyncTask 通过 Status 定义其状态:
public enum Status {
/**
* Indicates that the task has not been executed yet.
*/
PENDING,
/**
* Indicates that the task is running.
*/
RUNNING,
/**
* Indicates that {@link AsyncTask#onPostExecute} has finished.
*/
FINISHED,
}
- 初始时是 PENDING 状态,如果是其他状态,立即抛出
IllegalStateException
- 调用
execute
后 立即进入 RUNNING 状态 - InternalHandler 处理完 MESSAGE_POST_RESULT 后进入 FINISHED 状态
AsyncTask 的状态保证其 execute
方法仅能被执行一次。
AsyncTask的坑
cancel的问题
可以在任何时候调用
cancel
取消任务。调用这个方法会导致后续所有的isCanceled
返回true
。 为保证任务尽早被取消,应该周期性地在doInBackground
方法中检查isCanceled
的返回值 (比如在循环当中)
注意: cancel
可能并不及时,特别是 doInBackground
没有按要求实现时。比如,根据一组url循环下载文件,每次下载前应检查 isCanceled
是否为 true
。如果不这么做,cancel
肯定不生效。另一种情况是,当前 doInBackground
读取一个 InputStream
并阻塞,简单地 cancel
并不能让这个过程取消。
线程池问题
AsyncTask中提到
首次引入
AsyncTask
时,它是在唯一的后台线程中顺序执行任务的。从DONUT
开始,单一线程改成了一个允许多个线程并发执行的线程池。 (注:线程池,corePoolSize 为 5, maximumPoolSize 为 128, keepAlive 时间为 1秒)。从HONEYCOMB
开始,为避免并发执行引起一些常见的编程错误,又重新改成了单线程。(注:从代码来看并不是真正的单线程,但 AsyncTask 中引入 SerialExecutor 保证所有的任务是顺序执行的)。
当然,文档中还提到多线程并发执行的问题,
如果确实需要并发执行,可以使用
AsyncTask.executeOnExecutor
方法,注意第一个参数传入THREAD_POOL_EXECUTOR
即可。
看来,Android确实在不但打磨啊。最初由单线程换成线程池,应该是前者性能不太好。现在Android手机性能基本足够了,性能不是问题,多线程反而容易引起问题,为了减少这些问题,干脆又换成单线程。反正,换或不换,总是有道理的。