AsyncTask详解
- 一,认识 AsyncTask
- 1,三个类型
- 2,四个步骤
- 3,取消任务
- 4,使用的线程规则
- 5,内存可观测性
- 6,执行顺序
- 1, 类结构
- 2, 类初始化
- 3, 任务从提交到完成
- 3.1,提交
- 3.2,入栈
- 3.3,轮询
- 3.4,处理
- 3.5,结束
一,认识 AsyncTask
AsyncTask
能够在UI线程中准确且简易地使用。这个类允许我们执行后台操作并在UI线程上发布结果,而不必操作线程和/或
Handler
。
AsyncTask
被设计成一个围绕
Thread
和
Handler
的帮助类,它不构成一个通用的线程框架。
AsyncTasks
理想情况下应该用于短操作(最多几秒钟)。如果需要让线程长时间运行,谷歌强烈建议我们使用
java.util.concurrent
提供的各种api,如
Executor
,
ThreadPoolExecutor
和
FutureTask
。
异步任务是由在后台线程上运行并在UI线程上发布其结果的计算定义的。异步任务由3个泛型类型
Params
、
Progress
、
Result
和4个步骤
onPreExecute
、
doInBackground
、
onProgressUpdate
、
onPostExecute
定义。
1,三个类型
AsyncTask的异步任务使用的三种泛型类型如下:
-
Params
:执行时发送给任务的参数的类型。
-
Progress
:在后台计算期间发布的进度单元的类型。
-
Result
:后台计算结果的类型。
异步任务并不总是所有类型都被使用。要标记一个类型为空类型,需使用类型Void:
AsyncTask<Void, Void, Void>
2,四个步骤
在执行异步任务时,该任务将经历4个步骤:
-
onPreExecute
:在执行任务之前在UI线程上调用。这个步骤通常用于设置任务,例如在用户界面中显示进度条。
-
doInBackground
:在
onPreExecute
完成执行后立即在后台线程上调用。此步骤用于执行可能花费较长时间的后台计算。异步任务的参数被传递到这一步,计算结果必须由这一步返回,并将被传递回最后一步。在这个步骤中也可以使用
publishProgress
来发布一个或多个进度单元,这些值发布在UI线程的
onProgressUpdate
步骤中。
-
onProgressUpdate
:调用
publishProgress
之后在UI线程中调用。执行的时间没有定义。此方法用于在后台计算仍在执行时在用户界面中显示任何形式的进度。例如,它可以用于动画进度条或在文本字段中显示日志。
-
onPostExecute
:后台计算完成后在UI线程上调用。后台计算的结果作为参数传递给这一步。
3,取消任务
我们可以在任何时候通过调用
cancel(boolean)
来取消任务。调用此方法将导致后续对
isCancelled()
的调用返回
true
。在调用这个方法后调用
onCancelled(Object)
,而不是
onPostExecute(Object)
将在
doInBackground(Object[])
返回后被调用。为了确保尽可能快地取消任务,应该经常定期从
doInBackground(Object[])
返回的值(例如在
loop
中)检查
isCancelled()
。
4,使用的线程规则
这个类正常工作必须遵循几个线程规则:
-
AsyncTask
类必须在UI线程上加载。从
Android4.1
开始是自动完成这个操作。
- 任务实例必须在UI线程上创建
-
execute
必须在UI线程上调用
-
onPreExecute()、onPostExecute}、doInBackground、onProgressUpdate
不能手动调用
- 该任务只能执行一次(如果尝试第二次执行,将抛出异常)。
5,内存可观测性
AsyncTask
保证所有回调调用都是同步的,以确保在不显式同步的情况下完成以下操作。
-
onPreExecute
的内存效果、以及在调用
execute
之前执行的任何其他操作、包括
AsyncTask
对象的构造,对
doInBackground
都是可见的。
-
doInBackground
的内存效果对
onPostExecute
是可见的。
- 调用
publishProgress
之前
doInBackground
的任何内存效果对相应的
onProgressUpdate
调用都是可见的。(但是
doInBackground
继续运行,需要注意的是,以后
doInBackground
中的更新不会干扰正在进行的
onProgressUpdate
调用。)
- 在调用返回
true
的
isCancelled
之后、或者在调用
onCancelled
期间和之后,任何在调用
cancel
之前的内存效果都是可见的。
6,执行顺序
AsyncTasks
的初始设计是在单个后台线程上串行执行的。从
Android1.6
开始。将其更改为线程池,允许多个任务并行操作。从
Android3.0
开始。任务在单个线程上执行,以避免并行执行导致的常见应用程序错误。
如果真的想要并行执行,可以调用带有
THREAD_POOL_EXECUTOR
对象的方法,即:
executeOnExecutor(THREAD_POOL_EXECUTOR, Object[])
。
二,AsyncTask的实现
AsyncTask中提供了如下几个可供我们外部调用的方法:
AsyncTask中允许我们子类化实现的方法如下(doInBackground是抽象方法,必须实现):
我们知道
AsyncTask
默认是串行的,但也能实现并行;我们还知道
onPreExecute
和
onProgressUpdate
是运行于主线程的,
publishProgress
是运行于工作线程的;
AsyncTask
是如何做到这些的呢?跟着源码我们一步步来看。关键代码我会做逐行注释。
1, 类结构
/*** 按顺序一次执行一个任务的Executor。这种串行化对于特定进程是全局有效的。*/public static final Executor SERIAL_EXECUTOR = new SerialExecutor();/*** 可用于并行执行任务的Executor。*/public static final Executor THREAD_POOL_EXECUTOR;// 默认在当前进程中的所有AsyncTask任务都是串行的@UnsupportedAppUsageprivate static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
// AsyncTask内部创建的Handler子类,绑定至UI线程,一旦创建,存活于整个进程。// 用以将onProgressUpdate和finish方法的执行发布至UI线程private static InternalHandler sHandler;// WorkerRunnable是一个抽象类,CallBack的实现类,持有Params[]。// mWorker作为AsyncTask任务的运行工作区,doInBackground将在其call()方法里执行@UnsupportedAppUsageprivate final WorkerRunnable<Params, Result> mWorker;// 用以在适时的时候将Result通过sHandler通知到UI线程// FutureTask是Future和Runnable的子接口,一个可取消的异步任务。// mFuture中持有mWorker对象,当任务被执行时会运行mFuture.run(),并在这个方法中调用mWorker.call()@UnsupportedAppUsageprivate final FutureTask<Result> mFuture;// 用于标记当前任务是否已经调用过cancel或执行过程中发生异常private final AtomicBoolean mCancelled = new AtomicBoolean();// 用于标记当前任务是否已经调用过postResult将消息发至主线程@UnsupportedAppUsageprivate final AtomicBoolean mTaskInvoked = new AtomicBoolean();// 关联与AsyncTask创建所属的线程(对于我们而言只能是UI线程)的Handler,// 当是UI线程时,mHandler实际上与sHandler指向同一个地址,区别在于:// sHandler的作用域是整个进程,而mHandler只存在于当前AsyncTask的实例。// 对于我们而言mHandler == sHandlerprivate final Handler mHandler;
2, 类初始化
AsyncTask内共有三个构造方法,但是作为开放api可供我们使用的只有一个无参构造方法,其他方法被标注为@hide,只有SDK自己的代码可以调用。无参构造方法中调用
AsyncTask(@Nullable Looper callbackLooper)
,并传入一个值为
null
的
Looper
。
我们以
AsyncTask(@Nullable Looper callbackLooper)
来讲解,代码中有看不明白的注释不要紧,下文会从提交到完成的全过程去走一遍代码。
/*** 创建一个新的异步任务。这个构造函数必须在UI线程上调用。* @hide*/public AsyncTask(@Nullable Looper callbackLooper) {// callbackLooper为空会获取主线程(UI线程)的Looper,所以AsyncTask的构造必须位于UI线程。// getMainHandler()获取到的是sHandler,此时,mHandler == sHandlermHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()? getMainHandler(): new Handler(callbackLooper);mWorker = new WorkerRunnable<Params, Result>() {public Result call() throws Exception {// 工作线程,当调用Executor#execute时,代码会执行到这里。// mTaskInvoked被标记为已经调用过executemTaskInvoked.set(true);Result result = null;try {Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);// 从这里可以知道doInBackground是运行于工作线程(子线程)的result = doInBackground(mParams);Binder.flushPendingCommands();} catch (Throwable tr) {// 发生异常时,任务会被标记为取消mCancelled.set(true);throw tr;} finally {// doInBackground执行完毕后发送结果, 如果发生异常仍然会发送,只不过此时result==nullpostResult(result);}return result;}};// FutureTask是Future和Runnable的子接口, mFuture中持有mWorker对象,// 当任务被执行时会执行mFuture.run()方法,并在这个方法中调用mWorker.call()// 在此处通过调用mFuture.get()方法将会得到mWorker.call()返回的结果mFuture = new FutureTask<Result>(mWorker) {// 在三种情况下会执行FutureTask.done():// 1,任务被取消;2,任务正常执行完毕;3,任务处理过程中出现异常。@Overrideprotected void done() {try {// postResultIfNotInvoked方法的作用是:判断是否已经执行过postResult,如果没有就执。// 完整流程下postResult到这里是已经被调用过的(第30行)。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);}}};}
3, 任务从提交到完成
通常使用的时候,我们都是调用
AsyncTask#execute()
,在
execute()
中是调用
executeOnExecutor(Executor exec, Params... params)
并传入默认的
Executor
(
sDefaultExecutor
即
SERIAL_EXECUTOR
)的。
想要并行执行,可以使用:
executeOnExecutor(THREAD_POOL_EXECUTOR, Params[])。
3.1,提交
/*** 使用指定的参数执行任务。任务返回自身(this),以便我们可以保留对它的引用。** 这个方法通常与THREAD_POOL_EXECUTOR一起使用,以允许在AsyncTask管理的线程池中并行运行多个任务,* 但是我们也可以使用自己的Executor来定制行为。(串行任务调用`AsyncTask#execute()`)** 这个方法必须在UI线程上调用。*/@MainThreadpublic 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;onPreExecute();mWorker.mParams = params;exec.execute(mFuture);return this;}
我们知道串行任务方法
AsyncTask#execute()
也是调用了上面的方法并传入
SERIAL_EXECUTOR
,那我们来看一下
SERIAL_EXECUTOR
的类型:
private static class SerialExecutor implements Executor {// Deque接口的可调整大小数组实现。阵列容量无限制; 它们会根据需要增长以支持使用。// 它们不是线程安全的; 在没有外部同步的情况下,它们不支持多线程并发访问。// AsyncTask的串行特点就表现在这。这里不做多余解释,知道是干什么的就行了。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);}}}
可以看到
scheduleNext()
方法中使用的是并行任务的执行器,也就是说,不论是串行还是并行,最后的
execute(mFuture)
都会走向
ThreadPoolExecutor
(
THREAD_POOL_EXECUTOR
的类型)。
有兴趣了解这个类的朋友可以去看看: ThreadPoolExecutor。因为我们这篇文章主要是将AsyncTask原理的,所以对
ThreadPoolExecutor
只说过程,不讲源码。
3.2,入栈
ThreadPoolExecutor#execute(Runnable)
:在将来的某个时候执行给定的任务。
public void execute(Runnable command) {>>>if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}else if (!addWorker(command, false))reject(command);}
ThreadPoolExecutor#addWorker(Runnable , false)
:将任务加入工作队列。
3.3,轮询
ThreadPoolExecutor#runWorker(Worker)
:主工作程序运行循环,从队列中反复获取任务并运行它们。
final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;w.unlock(); // allow interruptsboolean completedAbruptly = true;try {while (task != null || (task = getTask()) != null) {w.lock();>>>try {beforeExecute(wt, task);Throwable thrown = null;try {task.run();} catch >>>} finally {afterExecute(task, thrown);}} finally {task = null;w.completedTasks++;w.unlock();}}completedAbruptly = false;} finally {processWorkerExit(w, completedAbruptly);}}
第15行的
task.run()
即是前文构造方法中提到的
FutureTask#run()
,跟着源码我们接着再看这个方法都做了些什么。
3.4,处理
FutureTask#run()
:
public void run() {>>>try {Callable<V> c = callable;if (c != null && state == NEW) {V result;boolean ran;try {result = c.call();ran = true;} catch (Throwable ex) {result = null;ran = false;setException(ex);}if (ran)set(result);}} finally {>>>}}
第9行的
WorkerRunnable#call()
的返回值会通过第17行的
FutureTask#set(result)
保存在
mFuture
中。
FutureTask#set(result)
:
protected void set(V v) {if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {outcome = v;U.putOrderedInt(this, STATE, NORMAL); // final statefinishCompletion();}}
private void finishCompletion() {// assert state > COMPLETING;for (WaitNode q; (q = waiters) != null;) {if (U.compareAndSwapObject(this, WAITERS, q, null)) {for (;;) {Thread t = q.thread;if (t != null) {q.thread = null;LockSupport.unpark(t);}WaitNode next = q.next;if (next == null)break;q.next = null; // unlink to help gcq = next;}break;}}done();callable = null; // to reduce footprint}
任务执行完毕之后,会调用
FutureTask#done()
,通过调用查找可以知道
done()
方法只会在如下三种情况下被执行:
- 任务被取消;
- 任务正常执行完毕;
- 任务处理过程中出现异常。
至此,章节 【2.,类初始化】 中
WorkerRunnable
和
FutureTask
的注释描述就与实际代码串联上了。
3.5,结束
从章节 【2.,类初始化】 可以看到,
WorkerRunnable#call()
和
FutureTask#done()
方法中的最后都调用了
postResult(Result)
,这个方法的作用是通过
Handler
将执行结果发送到
Handler
所属的线程中。
private Result postResult(Result result) {@SuppressWarnings(\"unchecked\")Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,new AsyncTaskResult<Result>(this, result));message.sendToTarget();return result;}
getHandler()
返回
mHandler
,前面我们说过在我们的使用中
mHandler==sHandler
即主线程(UI线程),看一下他的实现:
private static class InternalHandler extends Handler {public InternalHandler(Looper looper) {super(looper);}@SuppressWarnings({\"unchecked\", \"RawUseOfParameterizedType\"})@Overridepublic void handleMessage(Message msg) {AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;switch (msg.what) {case MESSAGE_POST_RESULT:// There is only one resultresult.mTask.finish(result.mData[0]);break;case MESSAGE_POST_PROGRESS:result.mTask.onProgressUpdate(result.mData);break;}}}
mTask.finish(result)
:
private void finish(Result result) {if (isCancelled()) {onCancelled(result);} else {onPostExecute(result);}mStatus = Status.FINISHED;}
mHandler
是运行于主线程的,从这里也就明白了
onCancelled、onPostExecute、onProgressUpdate
是如何在异步的
doInBackground
执行完之后又被运行到主线程中去的。
PS:如果有对Handler的原理不是很了解的可以看这里:《Android 从源码了解Handler(Looper 、Message、MessageQueue)的实现原理》
4, 任务取消
在任务从提交到结束的过程中,我们是可以随时通过调用
cancel(boolean mayInterruptIfRunning)
来取消任务的。
public final boolean cancel(boolean mayInterruptIfRunning) {mCancelled.set(true);return mFuture.cancel(mayInterruptIfRunning);}
FutureTask.cancel(boolean)
:
public boolean cancel(boolean mayInterruptIfRunning) {// 如果已经处于中断或者取消,直接returnif (!(state == NEW &&U.compareAndSwapInt(this, STATE, NEW,mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))return false;try { // 如果调用中断抛出异常if (mayInterruptIfRunning) {// 如果需要中断正在执行的任务,直接使执行任务的工作线程interrupttry {Thread t = runner;if (t != null)t.interrupt();} finally { // final stateU.putOrderedInt(this, STATE, INTERRUPTED);}}} finally {finishCompletion();}return true;}