简介

AsyncTask 在Android API 3引入,是为了使UI线程能被正确和容易地使用。它允许你在后台进行一些操作,并且把结果带到UI线程中,而不用自己去操纵Thread或Handler。它被设计为一个围绕着Thread和Handler的辅助类,而不是一种通用的线程框架。在API文档中,建议你把它用于一些耗时比较短(大部分情况下只有几秒)的操作,如果你需要让线程长期运行,则强烈推荐使用java.util.concurrent里的API来处理。

如上所述,一个异步任务是在后台中进行计算,并把结果带到UI线程中。在这里,一个异步任务定义了三个泛型类型(ParamsProgressResult),以及四个步骤(onPreExecutedoInBackgroundonProgressUpdateonPostExecute)。

用法

在官方文档中,以下载文件为例,提供了示例代码,先定义一个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");
}
}

然后通过调用它的实例的execute(Params…)方法来执行。

 new DownloadFilesTask().execute(url1, url2, url3);

源码解析

下面来看一下这个类的代码,本文以android-23版本的代码为例。

成员变量

首先是这个类的成员变量。

    private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture; private volatile Status mStatus = Status.PENDING;//表示线程的执行状态 private final AtomicBoolean mCancelled = new AtomicBoolean();//表示是否被取消
private final AtomicBoolean mTaskInvoked = new AtomicBoolean();//表示是否被调用

其中WorkerRunnable是对Callable的封装,包装了一个异步任务的参数。Status是定义的表示异步任务状态的枚举类型,共有三个状态,分别是PENDING(待绪),RUNNING(正在运行),FINISHED(已经完成)。

成员方法

再来看API中提供的所有成员方法:

我们从上面的示例代码中一个异步任务的执行开始,逐步跟踪它的执行过程。

    @MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
} @MainThread
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;
}

从上面的代码可以看出,一个异步任务不能被重复执行,否则将会抛异常。当执行它的时候,状态会被设为Status.RUNNING,同时会调用onPreExecute()方法,然后把传进来的参数赋值到所封装的worker中,再调用一个Executor的 execute方法。

onPreExecute是前面所述的一个异步任务的四个步骤之一,它是用在UI线程中的。所以我们要在UI线程中调用execute方法,才能使其在UI线程中被调用。

上面所用的Executor为sDefaultExecutor,我们来看一下它的定义:

    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
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);
}
}
}

可以看到,默认的Executor是串行处理后台任务的。然而,需要注意的是,AsyncTask在Android API 11之前曾经被设计为并行的,所以如果你在API 11之后需要并行地执行任务,可以直接调用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params)。对此,谷歌在support库提供了封装这个操作的兼容类AsyncTaskCompat,因此你也可以直接调用AsyncTaskCompat.executeParallel(task, params)来使它能够被并行执行。

那么,一个异步任务是如何完成各个步骤的依次调用的?我们再回到它的构造方法来:

    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);
}
}
};
}

mFuture被Executor执行时,会执行其mWorker。它在调用的时候,先把mTaskInvoked设置为true表示已调用,设置线程优先级,然后才执行doInBackground()方法,并执行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;
}

如果在执行doInBackground过程中发生异常的话,而上面的代码可能就不会被执行,所以通过重写FutureTaskdone方法,在里面调用postResultIfNotInvoked,以确保任务执行完成的步骤被调用。

在前面的代码中,getHandler()返回的是一个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;
}
}
}

也就是消息被绑到主线程的消息队列了。同样,更新进度也是通过这种方法来把消息发送到主线程的消息队列中去执行:

    protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}

而在任务完成的方法中,先判断任务是否被取消,被取消的话则回调被取消的方法,否则回调onPostExecute,最终把任务的状态设为已结束。源码如下:

    private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}

以上就是AsyncTask对后台任务及UI线程之间的交互所做的封装。

AsyncTask还提供了取消任务以及等待任务执行的结果的几个API,它们原是FutureTask所提供的特性,所以也只是通过调用mFuture的对应方法,这里不再赘述。

总结

通过上面的代码可以总结出几条:

  • AsyncTask在不同的版本可能是串行执行的,也可能是并行执行的。
  • 如果要确保任务被并行执行,可以通过support库的AsyncTaskCompat来执行。
  • 一个AsyncTask只能被执行一次,不能被多次执行。
  • 取消一个AsyncTask,可以设置是否允许中断它。
  • 如果取消了一个AsyncTask,则不会再回调onProgressUpdate
  • 当一个任务完成时,如果它已经被取消,则回调onCancelled方法,否则回调onPostExecute方法。

