你真的了解AsyncTask?

<div id="article_content" class="article_content tracking-ad" data-mod="popu_307" data-dsm="post">


<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
虽说现在做网络请求有了Volley全家桶和OkHttp这样好用的库,但是在处理其他后台任务以及与UI交互上,还是需要用到AsyncTask。但是你真的了解AsyncTask吗?</p>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
AsyncTask的实现几经修改,因此在不同版本的<a href="http://lib.csdn.net/base/android" class="replace_word" title="Android知识库" target="_blank" style="color:#df3434; font-weight:bold;">Android</a>系统上表现各异;我相信,任何一个用户量上千万的产品绝对不会在代码里面使用系统原生的AsynTask,因为它蛋疼的兼容性以及极高的崩溃率实在让人不敢恭维。本文将带你了解AsyncTask背后的原理,并给出一个久经考验的AsyncTask修改版。</p>
<a target="_blank" id="more" style="color:rgb(34,34,34); border-bottom-width:1px; border-bottom-style:solid; border-bottom-color:rgb(204,204,204); word-wrap:break-word; font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify"></a><span style="color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify"></span>
<h2 id="AsyncTask是什么?" style="margin:20px 0px 10px; padding:10px 0px 0px; line-height:1; font-family:Cambria,Georgia,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei','Times New Roman',serif; font-size:22px; color:rgb(85,85,85); text-align:justify"><a name="t0" target="_blank"></a>
AsyncTask是什么?</h2>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
AsyncTask到底是什么呢?很简单,<strong>它不过是对线程池和Handler的封装</strong>;用线程池来处理后台任务,用Handler来处理与UI的交互。线程池使用的是<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">Executor</code>接口,我们先了解一下线程池的特性。</p>
<h2 id="线程池ThreadPoolExecutor" style="margin:20px 0px 10px; padding:10px 0px 0px; line-height:1; font-family:Cambria,Georgia,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei','Times New Roman',serif; font-size:22px; color:rgb(85,85,85); text-align:justify"><a name="t1" target="_blank"></a>
线程池ThreadPoolExecutor</h2>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
JDK5带来的一大改进就是<a href="http://lib.csdn.net/base/java" class="replace_word" title="Java 知识库" target="_blank" style="color:#df3434; font-weight:bold;">Java</a>的并发能力,它提供了三种并发武器:并发框架Executor,并发集合类型如ConcurrentHashMap,并发控制类如CountDownLatch等;圣经《Effective Java》也说,尽量使用Exector而不是直接用Thread类进行并发编程。</p>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
AsyncTask内部也使用了线程池处理并发;线程池通过<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">ThreadPoolExector</code>类构造,这个构造函数参数比较多,它允许开发者对线程池进行定制,我们先看看这每个参数是什么意思,然后看看Android是以何种方式定制的。</p>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
ThreadPoolExecutor的其他构造函数最终都会调用如下的构造函数完成对象创建工作:</p>
<table style="border-collapse:collapse; border-spacing:0px; width:auto; border:none; font-size:14px; margin:0px">
<tbody>
<tr style="background-color:rgb(249,249,249)">
<td class="gutter" style="padding:0px; vertical-align:middle; border:none">
<pre style="overflow:auto; font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:13px; margin-top:0px; margin-bottom:0px; padding:0px 20px 0px 0px; color:rgb(102,102,102); line-height:1.6; border:none; text-align:right; background:rgb(247,247,247)"><span class="line" style="height:20px">1</span>
<span class="line" style="height:20px">2</span>
<span class="line" style="height:20px">3</span>
<span class="line" style="height:20px">4</span>
<span class="line" style="height:20px">5</span>
<span class="line" style="height:20px">6</span>
<span class="line" style="height:20px">7</span>
</pre>
</td>
<td class="code" style="padding:0px; vertical-align:middle; border:none">
<pre style="overflow:auto; font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:13px; margin-top:0px; margin-bottom:0px; padding:0px; color:rgb(77,77,76); line-height:1.6; border:none; background:rgb(247,247,247)"><span class="line" style="height:20px"><span class="function" style="color:rgb(66,113,174)"><span class="keyword" style="color:rgb(137,89,168)">public</span> <span class="title" style="color:rgb(62,153,159)">ThreadPoolExecutor</span><span class="params" style="color:rgb(245,135,31)">(<span class="keyword" style="color:rgb(137,89,168)">int</span> corePoolSize,</span>
<span class="line" style="height:20px">                          <span class="keyword" style="color:rgb(137,89,168)">int</span> maximumPoolSize,</span>
<span class="line" style="height:20px">                          <span class="keyword" style="color:rgb(137,89,168)">long</span> keepAliveTime,</span>
<span class="line" style="height:20px">                          TimeUnit unit,</span>
<span class="line" style="height:20px">                          BlockingQueue&lt;Runnable&gt; workQueue,</span>
<span class="line" style="height:20px">                          ThreadFactory threadFactory,</span>
<span class="line" style="height:20px">                          RejectedExecutionHandler handler)</span></span></span>
</pre>
</td>
</tr>
</tbody>
</table>
<ul style="list-style:square; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
<li style="list-style:circle">corePoolSize: 核心线程数目,即使线程池没有任务,核心线程也不会终止(除非设置了allowCoreThreadTimeOut参数)可以理解为“常驻线程”</li><li style="list-style:circle">maximumPoolSize: 线程池中允许的最大线程数目;一般来说,线程越多,线程调度开销越大;因此一般都有这个限制。</li><li style="list-style:circle">keepAliveTime: 当线程池中的线程数目比核心线程多的时候,如果超过这个keepAliveTime的时间,多余的线程会被回收;这些与核心线程相对的线程通常被称为<em>缓存线程</em></li><li style="list-style:circle">unit: keepAliveTime的时间单位</li><li style="list-style:circle">workQueue: 任务执行前保存任务的队列;这个队列仅保存由execute提交的Runnable任务</li><li style="list-style:circle">threadFactory: 用来构造线程池的工厂;一般都是使用默认的;</li><li style="list-style:circle">handler: 当线程池由于线程数目和队列限制而导致后续任务阻塞的时候,线程池的处理方式。</li></ul>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
那么,当一个新的任务到达的时候,线程池中的线程是如何调度的呢?(别慌,讲这么一大段线程池的知识,是为了理解AsyncTask;Be Patient)</p>
<ol style="color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
<li>如果线程池中线程的数目少于corePoolSize,就算线程池中有其他的没事做的核心线程,线程池还是会重新创建一个核心线程;直到核心线程数目到达corePoolSize(常驻线程就位)</li><li>如果线程池中线程的数目大于或者等于corePoolSize,但是工作队列workQueue没有满,那么新的任务会放在队列workQueue中,按照FIFO的原则依次等待执行;(当有核心线程处理完任务空闲出来后,会检查这个工作队列然后取出任务默默执行去)</li><li>如果线程池中线程数目大于等于corePoolSize,并且工作队列workQueue满了,但是总线程数目小于maximumPoolSize,那么直接创建一个线程处理被添加的任务。</li><li>如果工作队列满了,并且线程池中线程的数目到达了最大数目maximumPoolSize,那么就会用最后一个构造参数<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">handler</code>处理;**默认的处理方式是直接丢掉任务,然后抛出一个异常。</li></ol>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
总结起来,也即是说,当有新的任务要处理时,<strong>先看线程池中的线程数量是否大于 corePoolSize,再看缓冲队列 workQueue 是否满,最后看线程池中的线程数量是否大于 maximumPoolSize</strong>。另外,当线程池中的线程数量大于 corePoolSize 时,如果里面有线程的空闲时间超过了 keepAliveTime,就将其移除线程池,这样,可以动态地调整线程池中线程的数量。</p>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
<a target="_blank" href="http://7xp3xc.com1.z0.glb.clouddn.com/20161-18.jpg" class="fancybox" title="风景" style="color:rgb(34,34,34); text-decoration:none; border-bottom-width:1px; border-bottom-style:solid; border-bottom-color:rgb(204,204,204); word-wrap:break-word; background-color:transparent"><img src="http://7xp3xc.com1.z0.glb.clouddn.com/20161-18.jpg" alt="风景" style="border:1px solid rgb(221,221,221); display:block!important; margin:0px auto; max-width:100%; height:auto; padding:3px"></a></p>
<div class="pic-title" style="margin:0px auto; text-align:center"><a target="_blank" href="http://7xp3xc.com1.z0.glb.clouddn.com/20161-18.jpg" class="fancybox" title="风景" style="color:rgb(34,34,34); text-decoration:none; border-bottom-width:1px; border-bottom-style:solid; border-bottom-color:rgb(204,204,204); word-wrap:break-word; background-color:transparent"><span style="min-width:20%; min-height:22px; display:inline-block; padding:10px; margin:0px auto; border-bottom-width:1px; border-bottom-style:solid; border-bottom-color:rgb(217,217,217); font-size:18px; color:rgb(153,153,153); line-height:1.7"><em>风景</em></span></a></div>
<p></p>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
我们以API 22为例,看一看AsyncTask里面的线程池是以什么参数构造的;AsyncTask里面有“两个”线程池;一个<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">THREAD_POOL_EXECUTOR</code>一个<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">SERIAL_EXECUTOR</code>;之所以打引号,是因为其实<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">SERIAL_EXECUTOR</code>也使用<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">THREAD_POOL_EXECUTOR</code>实现的,只不过加了一个队列弄成了串行而已,那么这个<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">THREAD_POOL_EXECUTOR</code>是如何构造的呢?</p>
<table style="border-collapse:collapse; border-spacing:0px; width:auto; border:none; font-size:14px; margin:0px">
<tbody>
<tr style="background-color:rgb(249,249,249)">
<td class="gutter" style="padding:0px; vertical-align:middle; border:none">
<pre style="overflow:auto; font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:13px; margin-top:0px; margin-bottom:0px; padding:0px 20px 0px 0px; color:rgb(102,102,102); line-height:1.6; border:none; text-align:right; background:rgb(247,247,247)"><span class="line" style="height:20px">1</span>
<span class="line" style="height:20px">2</span>
<span class="line" style="height:20px">3</span>
<span class="line" style="height:20px">4</span>
<span class="line" style="height:20px">5</span>
<span class="line" style="height:20px">6</span>
<span class="line" style="height:20px">7</span>
<span class="line" style="height:20px">8</span>
<span class="line" style="height:20px">9</span>
</pre>
</td>
<td class="code" style="padding:0px; vertical-align:middle; border:none">
<pre style="overflow:auto; font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:13px; margin-top:0px; margin-bottom:0px; padding:0px; color:rgb(77,77,76); line-height:1.6; border:none; background:rgb(247,247,247)"><span class="line" style="height:20px"><span class="keyword" style="color:rgb(137,89,168)">private</span> <span class="keyword" style="color:rgb(137,89,168)">static</span> <span class="keyword" style="color:rgb(137,89,168)">final</span> <span class="keyword" style="color:rgb(137,89,168)">int</span> CORE_POOL_SIZE = CPU_COUNT + <span class="number" style="color:rgb(245,135,31)">1</span>;</span>
<span class="line" style="height:20px"><span class="keyword" style="color:rgb(137,89,168)">private</span> <span class="keyword" style="color:rgb(137,89,168)">static</span> <span class="keyword" style="color:rgb(137,89,168)">final</span> <span class="keyword" style="color:rgb(137,89,168)">int</span> MAXIMUM_POOL_SIZE = CPU_COUNT * <span class="number" style="color:rgb(245,135,31)">2</span> + <span class="number" style="color:rgb(245,135,31)">1</span>;</span>
<span class="line" style="height:20px"><span class="keyword" style="color:rgb(137,89,168)">private</span> <span class="keyword" style="color:rgb(137,89,168)">static</span> <span class="keyword" style="color:rgb(137,89,168)">final</span> <span class="keyword" style="color:rgb(137,89,168)">int</span> KEEP_ALIVE = <span class="number" style="color:rgb(245,135,31)">1</span>;</span>
<span class="line" style="height:20px"><span class="keyword" style="color:rgb(137,89,168)">private</span> <span class="keyword" style="color:rgb(137,89,168)">static</span> <span class="keyword" style="color:rgb(137,89,168)">final</span> BlockingQueue&lt;Runnable&gt; sPoolWorkQueue =</span>
<span class="line" style="height:20px">            <span class="keyword" style="color:rgb(137,89,168)">new</span> LinkedBlockingQueue&lt;Runnable&gt;(<span class="number" style="color:rgb(245,135,31)">128</span>);</span>
<span class="line" style="height:20px">            </span>
<span class="line" style="height:20px"><span class="keyword" style="color:rgb(137,89,168)">public</span> <span class="keyword" style="color:rgb(137,89,168)">static</span> <span class="keyword" style="color:rgb(137,89,168)">final</span> Executor THREAD_POOL_EXECUTOR</span>
<span class="line" style="height:20px">            = <span class="keyword" style="color:rgb(137,89,168)">new</span> ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,</span>
<span class="line" style="height:20px">                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);</span>
</pre>
</td>
</tr>
</tbody>
</table>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
可以看到,AsyncTask里面线程池是一个核心线程数为<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">CPU + 1</code>,最大线程数为<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">CPU
 * 2 + 1</code>,工作队列长度为<strong>128</strong>的线程池;并且没有传递<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">handler</code>参数,那么使用的就是默认的Handler(拒绝执行).</p>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
