类注释
* <p>AsyncTask enables proper and easy use of the UI thread. This class allows you
* to perform background operations and publish results on the UI thread without
* having to manipulate threads and/or handlers.</p>
复制代码
AsyncTask 更加正确和方便地使用UI线程,它允许你执行后台的操作并且把结果反馈到UI线程上,而不需要操控线程和Handlers。
* <p>AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler}
* and does not constitute a generic threading framework. AsyncTasks should ideally be
* used for short operations (a few seconds at the most.) If you need to keep threads
* running for long periods of time, it is highly recommended you use the various APIs
* provided by the <code>java.util.concurrent</code> package such as {@link Executor},
* {@link ThreadPoolExecutor} and {@link FutureTask}.</p>
复制代码
AsyncTask 设计来成为 Thread 和 Handler 的辅助类,并不是一个通用的线程框架。它适合用于一些短时间的操作,例如几秒钟的一个操作。如果你想执行一个长时间的耗时操作,可以使用 java.util.concurrent 下的APIS。
* <p>An asynchronous task is defined by a computation that runs on a background thread and
* whose result is published on the UI thread. An asynchronous task is defined by 3 generic
* types, called <code>Params</code>, <code>Progress</code> and <code>Result</code>,
* and 4 steps, called <code>onPreExecute</code>, <code>doInBackground</code>,
* <code>onProgressUpdate</code> and <code>onPostExecute</code>.</p>
复制代码
通常密集型计算任务设计成一个异步的后台任务,它运行在后台线程,并把最终的结果展示到UI上。一个 AsyncTask 定义了3中参数泛型:Params,Progress,Result,和4个步骤:onPreExecute,doInBackgroud,onProgressUpdate,onPostExecute。
* <h2>AsyncTask's generic types</h2>
* <p>The three types used by an asynchronous task are the following:</p>
* <ol>
* <li><code>Params</code>, the type of the parameters sent to the task upon
* execution.</li>
* <li><code>Progress</code>, the type of the progress units published during
* the background computation.</li>
* <li><code>Result</code>, the type of the result of the background
* computation.</li>
* </ol>
复制代码
AsyncTask 定义的三个参数泛型:
- Params,输入参数,执行任务时发送到后台任务的参数类型
- Progress,后台任务执行时,更新进度的参数类型
- Result,后台任务执行完毕的输出结果
* <h2>The 4 steps</h2>
* <p>When an asynchronous task is executed, the task goes through 4 steps:</p>
* <ol>
* <li>{@link #onPreExecute()}, invoked on the UI thread before the task
* is executed. This step is normally used to setup the task, for instance by
* showing a progress bar in the user interface.</li>
* <li>{@link #doInBackground}, invoked on the background thread
* immediately after {@link #onPreExecute()} finishes executing. This step is used
* to perform background computation that can take a long time. The parameters
* of the asynchronous task are passed to this step. The result of the computation must
* be returned by this step and will be passed back to the last step. This step
* can also use {@link #publishProgress} to publish one or more units
* of progress. These values are published on the UI thread, in the
* {@link #onProgressUpdate} step.</li>
* <li>{@link #onProgressUpdate}, invoked on the UI thread after a
* call to {@link #publishProgress}. The timing of the execution is
* undefined. This method is used to display any form of progress in the user
* interface while the background computation is still executing. For instance,
* it can be used to animate a progress bar or show logs in a text field.</li>
* <li>{@link #onPostExecute}, invoked on the UI thread after the background
* computation finishes. The result of the background computation is passed to
* this step as a parameter.</li>
* </ol>
复制代码
异步任务通过4个步骤去执行:
- onPreExecute():任务开启前调用在UI线程上。通常在此方法上做一些初始化工作,例如展示一个进度条。
- doInBackground():在onPreExecute()执行完成后立即执行,在子线程上。这个方法内通常做一些耗时的后台计算,上面的泛型类型参数将传递到这个方法,计算结果必须由此步骤返回,并传递回最后一步。在这个方法内,还可以使用 publishProgress() 方法发布一个或者多个的进度单位,就是回调到方法 onProgressUpdate() 上,此方法运行在UI线程。
- onProgressUpdate():在调用 publishProgress() 方法后执行,但是执行的时间不确定。通常用户在后台计算执行是,显示任务进度。
- onPostExecute():在后台计算任务结束后执行。doInBackgroud() 的返回值将传递给此方法。
* <h2>Cancelling a task</h2>
* <p>A task can be cancelled at any time by invoking {@link #cancel(boolean)}. Invoking
* this method will cause subsequent calls to {@link #isCancelled()} to return true.
* After invoking this method, {@link #onCancelled(Object)}, instead of
* {@link #onPostExecute(Object)} will be invoked after {@link #doInBackground(Object[])}
* returns. To ensure that a task is cancelled as quickly as possible, you should always
* check the return value of {@link #isCancelled()} periodically from
* {@link #doInBackground(Object[])}, if possible (inside a loop for instance.)</p>
复制代码
任务可以调用 cancel(boolean) 去取消。取消后导致 isCancelled() 方法返回 true,并且会调用 onCancelled() 而不是 onPostExecute() 在方法 doInBackgroud() 完成后。为了确保能尽快地 cancel 任务,在 doInBackgroud 中应该时刻去判断任务是否被取消,尤其是在一个循环内,否则会做很多无用功。
* <h2>Threading rules</h2>
* <p>There are a few threading rules that must be followed for this class to
* work properly:</p>
* <ul>
* <li>The AsyncTask class must be loaded on the UI thread. This is done
* automatically as of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.</li>
* <li>The task instance must be created on the UI thread.</li>
* <li>{@link #execute} must be invoked on the UI thread.</li>
* <li>Do not call {@link #onPreExecute()}, {@link #onPostExecute},
* {@link #doInBackground}, {@link #onProgressUpdate} manually.</li>
* <li>The task can be executed only once (an exception will be thrown if
* a second execution is attempted.)</li>
* </ul>
复制代码
AsyncTask 需要遵循一些线程规则才能正常工作。
- AndroidVersion<16的时候,AsyncTask 必须在UI线程中加载,因为需要获取到UI线程的Handler,不过在16后就不需要了,因为16后面默认加载主线程的Handler。
- 实例必须在UI线程上被创建
- execute 必须在UI线程上被调用
- 请不要人为调用:onPreExecute,doInBackgroud,onProgressUpdate,onPostExecute
- 任务只能被执行一次,执行多次会抛出异常
* <h2>Memory observability</h2>
* <p>AsyncTask guarantees that all callback calls are synchronized in such a way that the following
* operations are safe without explicit synchronizations.</p>
* <ul>
* <li>Set member fields in the constructor or {@link #onPreExecute}, and refer to them
* in {@link #doInBackground}.
* <li>Set member fields in {@link #doInBackground}, and refer to them in
* {@link #onProgressUpdate} and {@link #onPostExecute}.
* </ul>
复制代码
保证一些方法的执行顺序,构造函数会先执行,然后到 onPreExecute,接着是 doInBackgroud,然后多次调用 onProgressUpdate ,最后是 onPostExecute。
* <h2>Order of execution</h2>
* <p>When first introduced, AsyncTasks were executed serially on a single background
* thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
* to a pool of threads allowing multiple tasks to operate in parallel. Starting with
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single
* thread to avoid common application errors caused by parallel execution.</p>
* <p>If you truly want parallel execution, you can invoke
* {@link #executeOnExecutor(java.util.concurrent.Executor, Object[])} with
* {@link #THREAD_POOL_EXECUTOR}.</p>
复制代码
在一开始,AsyncTask 任务的执行是串行执行,使用一条工作线程,从 Android-DONUT,AndroidVersion4 开始,使用线程池来创建线程,和允许并行执行任务,而从 AndroidHoneyComb,AndroidVersion11开始,支持串行和并行,默认提交是串行执行任务,避免了多线程并发导致脏资源的情况出现。当然,如果你想使用并行执行,可以调用 executeOnExecutor(THREAD_POOL_EXECUTOR) 提交。
串行执行
两个线程池
/**
* An {@link Executor} that executes tasks one at a time in serial
* order. This serialization is global to a particular process.
*/
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
复制代码
首先看到一个Executor接口,由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);
}
}
}
复制代码
SerialExecutor类里面用到一个双端队列ArrayDeque?
ArrayDeque不是线程安全的。 ArrayDeque不可以存取null元素,因为系统根据某个位置是否为null来判断元素的存在。 当作为栈使用时,性能比Stack好;当作为队列使用时,性能比LinkedList好。
SerialExecutor 内部的实现,内部不复杂,一个双端队列数据结构,从尾部 offer 添加任务,poll 出队列的第一个任务进行执行。而SerialExecutor 只是一个调用任务的线程池而已,最后的执行是交由:THREAD_POOL_EXECUTOR.execute(mActive);
,真正执行任务的是一个 ThreadPoolExecutor 线程池,静态加载的时候就被初始化好。所以这里的SerialExecutor线程池只是用来保证任务逐一被执行。
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;
}
复制代码
任务的提交
AsyncTask.execute:
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
复制代码
再查到,executeOnExecutor:
@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;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
复制代码
首先对任务的状态进行判断,从这里可以看到任务只能被执行一次,否则多次调用execute会抛出异常。还有注意看到有一个 @MainThread 的注解,应该是对方法执行的位置进行判断是否是主线程。接着初始化任务,调用 onPreExecute() 方法,调用 传入参数 exec.execute(mFuture) 当然默认就是上面的 SerialExecutor 实例。
worker & future
再回到我们的 AsyncTask.execute(),mWorker.mParams = params,初始化参数,然后,exec.execute(mFuture),这里的 wWorker 和 mFuture 是什么呢?我们找到定义和初始化的地方:
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
复制代码
public AsyncTask() {
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 是 WorkerRunnable 的实例对象,WorkerRunnable 是实现了 Callable 接口,其还内部存储了初始化的参数 params,在上面的构造方法中,mWorker 用一个匿名内部类实现了 callable 的 call() ,其里面就是 doInBackgroud 方法的执行,最后返回一个 result,调用 postResult(result),后面再说这个方法。
mFuture 是 FutureTask 的一个示例,传入了 mWorker 任务,exec.excute() 后,便开始执行任务,mFuture 内部 postResultIfNotInvoked(get());
,get() 是 FutureTask 的方法,一直阻塞运行 mWorker 任务,得到结果后,postResultIfNotInvoked(result)
:
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
复制代码
mTaskInvoked 是一个原子布尔值,多线程下安全的一个Boolean值,这里标记是否已经回调过结果,没有则:postResult(result)
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
复制代码
handler
看到这里,handler ?没错,又是通过它进行回调到别的线程上,那是别的什么线程?我们前面知道,结果反馈的方法 onPostExecute 运行在UI线程,我们查到这个 Handler 的源码:
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}
@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;
}
}
}
复制代码
bingo,这里的Handler是UI线程的,当然在处理方法里面我们也看到了,进度条的更新方法,和结果的回调方法finish
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
复制代码
这里,如果正常结束任务就是回调 onPostExecute,如果中途取消任务,则最后回调的是 onCancelled。
进度刷新
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
复制代码
调用在 worker 线程,通过 handler 回调到UI线程上。
取消任务
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
复制代码
真的可以取消正在运行的任务吗?偷偷看看注释:
* <p>Attempts to cancel execution of this task. This attempt will
* fail if the task has already completed, already been cancelled,
* or could not be cancelled for some other reason. If successful,
* and this task has not started when <tt>cancel</tt> is called,
* this task should never run. If the task has already started,
* then the <tt>mayInterruptIfRunning</tt> parameter determines
* whether the thread executing this task should be interrupted in
* an attempt to stop the task.</p>
复制代码
你个糟老头坏得很,注释很清楚,有可能会取消失败。最终调用的是 FutureTask 的cancel方法,摘个大神的源码分析结果:FutureTask介绍及使用
cancel()方法改变了futureTask的状态位,如果传入的是false并且业务逻辑已经开始执行,当前任务是不会被终止的,而是会继续执行,直到异常或者执行完毕。如果传入的是true,会调用当前线程的interrupt()方法,把中断标志位设为true。
事实上,除非线程自己停止自己的任务,或者退出JVM,是没有其他方法完全终止一个线程的任务的。mayInterruptIfRunning=true,通过希望当前线程可以响应中断的方式来结束任务。当任务被取消后,会被封装为CancellationException抛出。
先不深究 FutureTask 的源码,反正我们已经知道,这个 cancel 方法不靠谱,源码的注释接着也墙裂建议我们,在 doInBackgroud 方法里面,去做是否任务被取消的判断,尽快地去结束任务。
还有一个看到上面的分析说,当任务被取消后,会被封装为CancellationException抛出。 有点眼熟,在 mFuture 初始化中,有捕抓这个异常:
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);
}
}
};
复制代码
当捕抓到了,就说明取消了,以一个 null 的参数传入 postResultIfNotInvoked,继续上面的逻辑。
并行执行
并行任务可以这样提交:executeOnExecutor(java.util.concurrent.Executor, Object[])
,executeOnExecutor 方法内部逻辑跟上面串行的方法逻辑类似:
@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;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
复制代码
接下来的逻辑就跟上面的一样咯。
总结
- AsyncTask 串行还是并行跟版本有关,AndroidVersion<4,串行,11>AndroidVersion>=4,并行,并行后,出现很多多线程并发访问资源的问题,AndroidVersion>=11后,可以是并行,也可以是串行,而且并行串行都是用同一个线程池去执行。
- AsyncTask 的 cancel 不保证一定可以取消任务,需要在 doInBackgroud中时刻去判断任务是否别取消,尽早结束任务。
- 按照注释,严格地在指定线程上开启任务
- AsyncTask如果在Activity中使用,要注意内存泄漏问题,和屏幕旋转问题
- 如果任务是串行,必须要等前面任务执行完毕后,才能开始启动下一个
-
AsyncTask虽然可以以简短的代码实现异步操作,但是正如本文提到的,你需要让AsyncTask正常工作的话,需要注意很多条条框框。推荐的一种进行异步操作的技术就是使用Loaders。