深入理解AsyncTask的工作原理

一、为什么需要工作者线程

我们知道,Android应用的主线程(UI 线程)肩负着绘制用户界面和及时响应用户操作的重任,为了避免“用户点击按钮后没反应”这样的糟糕用户体验,我们就要确保主线程时刻保持着较高的响应性。为了做到这一点,我们就要把耗时的任务移出主线程,那么耗时的任务交给谁来完成呢?答案就是工作者线程。Android开发中我们通常让主线程负责前台用户界面的绘制以及响应用户的操作,让工作者线程在后台执行一些比较耗时的任务。Android中的工作者线程主要有AsyncTask、IntentService、HandlerThread,它们本质上都是对线程或线程池的封装。关于线程和线程池相关知识的介绍,请参考这两篇博文:Java核心技术点之多线程    深入理解Java之线程池

总的来说,我们使用工作者线程是因为主线程已经有很多活要干了,累活就得交给别人干。AsyncTask是我们日常中广泛使用的一种工作者线程,它的方便之处在于可以在后台任务执行完毕时根据返回结果相应的更新UI。下面我们来研究一下它的工作原理。

二、探索AsyncTask的工作原理

1. AsyncTask的使用简介

AsyncTask是对Handler与线程池的封装。使用它的方便之处在于能够更新用户界面,当然这里更新用户界面的操作还是在主线程中完成的,但是由于AsyncTask内部包含一个Handler,所以可以发送消息给主线程让它更新UI。另外,AsyncTask内还包含了一个线程池。使用线程池的主要原因是避免不必要的创建及销毁线程的开销。设想下面这样一个场景:有100个只需要0.1ms就能执行完毕的任务,如果创建100个线程来执行这些任务,执行完任务的线程就进行销毁。那么创建与销毁进程的开销就很可能成为了影响性能的瓶颈。通过使用线程池,我们可以实现维护固定数量的线程,不管有多少任务,我们都始终让线程池中的线程轮番上阵,这样就避免了不必要的开销。‘

    在这里简单介绍下AsyncTask的使用方法,为后文对它的工作原理的研究做铺垫,关于AsyncTask的详细介绍大家可以参考官方文档或是相关博文。

AsyncTask是一个抽象类,我们在使用时需要定义一个它的派生类并重写相关方法。AsyncTask类的声明如下:

public abstract class AsyncTask<Params, Progress, Result> 

我们可以看到,AsyncTask是一个泛型类,它的三个类型参数的含义如下:

  • Params:doInBackground方法的参数类型;
  • Progress:AsyncTask所执行的后台任务的进度类型;
  • Result:后台任务的返回结果类型。

我们再来看一下AsyncTask类主要为我们提供了哪些方法:

onPreExecute() //此方法会在后台任务执行前被调用,用于进行一些准备工作
doInBackground(Params... params) //此方法中定义要执行的后台任务,在这个方法中可以调用publishProgress来更新任务进度(publishProgress内部会调用onProgressUpdate方法)
onProgressUpdate(Progress... vaues) //由publishProgress内部调用,表示任务进度更新
onPostExecute(Result result) //后台任务执行完毕后,此方法会被调用,参数即为后台任务的返回结果
onCancelled() //此方法会在后台任务被取消时被调用

以上方法中,除了doInBackground方法由AsyncTask内部线程池执行外,其余方法均在主线程中执行。

2. AsyncTask的局限性

AsyncTask的有点在于执行完后台任务后可以很方便的更新UI,然而使用它存在着诸多的限制。先抛开内存泄漏问题,使用AsyncTask主要存在以下局限性:

  • 在Android 4.1版本之前,AsyncTask类必须在主线程中加载,这意味着对AsyncTask类的第一次访问必须发生在主线程中;在Android 4.1以及以上版本则不存在这一限制,因为ActivityThread(代表了主线程)的main方法中会自动加载AsyncTask
  • AsyncTask对象必须在主线程中创建
  • AsyncTask对象的execute方法必须在主线程中调用
  • 一个AsyncTask对象只能调用一次execute方法

接下来,我们从源码的角度去探究一下AsyncTask的工作原理,并尝试着搞清楚为什么会存在以上局限性。

3. AsyncTask的工作原理

