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

Android应用的主线程(UI线程)肩负着绘制用户界面和及时响应用户操作的重任,为了避免”ANR”,就要确保主线程时刻保持较高的响应性.为了做到这一点,我们就要把耗时的任务移出主线程,那么耗时的任务交给谁来完成呢?答案就是工作者线程。Android开发中我们通常让主线程负责前台用户界面的绘制以及响应用户的操作,让工作者线程在后台执行一些比较耗时的任务。Android中的工作者线程主要有AsyncTask、IntentService、HandlerThread,它们本质上都是对线程或线程池的封装。

AsyncTask是我们日常中广泛使用的一种工作者线程,它的方便之处在于可以在后台任务执行完毕时根据返回结果相应的更新UI。下面我们来研究一下它的工作原理。

二、探索AsyncTask的工作原理

1. AsyncTask的使用简介

AsyncTask是对Handler与线程池的封装。使用它的方便之处在于能够更新用户界面,当然这里更新用户界面的操作还是在主线程中完成的,但是由于AsyncTask内部包含一个Handler,所以可以发送消息给主线程让它更新UI。另外,AsyncTask内还包含了一个线程池。使用线程池的主要原因是避免不必要的创建及销毁线程的开销。设想下面这样一个场景:有100个只需要0.001ms就能执行完毕的任务,如果创建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… values) //由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类的构造器都做了些什么:

