目录介绍

  • 01.先看下AsyncTask用法
  • 02.AsyncTask源码深入分析
    • 2.1 构造方法源码分析
    • 2.2 看execute(Params... params)方法
    • 2.3 mWorker和mFuture的创建过程
  • 03.异步机制的实现
  • 04.不同的SDK版本区别
  • 05.AsyncTask的缺陷和问题
    • 5.1 AsyncTask对应线程池
    • 5.2 AsyncTask生命周期问题
    • 5.3 AsyncTask内存泄漏问题
    • 5.4 AsyncTask结果丢失问题
    • 5.5 AsyncTask并行还是串行问题

好消息

  • 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计47篇[近20万字],转载请注明出处,谢谢!
  • 链接地址:https://github.com/yangchong211/YCBlogs
  • 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变!

问题答疑

  • AsyncTask是如何实现异步机制的,底层原理是什么?
  • AsyncTask调用execute方法时,如果不是运行在主线程中会出现什么情况,如何解决?
  • 为什么异步任务对象不能执行多次,即不能创建一个对象执行多次execute方法?
  • doInBackground这个方法可以做什么操作?它是在主线程中还是工作线程中?为什么?
  • AsyncTask任务是否可以被中途取消?为什么?
  • AsyncTask对应线程池是如何操作的?它有什么弊端,为什么现在几乎很少用呢?
  • AsyncTask的执行策略是并行还是串行的?
  • 带着问题去看这篇文章,相信看完之后你对异常AsyncTask有了初步理解……

01.先看下AsyncTask用法

  • 来看一下AsyncTask的基本使用,代码如下所示

    • 定义了自己的MyAsyncTask并继承自AsyncTask;并重写了其中的是哪个回调方法:onPreExecute(),onPostExecute(),doInBackground();
    class MyAsyncTask extends AsyncTask<Integer, Integer, Integer> {
    @Override
    protected void onPreExecute() {
    super.onPreExecute();
    Log.i(TAG, "onPreExecute...(开始执行后台任务之前)");
    } @Override
    protected void onPostExecute(Integer i) {
    super.onPostExecute(i);
    Log.i("TAG", "onPostExecute...(开始执行后台任务之后)");
    } @Override
    protected Integer doInBackground(Integer... params) {
    Log.i(TAG, "doInBackground...(开始执行后台任务)");
    return 0;
    }
    }
  • 开始调用异步任务
    new MyAsyncTask().execute();

02.AsyncTask源码深入分析

2.1 构造方法源码分析

  • 源代码如下所示,主要是看AsyncTask(@Nullable Looper callbackLooper)中的代码

    • 这里面只是初始化了两个成员变量:mWorker和mFuture他们分别是:WorkerRunnable和FutureTask,对于熟悉java的逗比应该知道这两个类其实是java里面线程池先关的概念。
    • 异步任务的构造方法主要用于初始化线程池先关的成员变量
    //创建一个新的异步任务。必须在UI线程上调用此构造函数
    public AsyncTask() {
    this((Looper) null);
    } //创建一个新的异步任务。必须在UI线程上调用此构造函数
    public AsyncTask(@Nullable Handler handler) {
    this(handler != null ? handler.getLooper() : null);
    } public AsyncTask(@Nullable Looper callbackLooper) {
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
    ? getMainHandler()
    : new Handler(callbackLooper); mWorker = new WorkerRunnable<Params, Result>() {
    public Result call() throws Exception {
    mTaskInvoked.set(true);
    Result result = null;
    try {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    //noinspection unchecked
    result = doInBackground(mParams);
    Binder.flushPendingCommands();
    } catch (Throwable tr) {
    mCancelled.set(true);
    throw tr;
    } finally {
    postResult(result);
    }
    return 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);
    }
    }
    };
    }