首先,让我们来看一下AsyncTask类的构造器都做了些什么:

 1 public AsyncTask() {
 2         mWorker = new WorkerRunnable<Params, Result>() {
 3             public Result call() throws Exception {
 4                 mTaskInvoked.set(true);
 5
 6                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
 7                 //noinspection unchecked
 8                 Result result = doInBackground(mParams);
 9                 Binder.flushPendingCommands();
10                 return postResult(result);
11             }
12         };
13
14         mFuture = new FutureTask<Result>(mWorker) {
15             @Override
16             protected void done() {
17                 try {
18                     postResultIfNotInvoked(get());
19                 } catch (InterruptedException e) {
20                     android.util.Log.w(LOG_TAG, e);
21                 } catch (ExecutionException e) {
22                     throw new RuntimeException("An error occurred while executing doInBackground()",
23                             e.getCause());
24                 } catch (CancellationException e) {
25                     postResultIfNotInvoked(null);
26                 }
27             }
28         };
29     }

在第2行到第12行,初始化了mWorker,它是一个派生自WorkRunnable类的对象。WorkRunnable是一个抽象类,它实现了Callable<Result>接口。我们再来看一下第4行开始的call方法的定义,首先将mTaskInvoked设为true表示当前任务已被调用过,然后在第6行设置线程的优先级。在第8行我们可以看到,调用了AsyncTask对象的doInBackground方法开始执行我们所定义的后台任务,并获取返回结果存入result中。最后将任务返回结果传递给postResult方法。关于postResult方法我们会在下文进行分析。

接下来让我们看看对mFuture的初始化。我们可以看到在构造mFuture对象时我们传入了mWorker作为参数。也就是说,mFuture是一个封装了我们的后台任务的FutureTask对象。

我们再来看一下execute方法的源码:

 public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
}

我们可以看到它接收的参数是Params类型的参数,这个参数会一路传递到doInBackground方法中。execute方法仅仅是调用了executeOnExecutor方法,并将executeOnExecutor方法的返回值作为自己的返回值。我们注意到,传入了sDefaultExecutor作为executeOnExecutor方法的参数,那么sDefaultExecutor是什么呢?简单的说,它是AsyncTask的默认执行器。AsyncTask可以以串行(一个接一个的执行)或并行(一并执行)两种方式来执行后台任务,在Android3.0及以后的版本中,默认的执行方式是串行。这个sDefaultExecutor就代表了默认的串行任务执行器。也就是说我们平常在AsyncTask对象上调用execute方法,使用的是串行方式来执行后台任务。

我们再来看一下executeOnExecutor方法都做了些什么:

 1 public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
 2             Params... params) {
 3         if (mStatus != Status.PENDING) {
 4             switch (mStatus) {
 5                 case RUNNING:
 6                     throw new IllegalStateException("Cannot execute task:"
 7                             + " the task is already running.");
 8                 case FINISHED:
 9                     throw new IllegalStateException("Cannot execute task:"
10                             + " the task has already been executed "
11                             + "(a task can be executed only once)");
12             }
13         }
14
15         mStatus = Status.RUNNING;
16
17         onPreExecute();
18
19         mWorker.mParams = params;
20         exec.execute(mFuture);
21
22         return this;
23     }

从以上代码的第4行到第12行我们可以知道,当AsyncTask对象的当前状态为RUNNING或FINISHED时,调用execute方法会抛出异常,这意味着不能对正在执行任务的AsyncTask对象或是已经执行完任务的AsyncTask对象调用execute方法,这也就解释了我们上面提到的局限中的最后一条。

接着我们看到第17行存在一个对onPreExecute方法的调用,这表示了在执行后台任务前确实会调用onPreExecute方法。

在第19行,把我们传入的execute方法的params参数赋值给了mWorker的mParams成员变量;而后在第20行调用了exec的execute方法,并传入了mFuture作为参数。exec就是我们传进来的sDefaultExecutor。那么接下来我们看看sDefaultExecutor就是是什么。在AsyncTask类的源码中,我们可以看到这句:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

sDefaultExecutor被赋值为SERIAL_EXECUTOR,那么我们来看一下SERIAL_EXECUTOR:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

现在,我们知道了实际上sDefaultExecutor是一个SerialExecutor对象,我们来看一下SerialExecutor类的源码:

 1 private static class SerialExecutor implements Executor {
 2         final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
 3         Runnable mActive;
 4
 5         public synchronized void execute(final Runnable r) {
 6             mTasks.offer(new Runnable() {
 7                 public void run() {
 8                     try {
 9                         r.run();
10                     } finally {
11                         scheduleNext();
12                     }
13                 }
14             });
15             if (mActive == null) {
16                 scheduleNext();
17             }
18         }
19
20         protected synchronized void scheduleNext() {
21             if ((mActive = mTasks.poll()) != null) {
22                 THREAD_POOL_EXECUTOR.execute(mActive);
23             }
24         }
25     }

