AsyncTask是Android提供的一个轻量级的处理UI刷新的类,它提供了在子线程处理耗时任务并在UI线程刷新的功能,它实际上就是一个Handler+Thread,内部封装了这两者便于我们来简单编写UI刷新功能的代码。
AsyncTask的使用非常简单:
- 继承AsnycTask类,指定三个泛型参数,实现doInBackground(Result)方法,该方法用来处理耗时操作,会在子线程中执行
- 覆写onProgressUpdate(Progress)方法,用来实时刷新耗时操作的进度,覆写onPostExecute(Result)用来刷新最终处理结果
在不同的android版本中,AsyncTask的实现还是有一些区别的,最主要的区别就是在3.0之前AsyncTask是并行执行任务的,而在3.0之后就默认为串行执行,当然也支持并行执行。
这里就以Android7.0的源码,来分析AsyncTask的实现原理。
AsyncTask是一个抽象类,它的3个泛型参数分别表示耗时处理需要用到的参数,耗时操作的进行以及耗时操作的返回结果,如果都不需要的话可以传Void。先看一下AsyncTask定义的一些字段属性,这样有助于后面的理解。
public abstract class AsyncTask<Params, Progress, Result> {
//CPU数量
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//线程池的核心线程数量,创建线程池会用到
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;
//线程的保活时间为30秒,创建线程池会用到
private static final int KEEP_ALIVE_SECONDS = 30;
//线程工厂类,创建线程池要用到
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
//原子数,在高并发下是线程安全的,用来给线程生成一个唯一的名称
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
//阻塞队列,创建线程池要用到,默认128个大小
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
//执行异步任务的线程池
public static final Executor THREAD_POOL_EXECUTOR;
static {
//AsyncTask被加载后,就会初始化一个线程池出来
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
//串行的分发执行任务
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
//handler消息,用来在主线程中处理耗时操作返回的结果
private static final int MESSAGE_POST_RESULT = 0x1;
//handler消息,用来在主线程中处理耗时操作的实时进度
private static final int MESSAGE_POST_PROGRESS = 0x2;
//默认的Executor是串行来分发执行任务的
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
//内部持有Looper.getMainLooper()的handler
private static InternalHandler sHandler;
//一个Callable对象,在它的call方法中处理耗时操作
private final WorkerRunnable<Params, Result> mWorker;
//FutureTask一般与Callable配合使用,可以对任务进行查询、取消、获取结果操作。
private final FutureTask<Result> mFuture;
//AsyncTask在被创建的时候的状态为PENDING,表示可执行状态,一个任务AsyncTask只能被执行一次
private volatile Status mStatus = Status.PENDING;
//标识当前任务是否取消
private final AtomicBoolean mCancelled = new AtomicBoolean();
//标识当前任务是否已执行
private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
//串行分发执行任务的实现,在调用AsyncTask.execute()的时候默认就会走到SerialExecutor.execute()里面
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
//当前正在执行的任务
Runnable mActive;
public synchronized void execute(final Runnable r) {
//创建一个runnable将其放入ArrayDeque队列中
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
//当一个任务执行完成后就会触发执行下一个任务,所以这里是串行执行任务的
scheduleNext();
}
}
});
//如果当前没有任务在执行,就触发执行下一个任务
if (mActive == null) {
scheduleNext();
}
}
//执行下一个任务,实际就是从ArrayDeque任务队列中取出runnable交给线程池来处理
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
}
AsyncTask默认是串行执行任务的原因就是private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
这一句,sDefaultExecutor的默认值就是一个串行的Executor。
AsyncTask调用的入口函数是在execute(),从这里开始分析AsyncTask的实现原理。
//一般来调用此方法来执行任务,这里默认就串行执行的
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
//同样可以调用此方法来执行THREAD_POOL_EXECUTOR或者自定义一个Executor来执行任务都是可以的
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
//如果AsyncTask不是准备状态就会抛出异常,所以说AsyncTask只能执行一次
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;
//开始执行任务
exec.execute(mFuture);
return this;
}
在AsyncTask执行耗时操作之前会先执行onPreExecute()方法。所以可以在处理任务之前做一些初始化操作,比如显示一个弹框等,前提就是必须在主线程中调用execute(),否则就可能出错。在这里将mFuture交给了executor来执行,mFuture是一个FutureTask对象,如果不清楚FutureTask、Future和Callable的可以自行查阅,这里就不细说了。mFuture是在构造方法中初始化的,进入里面查看:
public AsyncTask() {
//初始化一个Callable对象
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
//标记当前任务已经执行
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//这里调用了doInBackground()来处理耗时操作
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
//将结果抛到主线程去
return postResult(result);
}
};
//初始化FutureTask对象,将mWorker这个Callable对象传入了进去,执行FutureTask任务的时候,其实就是在执行mWorker这个任务
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
//这里覆写了done()方法,当任务执行完成后会调用此方法,
//不管任务是否真正的完成了还是发生了异常还是取消了都会走到这里
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);
}
}
};
}
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
//因为在call里面将mTaskInvoked设置了true,所以一般这里走不到
if (!wasTaskInvoked) {
postResult(result);
}
}
//实际上就是一个Callable,里面只保存了传递进来的参数。
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
mWorker实际上就是一个Callale对象,和Runnable类似,只不过Callable有返回值,在它的call方法中,调用了doInBackground()方法来处理耗时操作,doInBackground()就是自己实现的。最后调用了postResult()将结果抛到主线程中去。到这里也就应该清楚AsyncTask的整体流程了,很简单。
那么还是来看一下它的几个postxxx()方法吧。
private Result postResult(Result result) {
//将结果通过handler抛到主线程中去
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
在postResult()方法中会先使用AsyncTaskResult对象将结果封装然后抛到主线程中去。
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
AsyncTaskResult则是封装了AsyncTask这个任务和Result结果。
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
result.mTask.finish(result.mData[0]);
break;
在handleMessage中则是调用了AsyncTask的finish方法来处理最好的结果的。
private void finish(Result result) {
//如果任务被取消,则调用onCancelled()方法
if (isCancelled()) {
onCancelled(result);
} else {
//onPostExecute()是一个空实现,需要自己实现来处理最后的结果
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
在finish()方法中看到了熟悉的身影,onPostExecute()就是在这里被调用的。
至于onProgressUpdate()则是需要自己主动触发才能调用的,在doInBackground()方法实现中,需要主动调用publishProgress()方法才能触发onProgressUpdate()的调用,它的原理还是通过handler消息来调用onProgressUpdate()方法的,所以说onProgressUpdate()也是在主线程中执行的。
AsyncTask中的一些坑
尽管AsyncTask简化了我们在子线程和主线UI之间的一些工作量,但是它却存在一些致命的缺点,如果不注意的话,很容易掉进坑里。
缺点1:
在1.6之前,AsyncTask是串行执行任务的,后来又在1.6-2.3之间改成了并行,在3.0之后又改成了串行,不过3.0之后也支持并行。如果想使用并行方式,只需要需要调用executeOnExecutor()即可。所以一定得弄清楚自己是想并行还是串行,否则可能达不到想要的效果。
缺点2:
如果使用的是并行方式,一定要注意任务数量大小,否则在线程池中会抛出java.util.concurrent.RejectedExecutionException
异常。在不同的版本这个最大值是不一样的,比如在4.1上,线程池的最大线程数是128个,等待线程是10,所以同时支持最大线程是138个,一旦执行的任务超过了138个就会抛出异常。
private static final int CORE_POOL_SIZE = 5;
//最大128个线程
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 1;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
//最多10个线程阻塞
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(10);
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
又比如在7.0上面,线程数量是依照cpu核心数的,不同的CPU可能会有不同的最大值限制。
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 ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
但是不管怎么样,一定要注意执行任务的数量限制,否则会导致应用程序崩溃。
缺点3:
如果AsyncTask使用不当,容易导致内存泄露,AsyncTask的生命周期并不是绑定在activity的,就算activity调用了finish,AsyncTask也是继续存在,继续执行后台任务的,所以如果在activity中没有将AsyncTask声明为静态,就会持有activity对象,造成内存泄露。同时如果AsyncTask执行完结果后发现在更新UI的时候,发现activity已经finish掉了,可能会因为一些UI控件找不到报异常。在切换横竖屏的时候,activity被重新创建后,AsyncTask持有的还是原来的那个activity,会导致我们的UI不会更新,处理结果也拿不到了。
缺点4:
cancel()取消任务可能并不好使,AsyncTask的cancel()实际上调用的是FutureTask的cancel(),而FutureTask.cancel()里面实际上调用的是Thread的interrupt()方法。
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
U.compareAndSwapInt(this, STATE, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try {
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally {
U.putOrderedInt(this, STATE, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
如果清楚interrupt()方法的话就很容易明白这一点。如果一个线程正在正常的执行,interrupt是不会中断线程的,interrput()这个方法名称比较误导人,它的作用是如果线程一个在阻塞状态,调用interrupt()会让该线程退出阻塞状态,并抛出一个InterruptedException异常。所以如果使用AsyncTask来处理网络请求的话,在处理IO的时候线程并不是一个阻塞状态,所以调用cancel()并没有什么卵用,任务还是在继续执行。
所以除非你明确的知道使用AsyncTask在干什么,如果不了解AsyncTask的一些细节,一定要慎用。