关于 AsyncTask 的一次深度解析
前言##
任何一个Android 开发者对AsnycTask 都应该不陌生;使用AsyncTask可以很方便的异步处理耗时操作;AsyncTask内部对Handler和Thread进行了封装,简化了Handler的使用方式,使用起来非常方便。首先看一下Android SDK 中关于AsyncTask的使用示例:
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled()) break;
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
在onCreate中,执行这个任务即可:
new DownloadFilesTask().execute(url1, url2, url3);
使用起来,的确是很方便(当然和当下非常流行的Retrofit、Volley相比还是有些繁琐)。
但是你想过下面这些问题吗;为什么doInBackground就可以用来进行耗时操作?为什么onPostExecute和onProgressUpdate就可以进行UI更新?是谁规定了这些方法处于UI线程的?publishProgress 传递的参数是怎样来到onProgressUpdate方法中的?怎样取消一个正在执行的耗时操作?如果你自己去封装Handler和Thread你会怎么做?
好了,带着这些疑问,让我们去看看AsyncTask的实现原理。
AsyncTask的实现原理可以说是十分巧妙,整个代码除去注释仅有300多行,但功能却非常强大,原因就是他用到了Java自带的并发工具包 java.util.concurrent,这个包包含有一系列能够让 Java 的并发编程变得更加简单轻松的类。好了为了方便理解AsyncTask的实现原理,先普及一波AsyncTask中用到一些java基础知识。
基础知识##
阻塞队列 BlockingQueue####
BlockingQueue 通常用于一个线程生产对象,而另外一个线程消费这些对象的场景。下图是对这个原理的阐述:
AsyncTask 默认串行很明显,一秒一个,默认就是串行执行。前面提到了ArrayDeque 是一个自增长的队列。因此,默认情况下,可以创建无数个AsyncTask任务。
这127个任务串行执行下去,要等到他们都执行完毕,得2分多钟,不行我不想等了,我想要并行,那也不难,虽然
AsyncTask默认的执行器是串行执行,但是我们可以这样做:
for (int i = 0; i < 127; i++) {
new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "AsyncTask#" + i);
}
我们可以去修改execute 的实现方法,替换默认的sDefaultExecutor为AsyncTask.THREAD_POOL_EXECUTOR,再次执行我们看一下日志:
并行可以看到每一秒有5个线程同时执行,这样127个线程用26秒就可以执行完了,比串行执行快了差不多5倍。为什么是5倍呢?这就要回到我们之前AsyncTask 属性定义当中了。
在定义并行执行的线程池当时, CORE_POOL_SIZE = CPU_COUNT + 1; 线程池核心程数为CPU 核心数+1,因此在我当前的4核手机上最大线程数为5。
串行执行的时候由于ArrayDeque 的优点,可以依次执行很多个任务,那并行呢?这里先说一下结论,以CPU 核心数为4的手机为例,最多一次可以执行137个任务。这个也很好理解,
线程池最大容量为9,阻塞队列容量为128,也就说在并行的时候,最多允许137个任务被阻塞,再多就不行了。
修改之前的代码:
for (int i = 0; i < 138; i++) {
new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "AsyncTask#" + i);
}
运行程序时,会抛出异常:
java.util.concurrent.RejectedExecutionException: Task android.os.AsyncTask$3@1429681 rejected from java.util.concurrent.ThreadPoolExecutor@bfb9326[Running, pool size = 9, active threads = 9, queued tasks = 128, completed tasks = 0]
public interface RejectedExecutionHandler {
/**
* Method that may be invoked by a {@link ThreadPoolExecutor} when
* {@link ThreadPoolExecutor#execute execute} cannot accept a
* task. This may occur when no more threads or queue slots are
* available because their bounds would be exceeded, or upon
* shutdown of the Executor.
*
* <p>In the absence of other alternatives, the method may throw
* an unchecked {@link RejectedExecutionException}, which will be
* propagated to the caller of {@code execute}.
*
* @param r the runnable task requested to be executed
* @param executor the executor attempting to execute this task
* @throws RejectedExecutionException if there is no remedy
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
很明显,这次是因为我们要执行的任务数已经超过了线程池能够承载的最多量,因此抛出了异常。
最后##
AsyncTask 的实现说起来很简单,就是封装了Handler+Thread,但是其细节部分的实现,有许多地方值得去深究。写代码的思路值得借鉴。
AsyncTask 这个类的代码在不同版本的SDK 有着些许差异,以上分析是基于 Android 6.0 ,也就是Android SDK 23 得出。