概要
AsyncTask
在后台执行操作,并把结果发布到 UI 线程,避免手动调用 Thread 和 Handler.
官网提到 AsyncTask
适用于短时间操作,最多几秒时间。why? 后面源码分析说明。
官网还提到,如果执行长时间操作,强烈推荐我们使用并发包下的 Executor
, ThreadPoolExecutor
, FutureTask
。 这个怎么玩呢? 后面举例。
例子
我把官网的小例子改编了下
private class DownloadFilesTask extends AsyncTask<URL, Void, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
}
return totalSize;
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
执行 AsyncTask
代码如下
new DownloadFilesTask().execute(url1, url2, url3);
构造方法
给开发者都调用的构造方法只有无参的构造方法
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
public AsyncTask() {
this((Looper) null);
}
从注释看,构造方法必须在 UI thread
中调用,why? 然后调用了带 Looper
参数的构造方法
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);
}
}
};
}
这个构造函数只初始化了三个变量 mHandler
, mWorker
, mFuture
。
mWorker
类型是 Callable
,是用来执行的任务。
mFuture
最主要的作用是用来获取执行的结果,例如 AsyncTask
的 get()
方法就是用 mFuture
来获取结果的。这里还复写了 done()
方法,也就是任务 mWorker
任务执行完毕后,会调用的,这里只是简单保证了结果会被发送到 UI Thread
中。
mHandler
因为传入的参数为 null
,所以它的值为 getMainHandler()
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());
}
return sHandler;
}
}
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;
}
}
}
也就是说 mHandler
也就是用主线程 Looper
创建的一个 Handler
对象, 用来传递结果,以及更新进度。
这也就回答了为何要在 UI thread
中创建 AsyncTask
对象,因为要保证用 Handler
能把消息发送回 UI thread
。
execute()
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
@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)");
}
}
// 更新 asyncTask 状态
mStatus = Status.RUNNING;
// 后台任务执行前调用
onPreExecute();
mWorker.mParams = params;
//执行任务
exec.execute(mFuture);
return this;
}
execute()
方法实际是调用了 executeOnExecutor()
方法,这两个方法都加上了 @MainThread
注解,告诉这两个方法需要在 main thread
中调用,原理与前面一样。
executeOnExecutor()
中通过对 mStatus
的判断的那段代码,就可以知晓为何一个 AsyncTask
对象只能调用一次 execute()
方法了。
executeOnExecutor()
最后用 sDefaultExecutor
的 execute()
方法执行了 mFuture
任务。看看 sDefaultExecutor
public static final Executor THREAD_POOL_EXECUTOR;
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;
}
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
是静态的对象,也就说所有的 AsyncTask
共享一个 sDefaultExecutor
,那么如果创建了 N 个 AsyncTask
对象并且调用 execute()
方法,都会用 sDefaultExecutor
执行任务。
sDefaultExecutor
决定了 AsyncTask
执行任务的顺序,是顺序执行任务还是并行执行任务。 默认的是用的 SerialExecutor
,它保证任务的顺序执行,怎么保证了?
SerialExecutor
的 execute()
和 scheduleNext()
方法都添加了 synchronized
关键字,也就是任意时刻,只有一个任务能提交,而且从代码逻辑 看,提交之后,必须执行完,才能提交第二个任务并执行,如此循环下去,就保证了任务的顺序执行。
OK, 现在回到正题, 现在是用 sDefaultExecutor
提交了 mFuture
, 而 mFuture
是由 mWorker
构造,那么就会执行 mWorker
的 call()
方法
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;
}
};
call()
方法中调用了喜闻乐见的 doInBackground()
方法,注意,这个方法是在后台线程中执行的,由 sDefaultExecutor
保证的。
然后在异常处理的代码中发现,在发生异常的时候,首先会把任务设置为取消状态,然后再抛出。
在 finally
中,用 postResult(result)
把结果发送出去,最后会传递到 UI thread
中,代码如下
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
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;
}
在 finish()
方法中可以看到,如果任务取消了(前面提到的异常 或者 手动调用 asyncTask.cancel(boolean)
),就会调用 onCancelled()
回调,如果是正常流程,就执行 onPostExecute()
回调。
并发执行任务
默认情况下,AsyncTask
是顺序执行任务,那么可不可以并行执行任务呢?我们只要改变执行任务的 Executor
就行了。 setDefaultExecutor()
方法可以改变默认的 Executor
,但是对开发者来说这个方法是不可以的
/** @hide */
public static void setDefaultExecutor(Executor exec) {
sDefaultExecutor = exec;
}
这个方法是隐藏的,why? 因为一旦开发者改变了 AsyncTask
的 sDefaultExecutor
,一会并行执行一会顺序执行,那岂不是乱套了~
那么利用 executeOnExecutor()
方法自己传入 Executor
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {}
AsyncTask
同时也提供了一个线程池用来并行执行任务
public static final Executor THREAD_POOL_EXECUTOR;
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;
}
也就是说 new MyAsyncTask().execute(THREAD_POOL_EXECUTOR, url1, url2, url3)
就可以并行执行多个任务。
那有人会问,既然可以并行,为何默认要顺序执行? 问题就出在 ThreadPoolExecutor
上,如果任务的处理速度超过了任务的提交速度,那么并行完全 OK。 然而,如果提交的任务的速度远远大于任务的处理速度,那么 CPU 超负荷,性能得不到保障。因此,如果想要并行,就要考虑如何构造 ThreadPoolExecutor
对象。
进度更新
到现在为止,我还没有提到如何更新进度,官方的例子如下
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled()) break;
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
从例子中可以看到,在 doInBackground()
方法中,调用了 publishProgress()
方法来发布进度,源码如下
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
private static class InternalHandler extends Handler {
// ...
@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;
}
}
}
publishProgress()
是通过 mHandler
把消息传回 UI thread
,然后回调了 onProgressUpdate()
(这个方法在例子中复写了)。 但是我们要注意,只有在这个任务没有被取消的情况下,才能发布进度。
如何正确取消任务
可能通过 AsyncTask
的 cancel()
方法取消任务
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
从源码实现看,参数 mayInterruptIfRunning
代表是否中断线程。如果线程不能响应中断,那么这种方式是无法取消任务的,只能等后台任务执行完毕,然后回调 onCancelled()
而不是 onPostExecute()
。
哪些情况线程能响应中断,请参考
Thread
类的interrupt()
方法.
例子中,在 doInBackground()
方法中,用 for
循环,进行了多个下载任务,需要注意的是在 publishProgress()
方法后,会调用 isCancelled()
来判断任务是否取消,如果取消了就不再进行下一个下载任务。 这给了我们一个很好的示范,如果在 doInbackground()
中通过循环来处理一些事情,需要在恰当的位置判断任务是否中断,从而决定任务是否进行下一次循环。
执行长时间任务
开头提到,官网说如果运行的不是几秒的任务,而是长时间的任务,就不要用 AsyncTask
,而是用 Executor
, ThreadPoolExecutor
, FutureTask
。 其实这是因为 AsyncTask
默认顺序执行任务,如果某一个任务执行时间过长,会导致后面的任务延迟太长,而如果你很有把握,可以并发执行,也就不存在所谓的不能执行长时间任务了。
那么,既然官网都说了这茬,那么如何做呢? 如果你对 Java 并发的这些类掌握得比较好的话,就很容易了。
demo 代码如下
public class TestActivity extends AppCompatActivity {
private TextView mTextView;
private Handler mUIHandler;
private FutureTask<String> mFutureTask;
private ExecutorService mService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_acrtivity);
mTextView = findViewById(R.id.tv);
mService = Executors.newCachedThreadPool();
mFutureTask = new FutureTask<String>(new Task()) {
@Override
protected void done() {
runOnUiThread(new Runnable() {
@Override
public void run() {
mUIHandler.sendEmptyMessage(666);
}
});
}
};
mUIHandler = new Handler(getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (msg.what == 666) {
try {
mTextView.setText(mFutureTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
};
}
@Override
protected void onResume() {
super.onResume();
mService.execute(mFutureTask);
}
private static class Task implements Callable<String> {
@Override
public String call() throws Exception {
Log.d("david", "doInBackground ... ");
// 假如这里3秒代表的是30秒,甚至更长时间运行的任务
TimeUnit.SECONDS.sleep(3);
return "Download has completed!";
}
}
}
原理与 AsyncTask
一致,我就不献丑解释代码了。
那么,你可能有疑问,这明明可以通过 Thread
和 Handler
实现的,为何要搞这么复杂。 简单的理由有几个
- Thread 类执行任务无法查询执行的结果,也没有 API 调用来取消任务。
- 并发任务需要用
ThreadPoolExecutor
,而 Thread 类无法做到这一点。
其实把代码做下封装,基本上就是 AsyncTask
了。