我们来看一下execute方法的实现。mTasks代表了SerialExecutor这个串行线程池的任务缓存队列,在第6行,我们用offer方法向任务缓存队列中添加一个任务,任务的内容如第7行到第13行的run方法定义所示。我们可以看到,第9行调用了mFuture的run方法,而mFuture的run方法内部会调用mWorker的call方法。正如我们看到的那样,mWorker的call方法内调用了doInBackground方法。也就是说,最终在线程池中执行的内容是mWorker的call方法。

第15行判断mActive(当前正在执行的AsyncTask对象)是否为null,若为null,就调用scheduleNext方法。如第20行到24行所示,在scheduleNext方法中,若缓存队列非空,则调用THREAD_POOL_EXECUTOR.execute方法执行从缓存队列中取出的任务。

通过以上的分析,我们可以知道SerialExecutor所完成的工作主要是把任务加到任务缓存队列中,而真正执行任务的是THREAD_POOL_EXECUTOR。我们来看下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的源码,我们可以获取它的各项参数如下:

 1 private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
 2 private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
 3 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
 4 private static final int KEEP_ALIVE = 1;
 5
 6 private static final ThreadFactory sThreadFactory = new ThreadFactory() {
 7     private final AtomicInteger mCount = new AtomicInteger(1);
 8
 9     public Thread newThread(Runnable r) {
10         return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
11     }
12 };
13
14 private static final BlockingQueue<Runnable> sPoolWorkQueue =
15             new LinkedBlockingQueue<Runnable>(128);

由以上代码我们可以知道:

  • corePoolSize为CPU数加一;
  • maximumPoolSize为CPU数的二倍加一;
  • 存活时间为1秒;
  • 任务缓存队列为LinkedBlockingQueue。

现在,我们回过头来看一看之前提到的postResult方法的源码:

private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
}

在以上源码中,先调用了getHandler方法获取AsyncTask对象内部包含的sHandler,然后通过它发送了一个MESSAGE_POST_RESULT消息。我们来看看sHandler的相关代码:

 1 private static final InternalHandler sHandler = new InternalHandler();
 2
 3 private static class InternalHandler extends Handler {
 4         public InternalHandler() {
 5             super(Looper.getMainLooper());
 6         }
 7
 8         @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
 9         @Override
10         public void handleMessage(Message msg) {
11             AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
12             switch (msg.what) {
13                 case MESSAGE_POST_RESULT:
14                     // There is only one result
15                     result.mTask.finish(result.mData[0]);
16                     break;
17                 case MESSAGE_POST_PROGRESS:
18                     result.mTask.onProgressUpdate(result.mData);
19                     break;
20             }
21         }
22 } 

从以上代码中我们可以看到,sHandler是一个静态的Handler对象。我们知道创建Handler对象时需要当前线程的Looper,所以我们为了以后能够通过sHandler将执行环境从后台线程切换到主线程,必须在主线程中创建sHandler。这也就解释了为什么必须在主线程中加载AsyncTask类,是为了完成sHandler这个静态成员的初始化工作。

在以上代码第10行开始的handleMessage方法中,我们可以看到,当sHandler收到MESSAGE_POST_RESULT方法后,会调用finish方法,finish方法的源码如下:

1 private void finish(Result result) {
2         if (isCancelled()) {
3             onCancelled(result);
4         } else {
5             onPostExecute(result);
6         }
7         mStatus = Status.FINISHED;
8 }

在第2行,会通过调用isCancelled方法判断AsyncTask任务是否被取消,若取消了则调用onCancelled方法,否则调用onPostExecute方法;在第7行,把mStatus设为FINISHED,表示当前AsyncTask对象已经执行完毕。

经过了以上的分析,我们大概了解了AsyncTask的内部运行逻辑,知道了它默认使用串行方式执行任务。那么如何让它以并行的方式执行任务呢? 阅读了以上的代码后,我们不难得到结论,只需调用executeOnExecutor方法,并传入THREAD_POOL_EXECUTOR作为其线程池即可。

三、参考资料

1. Android SDK Sources

2. 《Android开发艺术探索》

时间: 2024-11-05 13:34:37

深入理解AsyncTask的工作原理的相关文章

J2EE总结(三)——深入理解JSP开发工作原理

一.JSP及其工作原理 1.JSP(JavaServer page)Java服务器页面,从名字上看,它类似于ASP,并且是在服务端编写的一种技术. 2.用于开发动态web页面的技术 为什么它可以用来开发动态web页面呢?这与它在编写时用到的技术有关.它采用HTML来定义页面的结构,但是在jsp中允许编写Java代码,并且允许开发人员在页面中使用request,response,out等对象实现与浏览器的交互,所以jsp也是一种动态web页面开发技术. 举个例子: 输出当前的时间,在html中嵌套