2.2 看execute(Params... params)方法

  • 看一下execute方法

    • 发现该方法中添加一个@MainThread的注解,通过该注解,可以知道我们在执行AsyncTask的execute方法时,只能在主线程中执行
    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
    }
  • 如果execute方法不是运行在主线程中会出现什么情况呢?

    • 执行,但是并没有什么区别,程序还是可以正常执行。但是onPreExecute方法是与开始执行的execute方法是在同一个线程中的,所以如果在子线程中执行execute方法,一定要确保onPreExecute方法不执行刷新UI的方法,否则将会抛出异常。
    new Thread(new Runnable() {
    @Override
    public void run() {
    Log.i("tag", Thread.currentThread().getId() + "");
    new MAsyncTask().execute();
    }
    }).start();
    Log.i("tag", "mainThread:" + Thread.currentThread().getId() + ""); @Override
    protected void onPreExecute() {
    super.onPreExecute();
    //更新UI
    title.setText("潇湘剑雨");
    Log.i(TAG, "onPreExecute...(开始执行后台任务之前)");
    }
    • 异常如下所示,在子线程中执行execute方法,那么这时候如果在onPreExecute方法中刷新UI,会报错,即子线程中不能更新UI。
    Process: com.example.aaron.helloworld, PID: 659
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
  • 接着看看executeOnExecutor这个方法源码

    • 具体的内部实现方法里:首先判断当前异步任务的状态,其内部保存异步任务状态的成员变量mStatus的默认值为Status.PENDING,所以第一次执行的时候并不抛出这两个异常,那么什么时候回进入这个if判断并抛出异常呢,通过查看源代码可以知道,当我们执行了execute方法之后,如果再次执行就会进入这里的if条件判断并抛出异常
    • 在executeOnExecutor中若没有进入异常分之,则将当前异步任务的状态更改为Running,然后回调onPreExecute()方法,这里可以查看一下onPreExecute方法其实是一个空方法,主要就是为了用于我们的回调实现,同时这里也说明了onPreExecute()方法是与execute方法的执行在同一线程中。
  • 然后将execute方法的参数赋值给mWorker对象那个,最后执行exec.execute(mFuture)方法,并返回自身。

  • 模拟测试一下抛出异常的操作

    • 看到我们定义了一个AsyncTask的对象,并且每次执行点击事件的回调方法都会执行execute方法,当我们点击第一次的时候程序正常执行,但是当我们执行第二次的时候,程序就崩溃了。
    final MyAsyncTask mAsyncTask = new MyAsyncTask();
    title.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    new Thread(new Runnable() {
    @Override
    public void run() {
    Log.i("tag", Thread.currentThread().getId() + "");
    mAsyncTask.execute();
    }
    }).start();
    Log.i("tag", "mainThread:" + Thread.currentThread().getId() + "");
    }
    });
    • 若这时候第一次执行的异步任务尚未执行完成则会抛出异常:
    Cannot execute task:the task is already running.
    • 若第一次执行的异步任务已经执行完成,则会抛出异常:
    Cannot execute task:the task has already been executed (a task can be executed only once)
  • 然后看一下exec.execute(mFuture)的实现

    • 这里的exec其实是AsyncTask定义的一个默认的Executor对象:
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    • 那么,SERIAL_EXECUTOR又是什么东西呢?
    public static final Executor SERIAL_EXECUTOR = new 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);
    }
    }
    }
    • 可以发现其继承Executor类其内部保存着一个Runnable列表,即任务列表,在刚刚的execute方法中执行的exec.execute(mFuture)方法就是执行的这里的execute方法。
    • 这里具体看一下execute方法的实现:
      • 1)首先调用的是mTasks的offer方法,即将异步任务保存至任务列表的队尾
      • 2)判断mActive对象是不是等于null,第一次运行是null,然后调用scheduleNext()方法
      • 3)在scheduleNext()这个方法中会从队列的头部取值,并赋值给mActive对象,然后调用THREAD_POOL_EXECUTOR去执行取出的取出的Runnable对象。
      • 4)在这之后如果再有新的任务被执行时就等待上一个任务执行完毕后才会得到执行,所以说同一时刻只会有一个线程正在执行。
      • 5)这里的THREAD_POOL_EXECUTOR其实是一个线程池对象。

