从源码解析AsyncTask注意事项
AsyncTask是Android中除Handler外另一种方便处理耗时任务后的执行UI操作的工具类。其实这个说法本身有一定歧义,因为TsyncTask内部原理还是用到了Handler,可以说TsyncTask是Handler和线程使用的封装后的工具。
AsyncTask有以下几个注意事项
注意事项:
1.类的加载需要在主线程中
2.对象要在主线程中创建
3.execute在主线程中执行
4.不直接调用onPostExecute,onProgressUpdate,onPostExecute,doInBackground等方法
5.execute只能执行一次
6.Android3.0以后串行执行
源码解析
这些注意事项后续都会结合AsyncTask的源码进行解析。首先来看构造方法:
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
return postResult(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[] mParams;变量。这个mWorker其实就是执行耗时任务的子线程。mFture就是mWorker的FutureTask。AsyncTask的执行需要调用execute()方法,然后调用了executeOnExecutor方法。
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
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的mStatus,RUNNING和FINISHED都会报错,一旦调用该方法状态就会是RUNNING,AsyncTask执行完后mStatus就会是FINISHED,这就解释了注意事项5:execute只能执行一次。onPreExecute()方法需要执行在doInBackground之前的UI操作需要在UI线程中,所以executeOnExecutor方法必须要在UI线程中。这就解释了注意事项3:execute在主线程中执行。sDefaultExecutor的execute(mFuture)继续执行异步类的执行操作。sDefaultExecutor是SerialExecutor对象,从命名上可以猜测sDefaultExecutor的一个单执行的串行线程池。还是从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 内部定义了名为mTasks的ArrayDeque来存储线程,可以从execute方法中看出,每次执行SerialExecutor 只是从mTasks按顺序取出一个线程转交给THREAD_POOL_EXECUTOR去执行。可以看出AsyncTask有2个线程池分别是SerialExecutor和THREAD_POOL_EXECUTOR。SerialExecutor不做具体的线程执行操作而是每次取出一个线程给THREAD_POOL_EXECUTOR只做为“排队线程池“”使用,而THREAD_POOL_EXECUTOR才是执行线程操作的“执行线程池”。这就解释了注意事项:6THREAD_POOL_EXECUTOR是AsyncTask的静态变量。
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
从上述参数可以知道THREAD_POOL_EXECUTOR的核心线程数的CPU的量,最大线程数的2倍CPU数量+1,非核心线程的闲置超时时长为1秒。
THREAD_POOL_EXECUTOR执行的构造方法的mFuture。这就需要重新回看构造方法,主要分析下mWorker:
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
return postResult(result);
}
};
这里可以发现doInBackground()方法,所以doInBackground()本质上还是运行在子线程中去执行耗时任务。doInBackground()方法执行完之后应该需要将UI数据传给UI线程去更新UI,postResult()方法接受了result数据,所以这里应该就说执行向UI线程传递的任务。看下postResult()是如何实现该功能的。
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
Handler!AsyncTask的消息传递也是使用的Handler。既然用的Handler,如果要做UI操作,Handler必须要在主线程中创建。而sHandler是静态成员变量,在类加载的时候已经初始化了,这就解释了注意事项1:类的加载需要在主线程中。
private static InternalHandler sHandler = new InternalHandler();
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;
}
}
}
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
这里Handler做了2个处理,一是更新精度,这个msg.what的通过publishProgress()方法触发;二是处理耗时计算完成或者结束的情况。执行结束的话就会调用result.mTask.onPostExecute()方法。result.mTask就是AsyncTask。onPostExecute()方法需要在主线程中调用,这解释了注意事项2:AsyncTask对象要在主线程中创建。至于注意事项4:不直接调用onPostExecute,onProgressUpdate,onPostExecute,doInBackground等方法。这个比较好理解,因为这些方法在AsyncTask内部都已经调用了,手动调用会打乱AsyncTask的工作流程。
潜在问题
AsyncTask的内存泄漏原理和Handler类似。具体处理就不详细说明了。有些读者可能说在Activity的onDestory()中将AsyncTask取消掉,这里就讲些下AsyncTask的取消AsyncTask.cancel(mayInterruptIfRunning);。调用cancel方法并不能真正立即把task取消掉,而只是把task的状态置为Cancel而已,可以通过isCancelled()方法来判断,然后在doingbackground或其他方法中判断是否被取消,然后做相应的处理,具体根据开发者需要去编写。
需要注意的是调用的cancel方法不会执行onPostExecute(result),而是onCancelled(result)。另一个问题就是在屏幕旋转等造成Activity重新创建时AsyncTask数据丢失的问题。当Activity销毁并创新创建后,还在运行的AsyncTask会持有一个Activity的非法引用即之前的Activity实例。导致onPostExecute()没有任何作用。