AsyncTask可以非常方便、简单的在UI线程中使用,它帮助我们在不使用Thread和Handler的情况下,在后台执行一个后台(异步)任务,并且把执行结果通知到UI线程中。也就是说,使用AsyncTask可以极大的简化我们进行后台任务的操作,使用它,我们不必关心工作线程是如何启动的,也不必关心,工作线程和UI线程之间的通信问题,这些都被AsyncTask通过内部封装实现了,我们只需要按接口规范使用它即可。
AsyncTask适用于短时的后台(异步)任务(最多几秒钟),它并不适合时间较长的后台任务,如果我们想在后台线程执行长时间的异步任务,可以使用java.util.concurrent并发工具包中的Executor、ThreadPoolExecutor和FutureTask等。
AsyncTask执行后台任务,是由后台线程执行具体的任务,最后把结果发布到UI线程。
一个后台任务的声明由3种泛型类型:Params、Progress和Result,以及4个过程:onPreExecute、doInBackground、onProgressUpdate和onPostExecute构成。
Params:doInBackground方法的参数类型,它会在任务执行之前发送给后台任务。
Progress:onProgressUpdate方法的参数类型,它是在任务执行期间发布的进度类型。
Result:onPostExecute方法的参数类型,它是后台任务执行结束后的结果类型。
示例:
private class DownloadFilesTask extends AsyncTask {}
示例中的Params、Progress和Result分别是String、Integer和Long。
onPreExecute():UI线程中执行。它发生在后台任务开始执行之前,我们可以通过它来进行任务的配置,比如开始展示进度条等。
doInBackground(Params…):后台线程中执行。它在onPreExecute完成之后立即执行,它会执行较长时间的后台运算任务。运算结果必须由该步骤return,发布给onPostExecute方法。我们也可以在该过程中,调用一次或多次publishProgress(Progress…)方法来发布执行进度信息,进度信息最终会发布到UI线程中,成为onProgressUpdate(Progress…)的参数。
onProgressUpdate(Progress…):UI线程中执行。它是在调用publishProgress(Progress…)之后执行的,它通常用来展示后台任务的执行进度。
onPostExecute(Result):UI线程中执行。它是在后台任务执行结束之后调用的,由doInBackground(Params…)方法return的结果作为参数。
AsyncTask后台任务执行中的任意时刻,都可以调用cancel(boolean)来进行取消操作。
示例代码,创建了一个DownloadFilesTask类,继承于AsyncTask,用于执行后台任务。
private class DownloadFilesTask extends AsyncTask {//3个泛型分别为:String, Integer, Long
protected Long doInBackground(String... urls) {//参数为字符串列表
int count = urls.length;
long time = 0;
for (int i = 0; i < count; i++) {
try {
Thread.sleep(1000);//示例任务,执行线程休眠1秒,模拟耗时任务
} catch (InterruptedException e) {
e.printStackTrace();
}
time += 1000;
publishProgress((int) (((i + 1) / (float) count) * 100));//更新进度
if (isCancelled()) break;//检测是否执行过程中取消了任务
}
return time;
}
protected void onProgressUpdate(Integer... progress) { //更新进度。这里的参数是Integer对象
mTextView.setText("进度:" + progress[0]);
}
protected void onPostExecute(Long result) { //执行结果回调
mTextView.setText("time : " + result + " 毫秒");
}
}
异步任务准备好了,我们创建一个DownloadFilesTask类的实例,调用execute()方法即可执行:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new DownloadFilesTask().execute("url1", "url2", "url3", "url3");
}
例子很简单,但是也完整的展示了AsyncTask的基本用法。
接下来我们来分析AsyncTask的实现原理。
我们接下来分析AsyncTask的源码,看下它的实现原理。
在Demo中,我们调用AsyncTask的实例对象的execute方法来实现调用的,那么我们就从execute方法作为入口点来分析。
@MainThread
public final AsyncTask execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
这里调用了executeOnExecutor方法,参数sDefaultExecutor是一个线程池,作为默认线程池。
@MainThread
public final AsyncTask 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;
}
线程池执行的部分我们后面来分析,先来看这里有2个重要的属性mWorker和mFuture,它们起到了非常重要的作用,我们来看。
属性mWorker和mFuture是在AsyncTask的构造方法中初始化的,它们是后台线程控制逻辑的核心所在。
我们来看AsyncTask的构造方法:
public AsyncTask() {//这里是在UI线程中执行的
this((Looper) null);
}
public AsyncTask(@Nullable Handler handler) {
this(handler != null ? handler.getLooper() : null);
}
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
mWorker = new WorkerRunnable() {
public Result call() throws Exception {//这里是在后台线程中执行的
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);//这里设置了线程的优先级为THREAD_PRIORITY_BACKGROUND
//noinspection unchecked
result = doInBackground(mParams);//这里调用了doInBackground方法
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result); //post结果给UI线程
}
return result;//把后台线程处理结果返回
}
};
mFuture = new FutureTask(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);
}
}
};
}
private static abstract class WorkerRunnable implements Callable {
Params[] mParams;
}
我们来看,后台任务处理完成后,会在WorkerRunnable的call方法中,调用postResult(result)将结果返回给UI线程,但如果后台任务执行之前被终止,则会把结果传递给postResultIfNotInvoked方法。
我们来看这两个方法:
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) { //同步属性的布尔值,表示后台任务是否已经开始执行
postResult(result);//这里,后台任务并没有执行
}
}
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult(this, result));
message.sendToTarget();
return result;
}
postResult方法,创建一个message对象,把执行结果封装成一个AsyncTaskResult对象,通过Handler机制,将后台执行结果传递给UI线程(这里只考虑UI线程一种情况)。
到了这里,Asynctask的执行部分、结果处理以及跨线程通信部分,都已经分析完成了,接下来我们来看后台线程是如何执行的,任务队列是如何实现的。
我们从上文知道,AsyncTask的execute方法中,直接使用了一个默认线程池sDefaultExecutor来负责后台线程的创建及执行。
回顾一下:
@MainThread
public final AsyncTask execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static class SerialExecutor implements Executor {
final ArrayDeque mTasks = new ArrayDeque();
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是SerialExecutor的一个实例对象。
mTasks:是一个先进先出的队列,这里可以理解为容量无限的一个先进先出的队列(当然不可能无限,不过正常情况下我们是达不到它的最大容量的)。
mActive:表示当前正在执行的任务,它是一个Runnable对象。
串行执行的秘密:这里其实就实现了一个单线程的任务队列,所有AnsyncTask的任务,都会顺序的,单线程执行。它的实现原理其实就是,准备了一个可以扩容的先进先出的任务队列mTasks,所有的后台任务执行时,先进入队列中,然后调用scheduleNext执行,当前任务结束后,继续执行下一个任务,这样也就实现了串行的任务处理。
这里的线程池其实没有发挥线程池的作用,因为默认情况下,AsyncTask只会执行单个任务调用,因为在SerialExecutor中已经实现了任务的队列管理了,SerialExecutor会单个顺序的执行线程任务的调用。
public static final Executor THREAD_POOL_EXECUTOR;
private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 20;
private static final int BACKUP_POOL_SIZE = 5;
private static final int KEEP_ALIVE_SECONDS = 3;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
new SynchronousQueue(), sThreadFactory);
threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
THREAD_POOL_EXECUTOR其实是一个可并发的线程池,它的核心线程数是1,最大线程数是20。(注意,不同Android版本中的实现会有不同)
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());
}
};
线程池是通过ThreadFactory对象来进行线程创建的,并且会为线程命名为"AsyncTask #" + mCount.getAndIncrement()。
在Android 3.0时,AsyncTask的任务执行改回了单一线程中顺序执行,那么我们如果想用AsyncTask实现并发任务,可以做到吗?答案是肯定的,我们来看如何实现。
我们来看AsyncTask的executeOnExecutor方法:
public final AsyncTask executeOnExecutor(Executor exec,
Params... params) {
……
exec.execute(mFuture);
return this;
}
executeOnExecutor方法,可以接受一个线程池的参数,来让使用者实现自定义线程池的目的。
我们可以自定义一个并发的线程池,通过调用executeOnExecutor方法,替换掉AsyncTask的默认线程池,即可实现并发处理。
注意:虽然这里可以实现线程池的并发处理,但是并不建议这么做,如果想要实现并发的后台任务,推荐使用java.util.concurrent并发工具包中的Executor、ThreadPoolExecutor和FutureTask等。
到了这里,我们已经深入了解了AsyncTask的使用及实现原理了,那么我们在开发时,有哪些是需要注意的呢?
AsyncTask后台任务的执行顺序是不可靠的,因为它在Android历史版本中经历了多次变更。在刚刚引入AsyncTask时,AsyncTask后台任务的执行,是在单一线程中顺序执行的;但是在Android 1.6中,AsyncTask的任务执行改成了可多线程并发执行;在Android 3.0时,AsyncTask的任务执行又改回了单一线程中顺序执行,以避免多线程造成的并发等问题。
如果我们想通过AsyncTask来并发执行后台任务,可以使用executeOnExecutor(java.util.concurrent.Executor, java.lang.Object[])方法,传递一个线程池来执行。
注意:AsyncTask在Android R(Android 11)中被标记为弃用状态,建议使用java.util.concurrent并发工具包来代替。