2.3 构造方法中mWorker和mFuture的创建过程

  • 看一下执行过程中mWorker的执行逻辑:

    • 可以看到在执行线程池的任务时,我们回调了doInBackground方法,这也就是我们重写AsyncTask时重写doInBackground方法是后台线程的原因。
    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的执行逻辑
    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);
    }
    }
    };
    • 这里具体看一下postResultIfNotInvoked方法:
    private void postResultIfNotInvoked(Result result) {
    final boolean wasTaskInvoked = mTaskInvoked.get();
    if (!wasTaskInvoked) {
    postResult(result);
    }
    }
    • 其内部还是调用了postResult方法:
    private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
    new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
    }
    • 这里可以看到起调用了内部的Handler对象的sendToTarget方法,发送异步消息

03.异步机制的实现

  • 看AsyncTask内部定义了一个Handler对象

    • 内部的handleMessage方法,有两个处理逻辑,分别是:更新进入条和执行完成,这里的更新进度的方法就是我们重写AsyncTask方法时重写的更新进度的方法,这里的异步任务完成的消息会调用finish方法
    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:
    result.mTask.finish(result.mData[0]);
    break;
    case MESSAGE_POST_PROGRESS:
    result.mTask.onProgressUpdate(result.mData);
    break;
    }
    }
    }
  • 然后看看调用finish方法做了什么
    • 首先会判断当前任务是否被取消,若被取消的话则直接执行取消的方法,否则执行onPostExecute方法,也就是我们重写AsyncTask时需要重写的异步任务完成时回调的方法。
    private void finish(Result result) {
    if (isCancelled()) {
    onCancelled(result);
    } else {
    onPostExecute(result);
    }
    mStatus = Status.FINISHED;
    }
  • 既然有处理消息的,那么肯定有发送消息的。
    • 可以从构造方法中看到,当通过执行doInBackground方法拿到结果后,最后在finally执行发送该消息逻辑
    private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
    new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
    }
    • 可以看到MESSAGE_POST_PROGRESS这个消息发送是处理进度,需要在工作线程中
    @Override
    protected ReusableBitmap doInBackground(Void... params) {
    // enqueue the 'onDecodeBegin' signal on the main thread
    publishProgress();
    return decode();
    } @WorkerThread
    protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
    getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
    }

04.不同的SDK版本区别

  • 调用AsyncTask的execute方法不能立即执行程序的原因析及改善方案通过查阅官方文档发现,AsyncTask首次引入时,异步任务是在一个独立的线程中顺序的执行,也就是说一次只执行一个任务,不能并行的执行,从1.6开始,AsyncTask引入了线程池,支持同时执行5个异步任务,也就是说时只能有5个线程运行,超过的线程只能等待,等待前的线程某个执行完了才被调度和运行。换句话说,如果个进程中的AsyncTask实例个数超过5个,那么假如前5都运行很长时间的话,那么第6个只能等待机会了。这是AsyncTask的一个限制,而且对于2.3以前的版本无法解决。如果你的应用需要大量的后台线程去执行任务,那么只能放弃使用AsyncTask,自己创建线程池来管理Thread。不得不说,虽然AsyncTask较Thread使用起来方便,但是它最多只能同时运行5个线程,这也大大局限了它的作用,你必须要小心设计你的应用,错开使用AsyncTask时间,尽力做到分时,或者保证数量不会大于5个,否就会遇到上次提到的问题。可能是Google意识到了AsynTask的局限性了,从Android3.0开始对AsyncTask的API做出了一些调整:每次只启动一个线程执行一个任务,完了之后再执行第二个任务,也就是相当于只有一个后台线在执行所提交的任务。