那么问题来了:</p>
<ol style="color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
<li>
<p style="margin-top:0px; margin-bottom:25px">如果任务过多,那么超过了工作队列以及线程数目的限制导致这个线程池发生阻塞,那么悲剧发生,默认的处理方式会直接抛出一个异常导致进程挂掉。假设你自己写一个异步图片加载的框架,然后用AsyncTask实现的话,当你快速滑动ListView的时候很容易发生这种异常;这也是为什么各大ImageLoader都是自己写线程池和Handlder的原因。</p>
</li><li>
<p style="margin-top:0px; margin-bottom:25px">这个线程池是一个静态变量;那么在同一个进程之内,所有地方使用到的AsyncTask默认构造函数构造出来的AsyncTask都使用的是同一个线程池,如果App模块比较多并且不加控制的话,很容易满足第一条的崩溃条件;如果你不幸在不同的AsyncTask的doInBackgroud里面访问了共享资源,那么就会发生各种并发编程问题。</p>
</li><li>
<p style="margin-top:0px; margin-bottom:25px">在AsyncTask全部执行完毕之后,进程中还是会常驻corePoolSize个线程;在Android 4.4 (API 19)以下,这个corePoolSize是hardcode的,数值是5;API 19改成了<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">cpu
 + 1</code>;也就是说,在Android 4.4以前;如果你执行了超过五个AsyncTask;然后啥也不干了,进程中还是会有5个AsyncTask线程;不信,你看:</p>