Android源码解析——AsyncTask的更多相关文章

  1. Android源码解析系列

    转载请标明出处:一片枫叶的专栏 知乎上看了一篇非常不错的博文:有没有必要阅读Android源码 看完之后痛定思过,平时所学往往是知其然然不知其所以然,所以为了更好的深入Android体系,决定学习an ...

  2. android源码解析(十七)-->Activity布局加载流程

    版权声明:本文为博主原创文章,未经博主允许不得转载. 好吧,终于要开始讲讲Activity的布局加载流程了,大家都知道在Android体系中Activity扮演了一个界面展示的角色,这也是它与andr ...

  3. Android源码解析——LruCache

    我认为在写涉及到数据结构或算法的实现类的源码解析博客时,不应该急于讲它的使用或马上展开对源码的解析,而是要先交待一下这个数据结构或算法的资料,了解它的设计,再从它的设计出发去讲如何实现,最后从实现的角 ...

  4. Android源码解析——Toast

    简介 Toast是一种向用户快速展示少量信息的视图.当它显示时,它会浮在整个应用层的上面,并且不会获取到焦点.它的设计思想是能够向用户展示些信息,但又能尽量不显得唐突.本篇我们来研读一下Toast的源 ...

  5. Android 源码解析之AsyncTask

    AsyncTask相信大家都不陌生,它是为了简化异步请求.更新UI操作而诞生的.使用它不仅可以完成我们的网络耗时操作,而且还可以在完成耗时操作后直接的更新我们所需要的UI组件.这使得它在android ...

  6. Android 源码解析 之 setContentView

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/41894125,本文出自:[张鸿洋的博客] 大家在平时的开发中,对于setCont ...

  7. Android源码解析——Handler、Looper与MessageQueue

    本文的目的是来分析下 Android 系统中以 Handler.Looper.MessageQueue 组成的异步消息处理机制,通过源码来了解整个消息处理流程的走向以及相关三者之间的关系 需要先了解以 ...

  8. 【Android源码解析】View.post()到底干了啥

    emmm,大伙都知道,子线程是不能进行 UI 操作的,或者很多场景下,一些操作需要延迟执行,这些都可以通过 Handler 来解决.但说实话,实在是太懒了,总感觉写 Handler 太麻烦了,一不小心 ...

  9. Android 源码解析:单例模式-通过容器实现单例模式-懒加载方式

    本文分析了 Android 系统服务通过容器实现单例,确保系统服务的全局唯一. 开发过 Android 的用户肯定都用过这句代码,主要作用是把布局文件 XML 加载到系统中,转换为 Android 的 ...

随机推荐

  1. ZOJ-1649 Rescue---BFS+优先队列

    题目链接: https://vjudge.net/problem/ZOJ-1649 题目大意: 天使的朋友要去救天使,a是天使,r 是朋友,x是卫兵.每走一步需要时间1,打倒卫兵需要另外的时间1,问救 ...

  2. MongoDB系列五(地理空间索引与查询).

    一.经纬度表示方式 MongoDB 中对经纬度的存储有着自己的一套规范(主要是为了可以在该字段上建立地理空间索引).包括两种方式,分别是 Legacy Coordinate Pairs (这个词实在不 ...

  3. 对于手机APP偷窥个人隐私,你怎么看

    经过进两周的持续发酵,Facebook5000万用户数据泄露事件,已让其处在舆论的风尖浪口.对于手机APP泄漏用户个人隐私问题,再次受到人们的关注.对于这个问题,你会怎么看? 隐私,即不愿公开的个人信 ...

  4. [LeetCode] Shopping Offers 购物优惠

    In LeetCode Store, there are some kinds of items to sell. Each item has a price. However, there are ...

  5. AXIOS源代码重点难点分析

    摘要 vue使用axios进行http通讯,类似jquery/ajax的作用,类似angular http的作用,axios功能强大,使用方便,是一个优秀的http软件,本文旨在分享axios源代码重 ...

  6. Python网络爬虫笔记(五):下载、分析京东P20销售数据

    (一)  分析网页 下载下面这个链接的销售数据 https://item.jd.com/6733026.html#comment 1.      翻页的时候,谷歌F12的Network页签可以看到下面 ...

  7. LeetCode题目----求中位数---标签:Array

    题目难度---困难 题目要求: 给定两个大小为 m 和 n 的有序数组 nums1 和 nums2 . 请找出这两个有序数组的中位数.要求算法的时间复杂度为 O(log (m+n)) . 思路:第一眼 ...

  8. [SDOI2008]Sue的小球

    题目描述 Sue和Sandy最近迷上了一个电脑游戏,这个游戏的故事发在美丽神秘并且充满刺激的大海上,Sue有一支轻便小巧的小船.然而,Sue的目标并不是当一个海盗,而是要收集空中漂浮的彩蛋,Sue有一 ...

  9. [SHOI2008]小约翰的游戏John

    Description 小约翰经常和他的哥哥玩一个非常有趣的游戏:桌子上有n堆石子,小约翰和他的哥哥轮流取石子,每个人取 的时候,可以随意选择一堆石子,在这堆石子中取走任意多的石子,但不能一粒石子也不 ...

  10. [HNOI2001]软件开发

    题目描述 某软件公司正在规划一项n天的软件开发计划,根据开发计划第i天需要ni个软件开发人员,为了提高软件开发人员的效率,公司给软件人员提供了很多的 服务,其中一项服务就是要为每个开发人员每天提供一块 ...