05.AsyncTask的缺陷和问题

5.1 AsyncTask对应线程池

  • Asynctask对应的线程池ThreadPoolExecutr都是进程范围内共享的,都是static的,所以是Asynctask控制着进程范围内所有的子类实例。由于这个限制的存在,当使用默认线程池时,如果线程数超过线程池的最大容量,线程池就会爆掉(3.0后默认串行执行,不会出现个问题)。针对这种情况,可以尝试自定义线程池,配合Asynctask使用。
  • 关于默认线程池:
    • AsyncTask里面线程池是一个核心线程数为CPU + 1,最大线程数为CPU * 2 + 1,工作队列长度为128的线程池,线程等待队列的最大等待数为28,但是可以自定义线程池。线程池是由AsyncTask来处理的,线程池允许tasks并行运行,需要注意的是并发情况下数据的一致性问题,新数据可能会被老数据覆盖掉类似volatile变量。所以希望tasks能够串行运行的话,使用SERIAL_EXECUTOR。

5.2 AsyncTask生命周期问题

  • 很多开发者会认为一个在Activity中创建的AsyncTask随着Activity的销毁而销毁。然而事实并非如此。AsynTask会一直执行,直到doInBackground()方法执行完毕,然后,如果cancel(boolean)被调用,那么onCancelled(Result result)方法会被执行;否则,执行onPostExecuteResult result)方法。如果我们的Activity销毁之前,没有取消AsyncTask,这有可能让我们的应用崩溃(crash)。因为它想要处理的view已经不存在了。所以,我们是必须确保在销毁活动之前取消任务。总之,我们使用AsyncTask需要确保AsyncTask正确的取消。

5.3 AsyncTask内存泄漏问题

  • 如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将续在内存里保留这个引用,导致Activity无法被回收,引起内存泄漏。

5.4 AsyncTask结果丢失问题

  • 屏幕旋转或Activity在后台被系统杀掉等情况会导致Actvity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。

5.5 AsyncTask并行还是串行问题

  • 在Android1.6之前的版本,AsyncTask是串行的,在1.6-2.3的版本,改成了并行的。在2.3之后的版本又做了修改,可以支持并行和串行,当想要串行执行时,直接行execute()方法,如果需要并行执行时,执行executeOnExecutor(Executor)。

关于其他内容介绍

01.关于博客汇总链接

02.关于我的博客