</li></ol>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
<a target="_blank" href="http://7xp3xc.com1.z0.glb.clouddn.com/201512/1453101991064.png" class="fancybox" style="color:rgb(34,34,34); text-decoration:none; border-bottom-width:1px; border-bottom-style:solid; border-bottom-color:rgb(204,204,204); word-wrap:break-word; background-color:transparent"><img src="http://7xp3xc.com1.z0.glb.clouddn.com/201512/1453101991064.png" width="277" alt="" style="border:1px solid rgb(221,221,221); display:block!important; margin:0px auto; max-width:100%; height:auto; padding:3px"></a></p>
<h3 id="Handler" style="margin:20px 0px 10px; padding:10px 0px 0px; line-height:1; font-family:Cambria,Georgia,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei','Times New Roman',serif; font-size:20px; color:rgb(85,85,85); text-align:justify"><a name="t2" target="_blank"></a>
Handler</h3>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
AsyncTask里面的handler很简单,如下(API 22代码):</p>
<table style="border-collapse:collapse; border-spacing:0px; width:auto; border:none; font-size:14px; margin:0px">
<tbody>
<tr style="background-color:rgb(249,249,249)">
<td class="gutter" style="padding:0px; vertical-align:middle; border:none">
<pre style="overflow:auto; font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:13px; margin-top:0px; margin-bottom:0px; padding:0px 20px 0px 0px; color:rgb(102,102,102); line-height:1.6; border:none; text-align:right; background:rgb(247,247,247)"><span class="line" style="height:20px">1</span>
<span class="line" style="height:20px">2</span>
<span class="line" style="height:20px">3</span>
<span class="line" style="height:20px">4</span>
<span class="line" style="height:20px">5</span>
</pre>
</td>
<td class="code" style="padding:0px; vertical-align:middle; border:none">
<pre style="overflow:auto; font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:13px; margin-top:0px; margin-bottom:0px; padding:0px; color:rgb(77,77,76); line-height:1.6; border:none; background:rgb(247,247,247)"><span class="line" style="height:20px"><span class="keyword" style="color:rgb(137,89,168)">private</span> <span class="keyword" style="color:rgb(137,89,168)">static</span> <span class="keyword" style="color:rgb(137,89,168)">final</span> InternalHandler sHandler = <span class="keyword" style="color:rgb(137,89,168)">new</span> InternalHandler();</span>
<span class="line" style="height:20px"></span>
<span class="line" style="height:20px"><span class="function" style="color:rgb(66,113,174)"><span class="keyword" style="color:rgb(137,89,168)">public</span> <span class="title" style="color:rgb(62,153,159)">InternalHandler</span><span class="params" style="color:rgb(245,135,31)">()</span> </span>{</span>
<span class="line" style="height:20px">    <span class="keyword" style="color:rgb(137,89,168)">super</span>(Looper.getMainLooper());</span>
<span class="line" style="height:20px">}</span>
</pre>
</td>
</tr>
</tbody>
</table>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
注意,这里直接用的主线程的Looper;如果去看API 22以下的代码,会发现它没有这个构造函数,而是使用默认的;默认情况下,Handler会使用当前线程的Looper,如果你的AsyncTask是在子线程创建的,那么很不幸,你的<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">onPreExecute</code>和<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">onPostExecute</code>并非在UI线程执行,而是被Handler
 post到创建它的那个线程执行;如果你在这两个线程更新了UI,那么直接导致崩溃。这也是大家口口相传的<strong>AsyncTask必须在主线程创建</strong>的原因。</p>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
