使用AsyncTask的一般步骤是:
- 定义一个类继承自
AsyncTask
,实现抽象方法
- new 一个
AsyncTask
对象 - 调用
execute()
方法执行任务
那么就一步一步来分析AsyncTask的实现原理,首先看构造函数
/** * Creates a new asynchronous task. This constructor must be invoked on the UI thread. */
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);
}
}
}}
构造函数很简单,就是初始化了2个全局的变量mWorker
和mFuture
,并在创建mFuture
的时候把mWorker
作为参数传递进去。其中mWorker实现了Callable,mFuture是一个FutureTask。关于Callable和FutureTask请参考
然后执行execute()方法
@MainThreadpublic final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
调用了executeOnExecutor(sDefaultExecutor, params);
,params
就是execute()
传入的参数,这个sDefaultExecutor
是在哪里定义的呢?
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
原来sDefaultExecutor
是AsyncTask内部的一个常量指向的是SerialExecutor
。继续跟进executeOnExecutor().
@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一共有三种状态,并且非PENDING状态下调用execute()都会抛出异常
- PENDING 初始状态
- RUNNING 执行状态
- FINISHED 完成状态
第一次调用execute()会把状态置为RUNNING
,任务完成时会把状态置为FINISHED
。这就要求一个AsyncTask只能执行一次execute()只能完成一个后台任务,如果需要处理多个任务,只有重新创建一个AsyncTask。
回到上面的函数,可以看到 onPreExecute()
被最先调用,所以我们可以在这个回调中做一些初始化操作比如开始加载动画(这就是execute()一般都会在UI线程调用的原因)。然后把execute(Parem... param)
传递过来的param赋值给在构造中初始化好的mWorker
的mParams
变量。
再执行exec.execute(mFuture)
,其中exec是上文可以知道指向的是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就是mFuture
r.run();
}
finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
可以看到这里调用了mFuture的run()方法,在来看看这个方法
public void run() {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
//这个的c就是mFuture创建时候,传递的参数mWorker
result = c.call();
ran = true;
}
}
注意c.call(),这个c什么呢?其实就是在AsyncTask构造中创建mFuture的参数mWorker
,转了一大圈其实就是调用了mWorker.call()方法
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//调用doInBackground()做后台任务,mParams就是调用execute()时传递的参数
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
return postResult(result);
}
};
可以看到在这个方法中回调了doInBackground(mParams)
,而参数就是我们调用execute()
时传递的参数。因为WorkerRunnable
是实现Runnable
接口的所以doInBackground
的确是在子线程中执行的。
在doInBackground(mParams)
完成我们自己的后台逻辑之后,把结果作为参数传递给了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;
}
private static Handler getHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
} return sHandler;
}
}
private static class InternalHandler extends Handler {
public InternalHandler() {
//获取主线程Looper来构造Handler
super(Looper.getMainLooper());
}
@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;
}
}
}
这里代码比较简单,就是后台任务返回结果后,把结果封装进message发送,Handler接受到消息后,根据消息类型执行不同的逻辑。我们看到有2种消息类型,MESSAGE_POST_RESULT就是后台任务完成了,而MESSAGE_POST_PROGRESS看命名也大概知道了,没错,就是我们调用publishProgress()
更新进度的时候就会发送这个类型的消息
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
注意 : 3.0 以后,默认会使用主线程的Looper来构造handler,所以不管AsyncTask在那个线程创建或者execute()在那个线程调用,
onPostExecute(result)
,publishProgress()
都是在主线程执行
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
finish()中判断后台任务时候取消过,是,onPostExecute(result)
就不再调用,而是回调onCancelled(result)
。并且设置状态,保证AsyncTask只能执行一次。
3.0之后,AsyncTask默认是串行执行任务的,来看看是怎么实现串行的
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就是mFuture
r.run();
}
finally {
//上一个任务执行完毕后,才会从队列中取出下一个任务
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
//取出队列头部的任务
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
调用AsyncTask.execute()
后会调用SerialExecutor.execute()
,这是AsyncTask中维护的一个静态串行控制器,不管创建多少个AsyncTask都会使用同一个SerialExecutor来完成串行控制。
ArrayDeque
是一个线性双向队列,每一次调用SerialExecutor.execute()
都会把传进来的任务(FutureTask)加入到这个队列中。首先判断mActive
是否为空第一次肯定是null,调用schecduleNext()
会从队列中取出头部任务,交给AsyncTask
维护的线程池去执行,第二次mActive!=null
,schecduleNext()
就执行不到,那如果第二个任务怎么执行呢?注意try..finally中的逻辑,在前一个.run执行完之后,才会又一次schecduleNext()被执行。这就实现了任务的串行控制
SerialExecutor
是一个串行控制器,他会把加入的任务按顺序加入到任务队列中,给任务排序,然后一个一个去处交给AsyncTask
中维护的线程池执行。
AsyncTask
中维护的线程池是一个ThreadPoolExecutor
,这是一个并发线程池,3.0以前默认同一时刻运行的线程数是5个,最大线程数是128个,3.0以后根据CPU配置而定。
所以,在3.0以后我们也是可以让AsyncTask并行执行的,就是不让SerialExecutor来控制任务的串行,即调用AsyncTask.executeOnExcutor(THREAD_POOL_EXECUTOR),参数参入线程池的引用,这样任务就跳过了排序,被直接交给了线程池去执行。
AsyncTask的缺点
AsyncTask我们肯定都用过,优点就不说了,使用简单,简化代码,过程可控。我们来说一说它的缺点
使用不当可能造成内存泄漏。因为涉及到异步操作,非静态内部类/匿名内部类AsyncTask都有可能引起activity的泄漏
任务有没有正确取消导致nullPointer。AsyncTask并不会随着创建它的activity的生命周期的结束而结束,相反它会一直执行直到
doInBackground()
方法执行完毕,如果我们的Activity销毁之前,没有取消 AsyncTask,就有可能引起Crash,因为它想要处理的view已经不存在了。并且,就算调用cancle(true)
来取消任务,也不一定保证成功,因为这个方法是调用Thread.interrupt()
,如果正在做一个IO操作,还会抛出ClosedByInterruptException
异常结果丢失。屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用
onPostExecute()
再去更新界面将不再生效。
的确以上都是在使用AsnycTask过程中可能遇到的问题,但是我认为这些都不是AsyncTask自身的缺陷,而是程序猿代码设计的问题,因为使用
Thread+handler
或者相似的类库都可能出现同样的问题。比如内存泄漏,我们完全可以用静态内部类+弱引用、或者是用MVP解耦来解决。对于cancle()可能不正常取消,那也是Thread的问题。
** AsyncTask真正的缺点是处理多个任务和控制串行、并行**
- 多任务处理,通过源码我们知道一个AsyncTask调用一次execute()处理一次后台任务,如果有多个任务的时候只能多次创建,就像这样的代码
task1.execute()
task2.execute()
task3.execute()
我们还得关心每个task的回调,每一个任务的cancle
处理,并且如果每个任务需要的泛型参数不同的话,就的写多个AsyncTask。
-
串行、并行控制。AsyncTask内部维护了一个静态并发线程池THREAD_POOL_EXECUTOR和一个静态串行任务执行器**SERIAL_EXECUTOR **,这个执行器中实现了串行控制,会循环的取出一个个任务交给并发线程池去执行 。也就是说同时有多个
Task.execute
的时候,其实也是按顺序一个一个串行执行的。如果确实需要并行执行的时候,就需要手动调用AsyncTask.executeOnExecutor(THREAD_POOL_EXECUTOR, Params... params)
传入上述静态并发线程池。所以,AsyncTask在处理串并行的时候还是显得比较尴尬
总结
AsyncTask的内部封装线程池和Handler,暴露不同的执行在不同线程的回调函数,大大简化程序猿写子线程处理耗时任务--主线程刷新UI的逻辑。
常常在网上或者面试的时候被问到为什么AsyncTask必须在主线程创建或者execute()必须在主线程执行?
我觉得这种说法至少不准确的,的分版本,3.0以前,创建AsyncTask的时候会根据当前线程创建一个Handler,如果是子线程而我们又没有手动初始化Looper,程序会直接crash(而3.0以后会默认用主线程Looper创建Handler)onPostExecute(),publishProgress()
等回调都会执行在AsyncTask创建的的线程,execute()
中会回调onPreExecute()
,如果我们在这些回调中更新UI就一定会抛出异常。
所以,3.0以前创建AsynacTask必须在主线程,而3.0以后只要不在
onPreExecute(),publishProgress(),onPostExecute()
回调中更新UI,AsyncTask完全可以不在主线程创建。但是这明显跟我们日常的使用相悖