[diango]理解django视图工作原理

前言:正确理解django视图view,模型model,模板三种概念才能快速使用django制作网页 本文主要讲解自己在学习django后对视图view的理解 正文:一个django视图需要拥有视图函数,如果想要视图函数工作则需要把 url 映射l到视图. 视图函数放在叫做views.py的文件中,这个文件位置位于django工程目录下 下面举例说明视图工作原理: 下面是一个返回当前日期和时间作为HTML文档的视图: from django.http import HttpResponse im

160701、理解 Promise 的工作原理

Javascript 采用回调函数(callback)来处理异步编程.从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的回调金字塔(Pyramid of Doom),绝对是一种糟糕的编程体验.于是便有了 CommonJS 的 Promises/A 规范,用于解决回调金字塔问题.本文先介绍 Promises 相关规范,然后再通过解读一个迷你的 Promises 以加深理解. 什么是 Promise   一个 Promise 对象代表一个目前还不可用,但是在未

用一个实际例子理解Docker volume工作原理

要了解Docker Volume,首先我们需要理解Docker文件系统的工作原理.Docker镜像是由多个文件系统的只读层叠加而成.当一个容器通过命令docker run启动时,Docker会加载只读镜像层并在镜像栈顶部添加一个读写层.如果运行中的容器修改了现有的一个已经存在的文件,那该文件将会从读写层下面的只读层复制到读写层,但是该文件的只读版本依然存在,只不过已经被读写层中该文件的副本所隐藏. 当删除Docker容器,并通过该镜像重新启动时,之前在读写层的更改将会丢失.在Docker中,只读

Ajax学习--理解 Ajax 及其工作原理

Ajax 是 Asynchronous JavaScript and XML(以及 DHTML 等)的缩写. 下面是 Ajax 应用程序所用到的基本技术:• HTML 用于建立 Web 表单并确定应用程序其他部分使用的字段. • JavaScript 代码是运行 Ajax 应用程序的核心代码,帮助改进与服务器应用程序的通信. • DHTML 或 Dynamic HTML,用于动态更新表单.我们将使用 div.span 和其他动态 HTML 元素来标记 HTML. • 文档对象模型 DOM 用于(

理解 HTTPS 的工作原理

读完本文,你能明白 什么是HTTPS,TLS(SSL),TLS和HTTPS是什么关系? 什么是证书和数字签名,它们是如何传递信任的? HTTPS有什么样的功能,它是如何实现这样的功能的? 简介 HTTPS,也称作HTTP over TLS.TLS的前身是SSL,TLS 1.0通常被标示为SSL 3.1,TLS 1.1为SSL 3.2,TLS 1.2为SSL 3.3.本文着重描述TLS协议的1.2版本. 下图描述了在TCP/IP协议栈中TLS(各子协议)和HTTP的关系 Credit: Kaush

理解 Ajax 及其工作原理,构建网站的一种有效方法

?XMLHttpRequest 对象 要了解的一个对象可能对您来说也是最陌生的,即 XMLHttpRequest.这是一个 JavaScript 对象,创建该对象很简单,如清单 1 所示. 清单 1. 创建新的 XMLHttpRequest 对象 <script language="javascript" type="text/javascript"> var xmlHttp = new XMLHttpRequest(); </script>

Android AsyncTask工作原理

AsyncTask能够适当简单的使用在UI线程,在没有任务线程和handler的情况下,这个类也允许执行后台操作并将结果显示在UI线程上. AsyncTask的引入,我们在执行完耗时的后台任务后可以很方便的更新UI元素.相信大多数同学对AsyncTask的用法都已经很熟悉,那这里不在叙述他的基本用法了. AsyncTask的实现原理是封装线程池和消息机制,我已经在自己的博客写过了线程池和消息机制的.感兴趣的同学可以去阅读下. 那么直接进入AsyncTask的源码分析,一起从源码的角度彻底理解.

AsyncTask,IntentService工作原理分析&amp;Android线程池

一,android中的主线程和子线程 android中的主线程可以认为是UI线程,在主线程不可以执行耗时的操作,否则就会给人一种卡顿的感觉.而主线程主要用于处理四大组件,以及处理它们和用户的交互.anroid的子线程的主要功能就是处理耗时操作. 要知道"在android3.0之后,要求网络访问必须在子线程执行,否则会抛出NetWorkOnMainThreadException异常." 二,Android中的线程形态 Android中的线程状态,除了传统的Thread,还包含AsyncT