另外,AsyncTask里面的这个Handler是一个静态变量,也就是说它是在类加载的时候创建的;如果在你的APP进程里面,以前从来没有使用过AsyncTask,然后在子线程使用AsyncTask的相关变量,那么导致静态Handler初始化,如果在API 16以下,那么会出现上面同样的问题;这就是<strong>AsyncTask必须在主线程初始化</strong>&nbsp;的原因。</p>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
事实上,在Android 4.1(API 16)以后,在APP主线程ActivityThread的main函数里面,直接调用了<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">AscynTask.init</code>函数确保这个类是在主线程初始化的;另外,init这个函数里面获取了<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">InternalHandler</code>的Looper,由于是在主线程执行的,因此,AsyncTask的Handler用的也是主线程的Looper。这个问题从而得到彻底的解决。</p>
<h2 id="AsyncTask是并行执行的吗?" style="margin:20px 0px 10px; padding:10px 0px 0px; line-height:1; font-family:Cambria,Georgia,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei','Times New Roman',serif; font-size:22px; color:rgb(85,85,85); text-align:justify"><a name="t3" target="_blank"></a>
AsyncTask是并行执行的吗?</h2>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
现在知道AsyncTask内部有一个线程池,那么派发给AsyncTask的任务是并行执行的吗?</p>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
答案是不确定。在Android 1.5刚引入的时候,AsyncTask的<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">execute</code>是串行执行的;到了Android 1.6直到Android 2.3.2,又被修改为并行执行了,这个执行任务的线程池就是<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">THREAD_POOL_EXECUTOR</code>,因此在一个进程内,所有的AsyncTask都是并行执行的;但是在Android
 3.0以后,如果你使用<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">execute</code>函数直接执行AsyncTask,那么<strong>这些任务是串行执行的</strong>;(你说蛋疼不)源代码如下:</p>
