深入理解AsyncTask的工作原理,最新阿里P7技术体系

我们可以看到,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类的构造器都做了些什么:

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(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接口。我们再来看一下第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 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方法都做了些什么:

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 mTasks = new ArrayDeque();

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方法定义所示。我们可以看到,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是什么:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后笔者收集整理了一份Flutter高级入门进阶资料PDF

以下是资料目录和内容部分截图



里面包括详细的知识点讲解分析,带你一个星期入门Flutter。还有130个进阶学习项目实战视频教程,让你秒变大前端。

里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-vXCihJCM-1712029622096)]

最后笔者收集整理了一份Flutter高级入门进阶资料PDF

以下是资料目录和内容部分截图

[外链图片转存中…(img-mn8v2GGI-1712029622096)]
[外链图片转存中…(img-SMktQlPE-1712029622096)]
里面包括详细的知识点讲解分析,带你一个星期入门Flutter。还有130个进阶学习项目实战视频教程,让你秒变大前端。

[外链图片转存中…(img-CPkRrNMc-1712029622097)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值