对于AsyncTask,想必大家一定不陌生,它是我们网络请求然后更新UI比较常用的一种方式,跟Thread+Handler一样都是我们处于耗时操作,然后通过发送消息去UI线程进行更新UI。如果有同学还是不了解异步的,可以参考一下我的另一篇文章,关于异步消息处理机制的源码分析。http://blog.youkuaiyun.com/qq_435559203/article/details/52787339
简单介绍使用
可能有些同学会更喜欢使用AsyncTask,因为它十分清晰地向大家展示了从准备到子线程到UI线程的步骤,下面我们来看一下。
AsyncTask是一个抽象类,所以我们需要用一个自定义子类去继承它。当继承的时候,需要设定三个泛型参数。
Parmas:
AsyncTask在执行doInBackground()时需要用到的参数。Progress:
在doInBackground()执行时,可以指定返回类型作为执行进度。Result:
当AsyncTask执行完后,根据指定的泛型,将对于的JavaBean进行返回。
讲述完这对于的三个泛型参数,我们分别说一下最常重写的三个方法。
onPreExecute():
void的方法。通常我们在一开始需要准备的操作,例如重置某些参数、显示/隐藏某些UI,都会在这里完成处理。doInBackground():
抽象类AsyncTask的抽象方法,返回值为Result,参数为Parmas…,如我们可以将需要Post的参数以数组的方式传进doInBackground(),当子线程完成网络请求会根据我们需要的Result来return对于的数据类型。onPostExecute():
这里是从子线程到UI线程后的方法。我们这取得doInBackground()返回的Result值,就可以直接更新UI。
源码分析
注:以下源码参考的是。
上面的例子:
new CustomAsyncTask().execute(new String[]{});
那么我们先从构造方法开始看起。源码如下:
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);
}
}
};
}
第二行,初始化WorkerRunnable,WorkerRunnable是一个静态内部抽象类,它实现了Callable接口。接着就是Callable的方法。第四行将mTaskInvoked.set(true),表示当前任务已被调用。第六行就是设置线程优先级。第八行是调用类中的doInBackground(),并将值赋给Result,然后传递给postResult方法。我们先插队看看postResult()代码;
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
大家有没非常熟悉,这里就是我们之前在消息异步处理机制说过的,将消息发送至主线程的写法。这句new AsyncTaskResult(this, result)中,是我们异步任务最后的处理结果。
这里的getHandler(),命名方式就知道是获取一个Handler对象,我们就直接看AsyncTask类自定义的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;
}
}
}
上面的一看就能看到,我们直接看finish()。
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
这里可以看到异步任务执行完之后调用finish()后会调用onPostExecute(result); 这个就是我们一开始说处理耗时操作后获得的结果的方法。最后也将状态改成执行完成。
then,我们接着之前的第十四行,mFuture的初始化,将上面得到的mWorker对象传递进去,FutureTask类实现了RunnableFuture接口,通过这个接口可以方便的取消后台任务以及获取后台任务的执行结果。接着是重写done()方法。该方法里面的postResultIfNotInvoked(get());
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
get()传进来的是mWorker的call的返回值Result,第二行表示或者任务是否被调用。没有的话执行postResult(result)。但是在mWorker初始化时就已经将mTaskInvoked为true,所以一般这个postResult执行不到。
稍微梳理一下。构造方法中,当mWorder的call方法被执行,doInBackground就接开始执行,后台任务执行完之后通过消息处理机制,将我们得到的结果发送给主线程进行之后的操作。上面的分析,
紧接着思路,我们看execute()的方法源码。
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
这里可以看到execute()方法,携带的参数,就是我们doInBackground()到时用到的参,executeOnExecutor返回的类型跟自己本身一致,但是executeOnExecutor()的参数中有一个是sDefaultExecutor,那么sDefaultExecutor是什么呢?简单的说,它是AsyncTask的默认执行器(线程池)。AsyncTask可以以串行(一个接一个的执行)或并行(一并执行)两种方式来执行后台任务,在Android3.0及以后的版本中,默认的执行方式是串行。这个sDefaultExecutor就代表了默认的串行执行器(线程池)。也就是说我们平常在AsyncTask对象上调用execute方法,使用的是串行方式来执行后台任务。我们接着往下看executeOnExecutor()方法。
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;
}
从if语句开始,判断当前的状态是否为RUNNING或FINISHED时,就会抛出IllegalStateException异常。这个意思是我们要执行的异步状态不能是正在执行和执行结束这两个状态。
到十五行,我们将异常状态改成执行状态,然后调用onPreExecute()方法。这里我们之前就说,我们通常在
onPreExecute()做一些异步任务前所需要的做的准备动作。
到十九行,将我们传进来的params参数赋值给了mWorker的mParams成员变量;随后调用exec的execute()方法,其实方法带mFuture参数。这里mWorker和mFutrue,在我们上述AsyncTask的构造方法里就介绍过一下,大家梳理一下。
我们继续分析exec,exec就是我们之前方法的sDefaultExecutor。
那我们点一下这个sDefaultExecutor对象看看它是搞什么的。
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
sDefaultExecutor被赋值为SERIAL_EXECUTOR,那么我们来看一下SERIAL_EXECUTOR:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
哦,原来sDefaultExecutor其实是一个SerialExecutor的对象。那我们看看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);
}
}
}
第二行mTasks是SerialExecutor这个串行线程池的任务缓存队列,第三行定义了一个Runnable变量mActive,他表示当前正执行的AsyncTask对象,之后判断mActive是否为null,若null就调用scheduleNext()方法。
我们先继续,第六行用offer方法向任务缓存队列中添加一个任务,任务的内容就如第七行到第十三行的run()方法。第九行调用的是mFuture对象的run()方法(一开始execute传进来的参数就是mFuture),这run()方法里会调用mWorker的call()方法,那么就如构造方法那时说的,mWorker的call()方法里面就会调用doInBackground方法,我们的后台任务也就开始执行了(需要的翻上去看一下)。
接着刚刚说的scheduleNext()。在scheduleNext()方法中,若缓存队列非空,则调用THREAD_POOL_EXECUTOR.execute方法执行从缓存队列中取出的任务,这时我们的后台任务便开始你真正执行了。
然而THREAD_POOL_EXECUTOR是什么呢?点一下:
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
一眼看出,线程池。再看在AsyncTask的源码:
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;
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);
看完线程池这里,我们回顾一下SerialExecutor。例如我们有多个异步任务,调用execute(s synchronized)方法,第一个任务入队,然后在mActive = mTasks.poll()) != null被取出,并且赋值给mActivte,然后交给线程池去执行。然后第二个任务入队,但是此时mActive并不为null,并不会执行scheduleNext();所以如果第一个任务比较慢,多个任务都会进入队列等待;真正执行下一个任务的时机是,线程池执行完成第一个任务以后,调用Runnable中的finally代码块中的scheduleNext,所以虽然内部有一个线程池,其实调用的过程还是线性的。一个接着一个的执行,相当于单线程。
上面就是对AsyncTask的源码分析,整体是先对AsyncTask的构造方法开始,然后到execute()。在分析execute()的过程中将onPreExecute()、doInBackground()、onPostExecute()串联在一起分析。
AsyncTask的不足
我相信有一部分的同学可能更关心下面说的,因为在面试的时候,面试官会先从Thread+Handler的切入,然后会问与AsyncTask的差异和缺点。针对的AsyncTask中线程池的源码看出,corePoolSize为CPU数加一,maximumPoolSize为CPU数的二倍加一,存活时间为1秒,任务缓存队列为LinkedBlockingQueue,128。所以当超过这个度之后就会抛出java.util.concurrent.RejectedExecutionException;
还有就是AsyncTask是单线程方式进行的,有一些公司对业务需求是同时进行的。
AsyncTask异步任务基本就分析到此,希望能让大家对AsyncTask的认识加深一点点。我们也要培养一种看源码分析的习惯。谢谢大家!
注参考资料:
- http://blog.youkuaiyun.com/lmj623565791/article/details/38614699
- http://www.cnblogs.com/absfree/p/5357678.html
- http://blog.youkuaiyun.com/guolin_blog/article/details/11711405