<table style="border-collapse:collapse; border-spacing:0px; width:auto; border:none; font-size:14px; margin:0px">
<tbody>
<tr style="background-color:rgb(249,249,249)">
<td class="gutter" style="padding:0px; vertical-align:middle; border:none">
<pre style="overflow:auto; font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:13px; margin-top:0px; margin-bottom:0px; padding:0px 20px 0px 0px; color:rgb(102,102,102); line-height:1.6; border:none; text-align:right; background:rgb(247,247,247)"><span class="line" style="height:20px">1</span>
<span class="line" style="height:20px">2</span>
<span class="line" style="height:20px">3</span>
</pre>
</td>
<td class="code" style="padding:0px; vertical-align:middle; border:none">
<pre style="overflow:auto; font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:13px; margin-top:0px; margin-bottom:0px; padding:0px; color:rgb(77,77,76); line-height:1.6; border:none; background:rgb(247,247,247)"><span class="line" style="height:20px"><span class="keyword" style="color:rgb(137,89,168)">public</span> <span class="keyword" style="color:rgb(137,89,168)">final</span> AsyncTask&lt;Params, Progress, Result&gt; execute(Params... params) {</span>
<span class="line" style="height:20px">    <span class="keyword" style="color:rgb(137,89,168)">return</span> executeOnExecutor(sDefaultExecutor, params);</span>
<span class="line" style="height:20px">}</span>
</pre>
</td>
</tr>
</tbody>
</table>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
这个<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">sDefaultExecutor</code>就是用来执行任务的线程池,那么它的值是什么呢?继续看代码:</p>
<table style="border-collapse:collapse; border-spacing:0px; width:auto; border:none; font-size:14px; margin:0px">
<tbody>
<tr style="background-color:rgb(249,249,249)">
<td class="gutter" style="padding:0px; vertical-align:middle; border:none">
<pre style="overflow:auto; font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:13px; margin-top:0px; margin-bottom:0px; padding:0px 20px 0px 0px; color:rgb(102,102,102); line-height:1.6; border:none; text-align:right; background:rgb(247,247,247)"><span class="line" style="height:20px">1</span>
</pre>
</td>
<td class="code" style="padding:0px; vertical-align:middle; border:none">
<pre style="overflow:auto; font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:13px; margin-top:0px; margin-bottom:0px; padding:0px; color:rgb(77,77,76); line-height:1.6; border:none; background:rgb(247,247,247)"><span class="line" style="height:20px"><span class="keyword" style="color:rgb(137,89,168)">private</span> <span class="keyword" style="color:rgb(137,89,168)">static</span> <span class="keyword" style="color:rgb(137,89,168)">volatile</span> Executor sDefaultExecutor = SERIAL_EXECUTOR;</span>
</pre>
</td>
</tr>
</tbody>
</table>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
因此结论就来了:<strong>Android 3.0以上,AsyncTask默认并不是并行执行的</strong>;</p>
<h3 id="为什么默认不并行执行?" style="margin:20px 0px 10px; padding:10px 0px 0px; line-height:1; font-family:Cambria,Georgia,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei','Times New Roman',serif; font-size:20px; color:rgb(85,85,85); text-align:justify"><a name="t4" target="_blank"></a>
为什么默认不并行执行?</h3>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
也许你不理解,为什么AsyncTask默认把它设计为串行执行的呢?</p>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
由于一个进程内所有的AsyncTask都是使用的同一个线程池执行任务;如果同时有几个AsyncTask一起并行执行的话,恰好AysncTask的使用者在<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">doInbackgroud</code>里面访问了相同的资源,但是自己没有处理同步问题;那么就有可能导致灾难性的后果!</p>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
由于开发者通常不会意识到需要对他们创建的所有的AsyncTask对象里面的<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">doInbackgroud</code>做同步处理,因此,API的设计者为了避免这种无意中访问并发资源的问题,干脆把这个API设置为默认所有串行执行的了。如果你明确知道自己需要并行处理任务,那么你需要使用<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">executeOnExecutor(Executor
 exec,Params... params)</code>这个函数来指定你用来执行任务的线程池,同时为自己的行为负责。(处理同步问题)</p>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