public AsyncTask() {mWorker = new WorkerRunnable<Params, Result>() {public Result call() throws Exception {mTaskInvoked.set(true);Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);//noinspection uncheckedResult result = doInBackground(mParams);Binder.flushPendingCommands();return postResult(result);}};mFuture = new FutureTask<Result>(mWorker) {@Overrideprotected 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);}}};}

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

接下来让我们看看对mFuture的初始化。我们可以看到mFuture是一个FutureTask的直接子类(匿名内部类)的对象,在FutureTask的构造方法中我们传入了mWorker作为参数。我们使用的是FutureTask的这个构造方法:

public FutureTask(Callable<V> callable) {if (callable == null)throw new NullPointerException();this.callable = callable;this.state = NEW;       // ensure visibility of callable
}

也就是说,mFuture是一个封装了我们的后台任务的FutureTask对象,FutureTask类实现了FutureRunnable接口,通过这个接口可以方便的取消后台任务以及获取后台任务的执行结果,具体介绍请看这里:Java并发编程:Callable、Future和FutureTask。

从上面的分析我们知道了,当mWorker中定义的call方法被执行时,doInBackground就会开始执行,我们定义的后台任务也就真正开始了。那么这个call方法什么时候会被调用呢?我们可以看到经过层层封装,实际上是mFuture对象封装了call方法,当mFuture对象被提交到AsyncTask包含的线程池执行时,call方法就会被调用,我们定义的后台任务也就开始执行了。下面我们来看一下mFuture是什么时候被提交到线程池执行的。

首先来看一下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方法都做了些什么:

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

从以上代码的第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类的源码:

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

我们来看一下execute方法的实现。mTasks代表了SerialExecutor这个串行线程池的任务缓存队列,在第6行,我们用offer方法向任务缓存队列中添加一个任务,任务的内容如第7行到第13行的run方法定义所示。我们可以看到,run方法中:第9行调用了mFuture(第5行的参数r就是我们传入的mFuture)的run方法,而mFuture的run方法内部会调用mWorker的call方法,然后就会调用doInBackground方法,我们的后台任务也就开始执行了。那么我们提交到任务缓存队列中的任务什么时候会被执行呢?我们接着往下看。

首先我们看到第三行定义了一个Runnable变量mActive,它代表了当前正在执行的AsyncTask对象。第15行判断mActive是否为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的源码,我们可以获取它的各项参数如下:

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

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

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

现在,我们已经了解到了从我们调用AsyncTask对象的execute方法开始知道后台任务执行完都发生了什么。现在让我们回过头来看一看之前提到的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的相关代码:

private static final InternalHandler sHandler = new InternalHandler();private static class InternalHandler extends Handler {public InternalHandler() {super(Looper.getMainLooper());}@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})@Overridepublic void handleMessage(Message msg) {AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;switch (msg.what) {case MESSAGE_POST_RESULT:// There is only one resultresult.mTask.finish(result.mData[0]);break;case MESSAGE_POST_PROGRESS:result.mTask.onProgressUpdate(result.mData);break;}}}

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

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

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

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

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

如果还有什么问题,可以参考下资料。

这是我的公众号,有什么问题,我们进行交流!

AsyncTask原理及不足相关推荐

  1. AsyncTask原理

    为什么要用AsyncTask 我们知道,Android应用的主线程(UI 线程,是线程不安全的,负责前台用户界面的绘制以及响应用户的操作)肩负着绘制用户界面和及时响应用户操作的重任,为了避免" ...

  2. 【Android】AsyncTask原理应用及源码关键部分解析

    为了更加方便我们在子线程中更新UI元素,Android从1.5版本就引入了一个AsyncTask类,使用它就可以非常灵活方便地从子线程切换到UI线程.AsyncTask是android提供的轻量级的异 ...

  3. android asynctask,Android AsyncTask原理解析

    想要启动一个AsyncTask,首先需要创建一个AsyncTask对象然后调用execute方法.例如: new DownloadFilesTask().execute(); DownloadFile ...

  4. 2019 Android 高级面试题总结 从java语言到AIDL使用与原理

    说下你所知道的设计模式与使用场景 a.建造者模式: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 使用场景比如最常见的AlertDialog,拿我们开发过程中举例,比如C ...

  5. Android线程,线程池使用及原理博文参考

    2019独角兽企业重金招聘Python工程师标准>>> 还是先回顾下Handler消息机制的原理图: 同样的还是先看看一篇对<Android开发艺术探索>的总结,对线程和 ...

  6. Android 中的线程有哪些,原理与各自特点

    AsyncTask.HandlerThread.IntentService AsyncTask原理 内部是Handler和两个线程池实现的,Handler用于将线程切换到主线程,两个线程池一个用于任务 ...

  7. 2019-Android-高级面试题总结-从java语言到AIDL使用与原理

    匿名内部类同样会持有外部类的引用,如果在线程中执行耗时操作就有可能发生内存泄漏,导致外部类无法被回收,直到耗时任务结束,解决办法是在页面退出时结束线程中的任务 3.Handler内存泄漏 Handle ...

  8. 【大牛系列教学】Android热修复原理,满满干货指导

    优秀的战士需要出色的剑才能战斗.同样,在现代IT中,每个编码人员都需要最好的Android开发人员工具来提高他们的技能和效率.在Android应用程序开发这个残酷的竞争行业中,只有优秀的开发人员才能生 ...

  9. 2016BAT+华为+滴滴+搜狗Android开发岗面试问题整理

    实习面了阿里和腾讯,校招面了腾讯,百度,华为,搜狗和滴滴,总结一下遇到的面试知识点.知识点是散的而且也比较容易掌握,难点是这些知识点的应用中并且深刻理解.实习面试的时候阿里通过,腾讯一面跪(内推和实习 ...

最新文章

  1. 无人机数车--Drone-based Object Counting by Spatially Regularized Regional Proposal Network
  2. Java集合类解析 ***
  3. 深度学习(二十九)——Normalization进阶, CTC
  4. P6348 [PA2011]Journeys 线段树优化建图 区间连区间
  5. 3-pycharm找不到库的解决办法
  6. php 如何宏定义,php – 在html中实现宏定义的方法
  7. 静态路由及默认路由实验配置
  8. Vue笔记:使用 axios 中 this 指向问题
  9. Hibernate缓存的evict、clear和flush方法
  10. CISP考试的全过程
  11. mysql error1682_mysql5.7报错 1546、1577和1682问题分析
  12. mysql如何高效存储IPv4、IPv6地址
  13. html里如何将数字转换为条形码,excel中如何把数字变成条形码?
  14. 使用jQuery与后端进行数据传输代码示例
  15. 17 追悔:回到过去,你也不能改变命运
  16. Redis性能优化方案总结
  17. 网吧 开始-》运行 被禁用之破解方法
  18. HDFS 文件权限验证
  19. C语言windows.h库的常用函数(三)
  20. 企业避税数据计算(含计算该过程以及STATA源代码)

热门文章

  1. 游戏服务器架构演进(完整版)
  2. android如何设置软件的版本,android如何实现对软件版本的配置
  3. html中去除浮漂有什么作用,鱼漂吃铅量大、和吃铅量小都有啥优点?
  4. AI行业态势感知(第六期)
  5. 求助打开网站显示welcome to nginx!
  6. 基站网口损坏检查方法
  7. 24HTML5期末大作业:XXX 网站设计——指环王:护戒使者(13页) HTML+CSS+JavaScript HTML+CSS+JS网页设计期末课程大作业 web前端开发技术 web课程 网页规
  8. 实时票房大盘 API数据接口
  9. 大数据技术人年度盛事! BDTC 2016将于12月8-10日在京举行
  10. Unraid 安装百度网盘