AsyncTask 笔记整理
源码基于 Android API 25
1、概述
AsyncTask 是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新 UI。
AsyncTask 是一个抽象的泛型类。一般需要重写下面四个方法:
// 1、在线程池中执行,用于执行耗时的异步任务
@WorkerThread
protected abstract Result doInBackground(Params... params);
// 2、实际上是在调用 execute(Params... params) 的线程中执行,但是无法保证一定在主线程中被调用。
// 只不过一般是在主线程中调用。(后面讲解源码时会说说明清楚)
@MainThread
protected void onPreExecute();
// 3、当 doInBackground() 执行完之后,会在主线程中回调该方法
// result 即 doInBackground() 的返回值
@SuppressWarnings({"UnusedDeclaration"})
@MainThread
protected void onPostExecute(Result result);
// 4、在主线程中被回调,可以用于更新 UI 进度等。
// 该方法需要通过主动调用 publishProgress(Progress... values) 才能被回调到
@SuppressWarnings({"UnusedDeclaration"})
@MainThread
protected void onProgressUpdate(Progress... values)
以及三个范型参数代表的含义:
AsyncTask<Params, Progress, Result>
Params: 表示 execute(Params... params) 方法传递的参数类型
Progress:表示 onProgressUpdate(Progress... values) 中进度值类型
Result:表示 onPostExecute(Result result) 中的结果类型,也是 doInBackground() 方法返回值的类型
2、源码分析
2.1 内部的线程池
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
// 其属性是 public,意味着可以通过 AsyncTask.THREAD_POOL_EXECUTOR 直接拿到
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;
}
维护的线程池,其核心线程数为 CPU 数量 - 1
,但是会保证在 [2, 4]
的范围内,最大线程数为 CPU 数量 * 2 + 1
,线程的存活时间为 30s,且等待队列最多容纳数量为 128。
且该线程池是静态变量,即所有的 AsyncTask 实例都共享该线程池,因此所有实例提交的任务都是在该线程池中被执行。
2.2 关联主线程的 Handler
AsyncTask 内部会维护一个静态的 Handler,用于从线程池中线程环境切换到主线程中。
private static InternalHandler sHandler;
private static Handler getHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}
sHandler 会在首次使用时被初始化。
private static class InternalHandler extends Handler {
public InternalHandler() {
// 直接获取主线程的 Looper
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;
}
}
}
当 InternalHandler 被初始化的时候,会主动关联主线程的 Looper,从而实现切换到主线程。
因此没必要非得在主线程中初始化该 Handler。(以当前版本来说)
而传闻之前的版本,为了能够将执行环境切换到主线程,这就要求 sHandler 这个对象必须在主线程创建。由于静态成员会在加载类的时候进行初始化,因此这就变相要求 AsyncTask 的类必须在主线程中加载,否则同一个进程中的 AsyncTask 都将无法正常工作。
2.3 任务串行提交器
AsyncTask 内部还维护者一个静态的 Executor 实例变量 SERIAL_EXECUTOR。
(有的文章说这也是一个线程池,但是个人认为说是线程池并不准确,因为 Executor 接口的目的是了将任务的提交与执行分离,SERIAL_EXECUTOR 实际上也没有涉及到关于线程的处理 )
SERIAL_EXECUTOR 实际上是 SerialExecutor 类型,简单的说它是一个串行任务提交器。
默认情况下,通过 AsyncTask#execute(Params... params()
提交的 真正的任务 会经由 SERIAL_EXECUTOR.execute()
包装起来并保存到 SERIAL_EXECUTOR.mTasks
中,然后在一定的时机触发下,会将 mTasks 中 包装任务 串行的提交到 THREAD_POOL_EXECUTOR 中,因此真正的任务也会串行的被提交到线程池中。(通过 SERIAL_EXECUTOR,只有当上一个任务被执行完了,才会使得下一个任务被提交到线程池中。)
因此,默认情况下,就算是并发的通过 SERIAL_EXECUTOR 提交数个任务,但还是只会串行的被提交到线程池中执行。
public static final Executor SERIAL_EXECUTOR = new 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 {
// 触发 mTasks 中下一条包装任务提交到 THREAD_POOL_EXECUTOR 中
scheduleNext();
}
}
});
// 触发 mTasks 中第一个任务的执行,从而使得 mTasks 里的包装任务都能够被执行
// 因为在包装任务中,除了执行原始的任务外,还会执行 scheduleNext()
// 而 scheduleNext() 的作用就是将包装任务提交到 THREAD_POOL_EXECUTOR 中去执行
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
2.4 任务的提交
任务提交是通过 execute() 方法进行的。该方法存在两个具体的实现:
// (1)静态方法
public static void execute(Runnable runnable) {
// 默认情况下,直接提交到 SERIAL_EXECUTOR.mTask 中
sDefaultExecutor.execute(runnable);
}
// (2)成员方法,会将任务需要的参数 params 传递过来
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
// 默认情况下 sDefaultExecutor 为 SERIAL_EXECUTOR
return executeOnExecutor(sDefaultExecutor, params);
}
其中 execute(Params... params)
会进一步调用 executeOnExecutor(Executor exec, Params... params)
:
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
// executeOnExecutor() 只能调用一次,因为首次调用后 mStatus 会被赋值为 Status.RUNNING
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()
onPreExecute();
mWorker.mParams = params;
// 默认情况下通过 SERIAL_EXECUTOR 串行的提交任务
exec.execute(mFuture);
return this;
}
executeOnExecutor() 只能调用一次,因为 mStatus 被赋值为 Status.RUNNING 后,就无法再次执行,否则会抛出异常。
可以看到,通过 execute(Params... params)
方法,会把任务需要的参数 params 赋值给 mWorker.mParams,然后通过 sDefaultExecutor 将 mFuture 提交。
而且,还会调用之前说过的 onPreExecute()
方法,这里就解释了为什么该方法无法保证一定在主线程中被调用,因为它是处于调用 execute(Params... params)
的线程中执行的。
其中,mWorker、mFuture 是在 AsyncTask 的构造方法中初始化的。
mFuture 是 FutureTask 类型,其内部持有 mWorker 的引用,当通过 sDefaultExecutor 将 mFuture 提交后,正常情况下最终会在线程池中调用 mFuture.run()
,从而会进一步调用 mWorker.call()
。
```java
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
// 将 mTaskInvoked 设置为 true,表示当前任务已经被调用
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
// 回调 doInBackground() 方法,mParams
// 则是通过 execute(Params... params) 传递进来的
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
// 将任务状态直为 Cancelled
mCancelled.set(true);
throw tr;
} finally {
// 将返回值传递给 postResult() 方法
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.call()
中会回调 doInBackground(mParams)
,mParams 即通过 execute(Params... params)
传递进来并在 executeOnExecutor()
被赋值的参数。
当 doInBackground(mParams)
执行完,得到其返回值 result 后,会调用 postResult()
,并将结果传递过去。
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
在 postResult() 中,会将 result 包装为 AsyncTaskResult 类型,其中会把 AsyncTask 的实例引用传递进去并持有:
@SuppressWarnings({"RawUseOfParameterizedType"})
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
然后通过关联了主线程的 handler 将结果传递到主线程中处理。
对于 MESSAGE_POST_RESULT
有 :
// InternalHandler#handleMessage()
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
result.mTask 即之前传递进来的 AsyncTask 的实例引用,因此这里进一步调用 AsyncTask#finish()
:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
// 1、
@SuppressWarnings({"UnusedParameters"})
@MainThread
protected void onCancelled(Result result) {
onCancelled();
}
@MainThread
protected void onCancelled() {
// 默认为空实现,可以覆盖重写
}
此时,如果 AsyncTask 被取消执行,就会调用 onCancelled(result)
,该方法最终会调用 onCancelled()
;
否则,就会回调 onPostExecute(Result result)
方法。
2.5 onProgressUpdate(Progress… values)
通过 onProgressUpdate()
方法,可以在执行异步任务的在主线程中更新 UI。
要触发 onProgressUpdate()
的回调,就要主动调用 publishProgress()
。
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
针对 MESSAGE_POST_PROGRESS
有:
// InternalHandler#handleMessage()
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
关于 publishProgress()
通常是在 doInBackground()
中根据异步任务的进度来主动多次调用的。
3、补充
(1)关于 SERIAL_EXECUTOR
SERIAL_EXECUTOR 的作用就是为了使所有 AsyncTask 实例提交的任务都串行的执行,不然并情况下,可能会造成数据一致性问题,导致新数据可能会被老数据覆盖掉。
如果想要实现并发的执行任务,可以通过 setDefaultExecutor(Executor exec)
将 sDefaultExecutor 设置为自己的线程池,从而在执行 sDefaultExecutor.excute()
的时候 直接将任务提交到自定义的线程池 中去执行,而非通过 SERIAL_EXECUTOR 再提交到 THREAD_POOL_EXECUTOR 中去执行。
public static void setDefaultExecutor(Executor exec) {
sDefaultExecutor = exec;
}
又或者是直接调用 executeOnExecutor(Executor exec, Params... params)
时传递自定义的线程池,也可以实现将任务提交到自定义线程池中去执行的目的。
当然,在使用 executeOnExecutor(Executor exec, Params... params)
的时候,也可以传入 AsyncTask.THREAD_POOL_EXECUTOR
来直接向 THREAD_POOL_EXECUTOR 提交任务。
(2)一些问题
1. 生命周期
很多开发者会认为一个在 Activity 中创建的 AsyncTask 会随着 Activity 的销毁而销毁。然而事实并非如此。AsynTask 会一直执行,直到 doInBackground() 方法执行完毕,然后,如果 cancel(boolean) 被调用,那么 onCancelled(Result result) 方法会被执行;否则,执行 onPostExecute(Result result) 方法。如果我们的 Activity 销毁之前,没有取消 AsyncTask,这有可能让我们的应用崩溃 (crash)。因为它想要处理的 view 已经不存在了。所以,我们是必须确保在销毁活动之前取消任务。总之,我们使用 AsyncTask 需要确保 AsyncTask 正确的取消。
2. 内存泄漏
如果 AsyncTask 被声明为 Activity 的非静态内部类,那么 AsyncTask 会保留一个对 Activity 的引用。如果Activity 已经被销毁,AsyncTask 的后台线程还在执行,它将继续在内存里保留这个引用,导致 Activity 无法被回收,引起内存泄漏。
3. 结果丢失
屏幕旋转或 Activity 在后台被系统杀掉等情况会导致 Activity 的重新创建,之前运行的 AsyncTask 会持有一个之前 Activity 的引用,这个引用已经无效,这时调用 onPostExecute() 再去更新界面将不再生效。
4. 一个 AsyncTask 实例只能执行一次,如果执行第二次将会抛出异常
5. 以当前源码的版本 API 25 来说,MyAsyncTask 不一定非得在主线程中实例化,且 execute(Params… params) 也没有说要强制在主线程中调用,只不过在哪个线程被调用,onPreExecute() 也会相应的在该线程中被调用
上一篇: 修改IE浏览器参数 增强上网安全性
下一篇: 爱奇艺verb无线耳机怎么控制音乐播放?