实际上《Effective Java》里面有一条原则说的就是这种情况:不要在同步块里面调用不可信的外来函数。这里明显违背了这个原则:AsyncTask这个类并不知道使用者会在<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">doInBackgroud</code>这个函数里面做什么,但是对它的行为做了某种假设。</p>
<h3 id="如何让AsyncTask并行执行?" style="margin:20px 0px 10px; padding:10px 0px 0px; line-height:1; font-family:Cambria,Georgia,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei','Times New Roman',serif; font-size:20px; color:rgb(85,85,85); text-align:justify"><a name="t5" target="_blank"></a>
如何让AsyncTask并行执行?</h3>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
正如上面所说,如果你确定自己做好了同步处理,或者你没有在不同的AsyncTask里面访问共享资源,需要AsyncTask能够并行处理任务的话,你可以用带有两个参数的<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">executeOnExecutor</code>执行任务:</p>
<table style="border-collapse:collapse; border-spacing:0px; width:auto; border:none; font-size:14px; margin:0px">
<tbody>
<tr style="background-color:rgb(249,249,249)">
<td class="gutter" style="padding:0px; vertical-align:middle; border:none">
<pre style="overflow:auto; font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:13px; margin-top:0px; margin-bottom:0px; padding:0px 20px 0px 0px; color:rgb(102,102,102); line-height:1.6; border:none; text-align:right; background:rgb(247,247,247)"><span class="line" style="height:20px">1</span>
<span class="line" style="height:20px">2</span>
<span class="line" style="height:20px">3</span>
<span class="line" style="height:20px">4</span>
<span class="line" style="height:20px">5</span>
<span class="line" style="height:20px">6</span>
<span class="line" style="height:20px">7</span>
</pre>
</td>
<td class="code" style="padding:0px; vertical-align:middle; border:none">
<pre style="overflow:auto; font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:13px; margin-top:0px; margin-bottom:0px; padding:0px; color:rgb(77,77,76); line-height:1.6; border:none; background:rgb(247,247,247)"><span class="line" style="height:20px"><span class="keyword" style="color:rgb(137,89,168)">new</span> AsyncTask&lt;Void, Void, Vo</span>
<span class="line" style="height:20px">    <span class="annotation">@Override</span></span>
<span class="line" style="height:20px">    <span class="function" style="color:rgb(66,113,174)"><span class="keyword" style="color:rgb(137,89,168)">protected</span> Void <span class="title" style="color:rgb(62,153,159)">doInBackground</span><span class="params" style="color:rgb(245,135,31)">(Void... params)</span> </span>{</span>
<span class="line" style="height:20px">        <span class="comment" style="color:rgb(142,144,140)">// do something</span></span>
<span class="line" style="height:20px">        <span class="keyword" style="color:rgb(137,89,168)">return</span> <span class="keyword" style="color:rgb(137,89,168)">null</span>;</span>
<span class="line" style="height:20px">    }</span>
<span class="line" style="height:20px">}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);</span>
</pre>
</td>
</tr>
</tbody>
</table>
<h2 id="更好的AsyncTask" style="margin:20px 0px 10px; padding:10px 0px 0px; line-height:1; font-family:Cambria,Georgia,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei','Times New Roman',serif; font-size:22px; color:rgb(85,85,85); text-align:justify"><a name="t6" target="_blank"></a>
更好的AsyncTask</h2>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
从上面的分析得知,AsyncTask有如下问题:</p>
<ol style="color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
<li>默认的AsyncTask如果处理的任务过多,会导致程序直接崩溃;</li><li>AsyncTask类必须在主线程初始化,必须在主线程创建,不然在API 16以下很大概率崩溃。</li><li>如果你曾经使用过AsyncTask,以后不用了;在Android 4.4以下,进程内也默认有5个AsyncTask线程;在Android 4.4以上,默认有<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">CPU + 1</code>个线程。</li><li>Android 3.0以上的AsyncTask默认是串行执行任务的;如果要并行执行需要调用低版本没有的API,处理麻烦。</li></ol>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
因此我们对系统的AsyncTask做了一些修改,在不同Android版本提供一致的行为,并且提高了使用此类的安全性,主要改动如下:</p>
<ol style="color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
<li>添加对于任务过多导致崩溃的异常保护;在这里进行必要的数据统计上报工作;如果出现这个问题,说明AsyncTask不适合这种场景了,需要考虑重构;</li><li>移植API 22对于Handler的处理;这样就算在线程创建异步任务,也不会有任何问题;</li><li>提供串行执行和并行执行的<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">execute</code>方法;默认串行执行,如果明确知道自己在干什么,可以使用<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">executeParallel</code>并行执行。</li><li>在<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">doInbackgroud</code>里面频繁崩溃的地方加上<code style="font-family:'PT Mono',Consolas,Monaco,Menlo,monospace; font-size:1em; padding:0px 0.3em; background:rgb(238,238,238)">try..catch</code>;自己处理数据上报工作。</li></ol>
<p style="margin-top:0px; margin-bottom:25px; color:rgb(85,85,85); font-family:Lato,'Microsoft Jhenghei','Hiragino Sans GB','Microsoft YaHei',sans-serif; font-size:16px; line-height:27.2px; text-align:justify">
完整代码见gist,<a target="_blank" href="https://gist.github.com/tiann/8860bcc514f067ab4291" rel="external" style="color:rgb(34,34,34); text-decoration:none; border-bottom-width:1px; border-bottom-style:solid; border-bottom-color:rgb(204,204,204); word-wrap:break-word; background-color:transparent">BetterAsyncTask</a></p>
   
</div>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值