AsyncTask异步任务类的更多相关文章

  1. AsyncTask异步任务类使用学习

    new MyAsyncTask() .execute("http://pic.baike.soso.com/p/20120716/bki-20120716095331-640956396.j ...

  2. Android线程管理之AsyncTask异步任务

    前言: 前面几篇文章主要学习了线程以及线程池的创建与使用,今天来学习一下AsyncTask异步任务,学习下AsyncTask到底解决了什么问题?然而它有什么弊端?正所谓知己知彼百战百胜嘛! 线程管理相 ...

  3. Android多线程分析之五:使用AsyncTask异步下载图像

    Android多线程分析之五:使用AsyncTask异步下载图像 罗朝辉 (http://www.cnblogs.com/kesalin) CC 许可,转载请注明出处 在本系列文章的第一篇<An ...

  4. 使用AsyncTask异步更新UI界面及原理分析

    概述: AsyncTask是在Android SDK 1.5之后推出的一个方便编写后台线程与UI线程交互的辅助类.AsyncTask的内部实现是一个线程池,所有提交的异步任务都会在这个线程池中的工作线 ...

  5. Android 多线程----AsyncTask异步任务详解

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3 ...

  6. android AsyncTask异步下载并更新进度条

    AsyncTask异步下载并更新进度条    //如果不是很明白请看上篇文章的异步下载 AsyncTask<String, Integer, String> 第一个参数:String 传入 ...

  7. Android中使用Thread线程与AsyncTask异步任务的区别

    最近和几个朋友交流Android开发中的网络下载问题时,谈到了用Thread开启下载线程时会产生的Bug,其实直接用子线程开启下载任务的确是很Low的做法,那么原因究竟如何,而比较高大上的做法是怎样? ...

  8. AsyncTask 异步任务基本使用-下载视频

    概述 android 提供了一个异步任务类AsyncTask,使创建异步任务.更新UI变得更加简单,不再需要编写任务线程和Handler实例即可完成相同的任务.本例子将演示并实现,使用AsyncTas ...

  9. AsyncTask异步交互和httpurlconnection结合使用

    //网络请求数据 package com.baidu.myutils; import java.io.BufferedReader; import java.io.InputStreamReader; ...

  10. C# 异步工具类 及一点小小的重构经验

    2015年新年第一篇随笔, 祝福虽然有些晚,但诚意还在:新年快乐. 今天主要是想分享一异步工具类,在C/S架构中.先进行网络资源异步访问,然后将回调函数 Invoke到UI线程中进行UI处理. 这样的 ...

随机推荐

  1. .NET 云原生架构师训练营(模块二 基础巩固 配置)--学习笔记

    2.2.3 核心模块--配置 IConfiguration Options ASP.NET Core 中的配置:https://docs.microsoft.com/zh-cn/aspnet/core ...

  2. Linux进程与线程的基本概念及区别

    前言 假设你正在玩一款在线多人游戏,在游戏中,有多个角色需要进行不同的操作,例如攻击.移动.释放技能等等. 接下来,我们用玩游戏的例子,来解释进程和和线程的概念,以及进程和线程的区别. 进程的基本概念 ...

  3. OFDM系统各种调制阶数的QAM误码率(Symbol Error Rate)与 误比特率(Bit Error Rate)仿真结果

    本文是OFDM系统的不同QAM调制阶数的误码率与误比特率仿真,仅考虑在高斯白噪声信道下的情景,着重分析不同信噪比下的误码(符号)率性能曲线,不关心具体的调制与解调方案,仿真结果与理论的误码率曲线进行了 ...

  4. TCP Server and Client Demo

    server.go package main import ( "bufio" "fmt" "io" "net" &qu ...

  5. Redis高级系列详解

    01-Redis系列之-Redis介绍安装配置 02-Redis系列之-架构和高级API的使用 03-Redis系列之-高级用法详解 04-Redis系列之-持久化(RDB,AOF) 05-Redis ...

  6. 问题:django中对datetime类型数据在pycharm中sqlite3进行修改时,修改后datetime日期数据变成了时间戳类型

    这是正在修改的 提交完之后 问题原因 问题原因是sqlite数据库对日期类型不敏感,Pycharm直接插入会变成图中这样的时间戳,用POST请求添加数据或Django自带的后台管理插入不会有这样的问题 ...

  7. MySQL 数据库死锁排查

    死锁排查方法 查看进程状态 show processlist; 查看行锁的状态 show status like 'InnoDB_row_lock%'; 查询是否有死锁 show engine inn ...

  8. 【Azure Function App】在ADF(Azure Data Factory)中调用 Azure Function 时候遇见 Failed to get MI access token

    问题描述 在ADF(Azure Data Factory)中,调用Azure Function App中的Function,遇见了 Failed to get MI access token Ther ...

  9. 【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.

    问题描述 Spring Cloud Azure 4.0 调用 Key Vault 的代码中,没有找到设置 authority-host 的配置项,导致认证出现错误 Caused by: com.mic ...

  10. 【Azure 应用服务】查看App Service for Linux上部署PHP 7.4 和 8.0时,所使用的WEB服务器是什么?

    问题描述 如何查看PHP应用部署到App Service后,Azure上面使用的应用服务器是什么呢?因为App Service支持两种操作系统,Windows 和 Linux.在Windows中,使用 ...