这是我参与更文挑战的第11天,活动详情查看: 更文挑战
AsyncTask 的使用方式
使用示例
private class DownloadFileTask extends AsyncTask<String, Integer, Long> {
@Override
public void onPreExecute() {
mProgress.setVisibility(View.VISIBLE);
mProgress.setMax(100);
mProgress.setProgress(0);
}
@Override
public Long doInBackground(String... uris) {
int count = uris.length;
long size = 0;
for (int i = 1; i <= count; i ++) {
try {
// 休眠5秒模拟下载过程
Thread.sleep(5 * 1000);
// 假设每个下载文件的大小为(序号*100)
size += i * 100;
// 发布进度更新
publishProgress( (100* i )/count);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
return size;
}
@Override
public void onProgressUpdate(Integer... progress) {
mProgress.setProgress(progress[0]);
}
@Override
public void onPostExecute(Long result) {
mText.setText(String.valueOf(result));
}
}
通过这段代码可以看到要使用AsyncTask实行异步任务是非常容易的,只需要做两件事:
- 确定在整个处理过程中需要的参数类型,包括
Params,Progress和Result,分别对应着输入参数、进度参数和结果参数。 - 实现必要的回调方法,其中必须是实现的是
doInBackground,耗时任务正是在这里进行处理的,可以想象doInBackground一定是在子线程里进行的;其他可选实现方法包括onPreExecute,onProgressUpdate和onPostExecute,这些在示例中都参与了UI的更新,所以一定是在主线程中进行的。
参数介绍
public abstract class AsyncTask<Params, Progress, Result> { ... }
可以发现AsyncTask中使用的都是泛型参数,在使用过程中要根据需求选择合适的参数类型,在示例中使用的参数类型分别是String,Integer和Long,如果某一个参数是不需要的,可以用Void来表示,下面通过一个表格来对每个参数进行说明:
| 参数声明 | 含义 | 作用 | 产生处/调用处 | 注意事项 |
|---|---|---|---|---|
| Params | 输入参数 | 任务开始执行时客户端发送开始参数 | execute()中发送,在doInBackground()中调用。 | 可变参类型 |
| Progress | 过程参数 | 任务后台执行过程中服务端发布的当前执行进度 | 在doInBackground()中产生并通过publishProgess()发送,在onProgressUpdate()调用。 | 可变参类型 |
| Result | 结果参数 | 任务执行完成后服务端发送的执行结果 | 在doInBackground()中产生并在onPostExecute()中调用。 |
参数类型不能是基本数据类型,要使用对应的封装类型,例如示例的
Progress和Result参数使用的Integer和Long而不是int和long。
回调接口
| 回调方法 | 运行线程 | 作用 | 执行顺序 | 是否需要重新实现 |
|---|---|---|---|---|
| onPreExecute | 主线程 | 在开始执行后台任务前进行初始化 | 首先开始执行 | 可选 |
| doInBackground | 后台线程 | 执行后台耗时任务完成后返回结果 | onPreExecute 执行完成后执行 | 必须实现 |
| publishProgress | 后台线程 | 在执行任务过程中发布执行进度 | 在 doInBackground 中执行 | 无须实现,直接调用。 |
| onProgressUpdate | 主线程 | 接收进度并在主线程处理 | 在 publishProgress 之后执行 | 可选 |
| onPostExecute | 主线程 | 接收执行结果并在主线程处理 | 在 doInBackground 执行完成后执行 | 可选 |
AsyncTask 源码分析
回到我们一开始提到的那个示例代码,当我们定义了好自己的AsyncTask之后,要开始运行这个任务时非常简单,只需要一行代码:
new DownloadFileTask().execute(url1, url2, url3);
我们就以这个为切入点来分析,首先看下execute()做了什么
首先,new DownloadFileTask() ,执行DownloadFileTask的构造方法,因此必然会执行DownloadFileTask的父类AsyncTask的构造方法,也就是 AsyncTask() :
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
public AsyncTask() {
this((Looper) null);
}
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);
}
}
};
}
构造方法的工作很简单,就是完成了mWorker 和 mFuture 的初始化工作,也就是Callable和Future 的初始化,并关联他们,让mFuture 可以获取mWorker 的执行结果,或者停止mWorker 的执行。
这里主要由两个方法call()和done(),总的来说当mFuture 开始被执行的时候,call() 就会执行,当这个任务执行完毕后done()方法就会执行。
那么这个mFuture 什么 时候会被执行呢?继续往下看execute(Params... params)
@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)");
}
}
mStatus = Status.RUNNING;
// 回调方法中首先被调用的方法,由于"execute()"是在主线程中执行的,
// 目前为止也没有进行线程的切换,所以"onPreExecute"也是在主线程中执行的。
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
到这里就很清楚了,mStatus 默认状态为PENDING,因此任务开始执行后首先将其状态改为RUNNING;同时从异常判断我们也可以看出一个AsyncTask的execute方法不能同时执行两次。
接下来,onPreExecute(),我们是在onCreate 中开启了AsyncTask的任务,因此这个时候,依旧属于主线程,onPreExecute()方法也会工作在主线程,我们可以在这个方法中执行一些预备操作,初始相关内容。
mWorker,前面已经说过他就是实现了Callable接口,并添加了一个参数属性,在这里我们把executor中传入的参数赋给了这个属性。exec=sDefaultExecutor=SerialExecutor ,这里任务就开始真正的执行了;按照之前所说就会开始执行mFuture这个任务,因此就会开始执行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;
}
};
到这里,我们终于看到了熟悉的 doInBackground,这是我们必须实现的一个方法,在其中完成耗时操作,并返回结果。由于已经设置了Process的优先级,因此这个方法会处于后台进程。 在 doInBackground 里,我们还可以返回当前执行进度
@Override
public Long doInBackground(String... uris) {
int count = uris.length;
long size = 0;
for (int i = 1; i <= count; i ++) {
try {
// 休眠5秒模拟下载过程
Thread.sleep(5 * 1000);
// 假设每个下载文件的大小为(序号*100)
size += i * 100;
// 发布进度更新
publishProgress( (100* i )/count);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
return size;
}
我们调用了 publishProgress 可以将 doInBackground中耗时任务的进度发送出去,大家都知道这个进度会发送到onProgressUpdate() 方法中,在onProgressUpdate我们可以方便的进行UI 更新,比如进度条进度更新等。那么他是怎么实现的呢?这就要看publishProgress这个方法的实现了。
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
AsyncTaskResult 顾名思义,很好理解了,就是AsyncTask的执行结果,这是一个静态的内部类,包括两个属性mTask和mData 。
@SuppressWarnings({"RawUseOfParameterizedType"})
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
}
因此publishProgress中 new AsyncTaskResult 就是创建了一个AsyncTaskResult,他的两个两个属性为当前的AsyncTask和任务任务执行进度。
到这里的逻辑很清楚了,如果当前任务没有被取消, 那么就从消息池中获取一个Message的实例,同时设置这个Message对象的msg.what=MESSAGE_POST_PROGRESS, msg.obj为一个AsyncTaskResult对象,最后执行sendToTarget方法,通过之前对Handler实现机制的了解,我们知道sendXXX方法殊途同归,所完成的任务都是将Message对象插入到MessageQueue当中,等着Looper的loop方法一个个取出。由于我们是在主线程开启了AsyncTask任务的执行,因此,一旦我们将一个消息插入到队列,那么就会执行Handler的handleMessage方法。下面就来看看你这个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;
}
}
}
很简单,在handleMessage中首先取出结果,并强制转换为AsyncTaskResult对象,在msg.what=MESSAGE_POST_PROGRESS时,就会执行result.mTask.onProgressUpdate(result.mData); mTask 就是当前AsyncTask,因此就会执行AsyncTask中声明的onProgressUpdate方法。这样,就把参数从一个子线程传递到了UI 线程,非常方便开发人员用这个完成相关业务。
我们再回到mWorker 的call() 方法中,当doInBackground执行完毕后,最后就会执行postResult。
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
这个方法和publishProgress逻辑一样,懂事把result 封装到一个AsyncTaskResult 对象中,做为一个Message对象的obj属性插入到MessageQueue中,只不过msg.what=MESSAGE_POST_RESULT.
这样就会来到InternalHandler 的handleMessage中,这一次msg.what=MESSAGE_POST_RESULT.时执行result.mTask.finish(result.mData[0]);
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
这个方法也很简单,任务未取消时,onPostExecute(result) 方法被执行。这个onPostExecute(result)就是我们最后要执行的方法,在这个方法中得到最终的执行结果;并将任务状态标记为FINISHED。
其他
串行or并行?
在SimpleAsyncTask中,我们使用private static final Executor EXECUTOR = Executors.newCachedThreadPool()作为线程池,而实际上,源码中的默认线程池是自定义的,这个类是SerialExecutor,从类的命名上看,Serial是串行的意思,所以很明显,AsyncTask默认是串行的。除此之外,AsyncTask里还有个线程池 THREAD_POOL_EXECUTOR,实在需要并行的话我们就用这个线程池。
如果都些都不满足要求,我们也可以自定义符合自己业务要求的线程池,并通过setDefaultExecutor(Executor exec)改变默认的线程池。
在executeOnExecutor中我们还可以传入自己自定义的线程池:
//跟默认一样的按顺序执行
asyncTask.executeOnExecutor(Executors.newSingleThreadExecutor());
//无限制的Executor
asyncTask.executeOnExecutor(Executors.newCachedThreadPool());
//同时执行数目为10的Executor
asyncTask.executeOnExecutor(Executors.newFixedThreadPool(10));
postResultIfNotInvoked的作用是什么?
AsyncTask有很多逻辑干扰了我们解读源码,postResultIfNotInvoked便是其中一个。它实际上是Google解决的一个bug,确保如果cancel()方法过早调用的场景下,onCancelled()仍然能顺利的执行,参考stackoverflow这篇文章。