AsyncTask是android中一个非常好用的异步执行工具类。
AsyncTask的应用
AsyncTask enables proper and easy use of the UI thread. 这个类允许执行后台操作并在UI线程中发布结果,而却不需要管理threads和/或handlers。
AsyncTask被设计为一个围绕着和
的辅助类,而不是一个通用的线程框架。AsyncTask理想的应用场景应该是耗时短的操作(最多几秒钟)。如果你需要使线程长时间的运行,则强烈建议你使用
java.util.concurrent
包提供的各种APIs,比如,
和
。
一个异步任务由一个在后台线程中执行的计算来定义,而其结果将在UI线程中发布。一个异步任务由3种泛型,称为
Params
, Progress
和 Result,和4个步骤,称为
onPreExecute
,doInBackground
,onProgressUpdate
和 onPostExecute
,来定义。
开发者指南
更多关于使用tasks和threads的信息,可以阅读 开发者指南。
使用
必须通过继承来使用AsyncTask。子类将覆写至少一个方法(),而大多数情况下将覆写第二个(
)。
这里是一个继承的例子:
private class DownloadFilesTask extends AsyncTask{ 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"); } }
创建之后,一个task的执行则非常简单:
new DownloadFilesTask().execute(url1, url2, url3);
AsyncTask的泛型
一个异步任务使用的三种类型如下:
Params
,任务执行时发送给它的参数的类型Progress
,后台计算过程中发布的进度单元的类型。Result
,后台计算的结果的烈性。
一个异步任务不总是会用到所有的类型。要标记一个类型未被使用,则简单的使用:
private class MyTask extends AsyncTask{ ... }
4个步骤
当一个异步任务被执行时,则它会历经4个步骤:
,任务被执行之前在UI线程中调用。这个步骤通常被用来设置任务,比如在UI中显示一个进度条。
,结束执行之后,这个方法会立即在后台线程中被调用。这个步骤用于执行可能需要比较长时间的后台的计算。异步任务的参数会被传递给这个步骤。计算的结果必须由这个步骤返回,并将会被传回给最后的步骤。这一步中也可以使用来发布一个或多个的进度单元。这些值在UI线程中发布,在一步中。
, 调用了一次之后,这个方法将在UI线程中被调用。执行的时间则未定义。这个方法被用于在后台计算仍在执行的过程中,在用户接口中显示任何形式的进度。比如,它可被用于在一个进度条中显示动画,或在一个text域中显示logs。
,后台计算结束以后在UI线程中调用。后台计算的结果会作为一个参数传给这个步骤。
取消一个任务
可以在任何时间通过调用来取消一个任务。调用这个方法将导致后续对于的调用返回true。调用这个方法之后,在返回之后,则将会被调用而不是。为了确保一个任务尽快被取消,只要可能(比如在一个循环中),你就应该总是在中周期性的检查的返回值。
线程规则
要使这个类能适当的运行,有一些线程规则必须遵循:
AsyncTask类必须在UI线程加载。自起将自动完成。
task实例必须在UI线程创建。
必须在UI线程中调用。.
不要手动地调用
,
,
,
。
task只能被执行一次(如果尝试再次执行的话将会有一个exception跑出来)。
内存可观察性
AsyncTask保证所有的回调调用以这样的方式同步,下面的操作在没有显式同步的情况下也是安全的:
.在构造函数中或中设置成员变量,并在 中引用它们。
在 中设置成员变量,并在和中引用它们。
执行顺序
当第一次被引入时,AsyncTasks是在一个单独的后台线程中串行执行的。自,开始,这被改为了一个线程池,从而允许多个任务并行的执行。自开始,任务被执行在一个单独的线程上来避免并发执行所导致的常见的应用错误。
如果你真的想要并发执行,你可以以调用。
AsyncTask的实现
上面AsyncTask的应用的部分,译自Google提供的关于。关于AsnycTask在使用时候的注意事项,Google提供的文档可以说覆盖的已经是比较全面了。可是,我们在使用AsyncTask时,为什么一定要按照Google的文档中描述的那样来做呢?这就需要通过研究AsyncTask的实现来解答了。
任务的创建和启动
我们在利用AsnycTask的子类来执行异步任务时,所做的操作通常是new一个对象,执行它的execute()方法,然后就等着任务执行完成就可以了。但是,创建对象的过程,即任务的构造过程,又都做了些什么事情呢?我们的task又是如何被提交并执行起来的呢?这就需要来看下AsyncTask中,任务启动执行部分代码的实现了(android code的分析基于4.4.2_r1)。
先来看AsyncTask的构造,也就是任务的创建:
215 private final WorkerRunnablemWorker;216 private final FutureTask mFuture;221 private final AtomicBoolean mTaskInvoked = new AtomicBoolean();278 /**279 * Creates a new asynchronous task. This constructor must be invoked on the UI thread.280 */281 public AsyncTask() {282 mWorker = new WorkerRunnable () {283 public Result call() throws Exception {284 mTaskInvoked.set(true);285286 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);287 //noinspection unchecked288 return postResult(doInBackground(mParams));289 }290 };291292 mFuture = new FutureTask (mWorker) {293 @Override294 protected void done() {295 try {296 postResultIfNotInvoked(get());297 } catch (InterruptedException e) {298 android.util.Log.w(LOG_TAG, e);299 } catch (ExecutionException e) {300 throw new RuntimeException("An error occured while executing doInBackground()",301 e.getCause());302 } catch (CancellationException e) {303 postResultIfNotInvoked(null);304 }305 }306 };307 }308309 private void postResultIfNotInvoked(Result result) {310 final boolean wasTaskInvoked = mTaskInvoked.get();311 if (!wasTaskInvoked) {312 postResult(result);313 }314 }315316 private Result postResult(Result result) {317 @SuppressWarnings("unchecked")318 Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,319 new AsyncTaskResult (this, result));320 message.sendToTarget();321 return result;322 }654 private static abstract class WorkerRunnable implements Callable {655 Params[] mParams;656 }
可以看到这段code所做的事情。首先是创建了一个Callable,也就是上面的那个WorkerRunnable,其call()所做的事情正是调用我们实现的AsyncTask子类中所覆写的doInBackground()方法,并在其执行结束后将其执行的结果post出来。然后利用前面创建的Callable对象mWorker创建一个FutureTask。由此我们不难理解,AsyncTask的task,其本质是doInBackground(),但是是借助于FutureTask来表述的,以期能够方便的与执行框架衔接。
我们的task创建好了,那这个task又是如何提交并被启动执行的呢?我们接着来看execute()的实现:
218 private volatile Status mStatus = Status.PENDING;506 /**507 * Executes the task with the specified parameters. The task returns508 * itself (this) so that the caller can keep a reference to it.509 *510 *Note: this function schedules the task on a queue for a single background511 * thread or pool of threads depending on the platform version. When first512 * introduced, AsyncTasks were executed serially on a single background thread.513 * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed514 * to a pool of threads allowing multiple tasks to operate in parallel. Starting515 * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being516 * executed on a single thread to avoid common application errors caused517 * by parallel execution. If you truly want parallel execution, you can use518 * the {@link #executeOnExecutor} version of this method519 * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings520 * on its use.521 *522 *
This method must be invoked on the UI thread.523 *524 * @param params The parameters of the task.525 *526 * @return This instance of AsyncTask.527 *528 * @throws IllegalStateException If {@link #getStatus()} returns either529 * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.530 *531 * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])532 * @see #execute(Runnable)533 */534 public final AsyncTask
execute(Params... params) {535 return executeOnExecutor(sDefaultExecutor, params);536 }538 /**539 * Executes the task with the specified parameters. The task returns540 * itself (this) so that the caller can keep a reference to it.541 *542 * This method is typically used with {@link #THREAD_POOL_EXECUTOR} to543 * allow multiple tasks to run in parallel on a pool of threads managed by544 * AsyncTask, however you can also use your own {@link Executor} for custom545 * behavior.546 *547 *
Warning: Allowing multiple tasks to run in parallel from548 * a thread pool is generally not what one wants, because the order549 * of their operation is not defined. For example, if these tasks are used550 * to modify any state in common (such as writing a file due to a button click),551 * there are no guarantees on the order of the modifications.552 * Without careful work it is possible in rare cases for the newer version553 * of the data to be over-written by an older one, leading to obscure data554 * loss and stability issues. Such changes are best555 * executed in serial; to guarantee such work is serialized regardless of556 * platform version you can use this function with {@link #SERIAL_EXECUTOR}.557 *558 *
This method must be invoked on the UI thread.559 *560 * @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a561 * convenient process-wide thread pool for tasks that are loosely coupled.562 * @param params The parameters of the task.563 *564 * @return This instance of AsyncTask.565 *566 * @throws IllegalStateException If {@link #getStatus()} returns either567 * {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.568 *569 * @see #execute(Object[])570 */571 public final AsyncTask
executeOnExecutor(Executor exec,572 Params... params) {573 if (mStatus != Status.PENDING) {574 switch (mStatus) {575 case RUNNING:576 throw new IllegalStateException("Cannot execute task:"577 + " the task is already running.");578 case FINISHED:579 throw new IllegalStateException("Cannot execute task:"580 + " the task has already been executed "581 + "(a task can be executed only once)");582 }583 }584585 mStatus = Status.RUNNING;586587 onPreExecute();588589 mWorker.mParams = params;590 exec.execute(mFuture);591592 return this;593 }
execute()是直接调用了executeOnExecutor(),参数为一个Executor和传进来的参数。而在executeOnExecutor()中,是首先检查了当前的这个AsyncTask的状态,如果不是Status.PENDING的话,则会抛出Exception出来,即是说,一个AsyncTask只能被执行一次。然后将状态设为Status.RUNNING。接着调用AsyncTask子类覆写的onPreExecute()方法。前面Google官方文档中4个步骤部分提到onPreExecute()这个步骤,这个方法在任务执行之前在UI线程中调用,这是由于我们在UI线程中执行execute()提交或启动任务时,会直接调用到这个方法(execute()->executeOnExecutor()->onPreExecute())。接着是将传进来的参数params赋给mWorker的mParams,至此才算是为任务的执行完全准备好了条件。万事俱备,只欠东风,准备好的任务只等提交执行了。接着便是将任务提交给传进来的Executor来执行。
那Executor又是如何来执行我们的Task的呢?我们来看AsyncTask中的Excutor。在execute()中是使用了sDefaultExecutor来执行我们的任务:
185 private static final ThreadFactory sThreadFactory = new ThreadFactory() {186 private final AtomicInteger mCount = new AtomicInteger(1);187188 public Thread newThread(Runnable r) {189 return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());190 }191 };192193 private static final BlockingQueuesPoolWorkQueue =194 new LinkedBlockingQueue (128);195196 /**197 * An {@link Executor} that can be used to execute tasks in parallel.198 */199 public static final Executor THREAD_POOL_EXECUTOR200 = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,201 TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);207 public static final Executor SERIAL_EXECUTOR = new SerialExecutor();214 private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;223 private static class SerialExecutor implements Executor {224 final ArrayDeque mTasks = new ArrayDeque ();225 Runnable mActive;226227 public synchronized void execute(final Runnable r) {228 mTasks.offer(new Runnable() {229 public void run() {230 try {231 r.run();232 } finally {233 scheduleNext();234 }235 }236 });237 if (mActive == null) {238 scheduleNext();239 }240 }241242 protected synchronized void scheduleNext() {243 if ((mActive = mTasks.poll()) != null) {244 THREAD_POOL_EXECUTOR.execute(mActive);245 }246 }247 }
sDefaultExecutor在默认情况下是SERIAL_EXECUTOR,而后者则是一个SerialExecutor对象。在SerialExecutor的execute()中其实只是创建了一个匿名Runnable,也就是我们的task,并将它提交进一个任务队列mTasks中,如果mActive为null,则意味着这是进程所执行的第一个AsnycTask,则将会调用scheduleNext()来第一次将一个task提交进一个线程池executor,来在后台执行我们的任务。
Google的官方文档中提到,AsyncTask任务是串行执行的。AsyncTask任务的串行执行,不是指这些任务是在一个SingleThreadExecutor上执行的(其实任务是在一个可以执行并发操作的thread pool executor中执行的),只是这些任务提交给THREAD_POOL_EXECUTOR是串行的。只有当一个任务执行结束之后,才会有另外的一个任务被提交上去。这种任务的串行提交,是在SerialExecutor的execute()方法里那个匿名Runnable实现的,我们可以看到它是在执行完了一个task之后,才会去schedule另一个task的。
生产者线程可以看作是UI主线程,它会向任务队列中提交任务,而消费者则是执行了某个任务的THREAD_POOL_EXECUTOR中的某个线程。某个时刻,最多只有一个生产者,也最多只有一个消费者。
捋一捋我们的那个用户任务doInBackground()到此为止被包了多少层了。先是被包进一个Callable (mWorker : WorkerRunnable)中,mWorker又被包进了一个FutureTask (mFuture)中,而mFuture在这个地方则又被包进了一个匿名的Runnable中,然后我们的task才会真正的被提交给THREAD_POOL_EXECUTOR而等待执行。我们的doInBackground()执行时,其调用栈将有点类似于 doInBackground()<-mWorker.call()<-mFuture.run()<-匿名Runnable的run()<-...。
AsyncTask与UI线程的交互
AsyncTask可以很方便的与UI线程进行交互,它可以方便地向UI线程中publish任务执行的进度和任务执行的结果。但这种交互又是如何实现的呢?AsyncTask中有提到,在doInBackground()中,可以通过调用来发布一个或多个的进度单元,而这些值将在UI线程中发布,在一步中。那我们就具体来看一下publishProgress()的实现:
212 private static final InternalHandler sHandler = new InternalHandler();607 /**608 * This method can be invoked from {@link #doInBackground} to609 * publish updates on the UI thread while the background computation is610 * still running. Each call to this method will trigger the execution of611 * {@link #onProgressUpdate} on the UI thread.612 *613 * {@link #onProgressUpdate} will note be called if the task has been614 * canceled.615 *616 * @param values The progress values to update the UI with.617 *618 * @see #onProgressUpdate619 * @see #doInBackground620 */621 protected final void publishProgress(Progress... values) {622 if (!isCancelled()) {623 sHandler.obtainMessage(MESSAGE_POST_PROGRESS,624 new AsyncTaskResult
所谓的与UI线程交互,其实也就是向一个Handler,即sHandler中发送消息。对于的case,即是发送类型为MESSAGE_POST_PROGRESS的消息。随后消息会在UI线程中被处理,从而实现了将信息由后台线程传递至前台线程的目的。此处的sHandler是一个static的InternalHandler,也即是说,当AsyncTask类被加载起来的时候,这个对象就会被创建。我们知道,Handler其实是向一个Looper中发送消息。这个Looper隶属于某一个Looper thread,比如UI线程。我们可以在创建一个Handler时为它指定一个Looper,当然也可以不指定。如果不指定的话,那Handler关联的Looper就会是创建Handler的线程的Looper。由此则我们不难理解,为什麽Google的文档会强调,AsyncTask类必须在UI线程加载,因为sHandler所关联的Looper将会是VM中第一次访问AsyncTask的线程的Looper。也即是说,VM中第一个访问AsyncTask的线程,也将决定后续创建的所有的AsyncTask,它发布进度及结果的目标线程。实际上,由于后台线程与之交互的线程和后台线程通信是通过handler来完成的,则大概只要保证加载AsyncTask的线程是一个HandlerThread就可以了,只不过在这种情况下,创建的所有的AsyncTask都将会把进度和结果publish到那个HandlerThread中了。
不过话说回来了,是否真的有可能AsyncTask与之交互的线程只是一个普通的HandlerThread而不是main UI线程呢?答案当然是否定的。来看下面的这么一段code:
if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } AsyncTask.init();
这段code来自于frameworks/base/core/java/android/app/ActivityThread.java的main()方法中。可见Google也是早就料到了其中的风险,并已经有在此做防范,以确保AsyncTask中的static的Handler一定是在主线程创建的。
由InternalHandler.handleMessage()中对于MESSAGE_POST_PROGRESS,相信我们也就不难理解所谓的步骤onProgressUpdate(Progress...)是什么了。
看完了后台线程向UI线程发布进度的实现之后,我们再来看后台线程向UI线程中发布结果的实现:
316 private Result postResult(Result result) {317 @SuppressWarnings("unchecked")318 Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,319 new AsyncTaskResult(this, result));320 message.sendToTarget();321 return result;322 }
同样是向sHandler中send message,只不过message的类型为了MESSAGE_POST_RESULT。这段code也不需要做过多解释。在MESSAGE_POST_RESULT的处理部分,我们看到了4个步骤中的另一个步骤onPostExecute():
628 private void finish(Result result) {629 if (isCancelled()) {630 onCancelled(result);631 } else {632 onPostExecute(result);633 }634 mStatus = Status.FINISHED;635 }
也就是在任务结束之后,通知UI线程的部分。
并行执行AsyncTask及THREAD_POOL_EXECUTOR
AsyncTask不只能像上面说的那样串行执行,它们同样可以并行执行。即以为参数调用。比如:
new DownloadFilesTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url1, url2, url3);
那THREAD_POOL_EXECUTOR本身又都有些什么样的限制呢?接下来我们来看THREAD_POOL_EXECUTOR的创建:
180 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();181 private static final int CORE_POOL_SIZE = CPU_COUNT + 1;182 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;183 private static final int KEEP_ALIVE = 1;184185 private static final ThreadFactory sThreadFactory = new ThreadFactory() {186 private final AtomicInteger mCount = new AtomicInteger(1);187188 public Thread newThread(Runnable r) {189 return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());190 }191 };192193 private static final BlockingQueuesPoolWorkQueue =194 new LinkedBlockingQueue (128);195196 /**197 * An {@link Executor} that can be used to execute tasks in parallel.198 */199 public static final Executor THREAD_POOL_EXECUTOR200 = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,201 TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
创建ThreadPoolExecutor传进去了一个容量为128的链接阻塞队列(sPoolWorkQueue)作为任务队列,这意味着在同一时间可以提交的最大的任务数量为128,既包括通过AsyncTask.execute()提交的任务,也包括通过AsyncTask.executeOnExecutor()提交的任务。线程池中线程的最小数量及最大数量则是与CPU数量相关的值CORE_POOL_SIZE和MAXIMUM_POOL_SIZE。KEEP_ALIVE则表示,当线程池中的线程数量超过了CORE_POOL_SIZE,那些空闲的线程能够存活的最长时间。sThreadFactory则用于维护线程池创建过的线程的数量,并给每个AsyncTask的线程创建一个适当的线程名。
Task的生命周期管理
声明周期管理,主要是指取消操作。
220 private final AtomicBoolean mCancelled = new AtomicBoolean();423 /**424 * Returns true if this task was cancelled before it completed425 * normally. If you are calling {@link #cancel(boolean)} on the task,426 * the value returned by this method should be checked periodically from427 * {@link #doInBackground(Object[])} to end the task as soon as possible.428 *429 * @return true if task was cancelled before it completed430 *431 * @see #cancel(boolean)432 */433 public final boolean isCancelled() {434 return mCancelled.get();435 }466 public final boolean cancel(boolean mayInterruptIfRunning) {467 mCancelled.set(true);468 return mFuture.cancel(mayInterruptIfRunning);469 }
AsyncTask用一个原子变量来描述,任务是否是被cancel的。而提交给线程池的任务的cancel则委托给FutureTask。我们知道,在Java中,对于线程的cancel只能是提示性的,而不是强制性的。要真正的使任务有好的生命周期管理,则还需要在doInBackground()中,在适当的时机检查任务是否被cancel掉,并进一步清理环境,退出任务执行。
Done。