AsyncTask源码梳理及总结

2023-11-18

结合Android 7.0源码,全面解析AsyncTask的源码,梳理AsyncTask使用过程中的一些注意事项。
分析源码之前,我们先来梳理一下使用,AsyncTask使用示例:

  public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new MyAsyncTask().execute();
    }

    private class MyAsyncTask extends AsyncTask<Void,Integer,Void>{
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        @Override
        protected Void doInBackground(Void... params) {
            int downPercent=1;
            publishProgress(downPercent);
            return null;
        }

       @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
        }

        @Override
        protected void onPostExecute(Void Void) {
            super.onPostExecute(Void);
        }
    }
}

AsyncTask是一个abstract类,我们要使用AsyncTask类,需要实现它的抽象方法。AsyncTask的泛型参数有3个:

  • Params 执行AsyncTask的时候传入的参数。
  • Progress 后台任务的执行的进度参数。
  • Result 后台任务执行的返回值。

    我们使用AsyncTask的时候会先构建一个AsyncTask的实例,然后调用它的execute方法,
    然后会回调AsyncTask的4个方法,onPreExecute是执行在主线程中的,我们通常会做一些初始化的操作。 doInBackground方法中我们会做一些真正的耗时操作,如果是需要通知进度的话publishProgress通过进行更新,然后在onProgressUpdate方法中更新ui组件。 onPostExecute方法在doInBackground方法执行结束后回调。

使用起来很简单,但是我们要深究的话,就有很多疑问?比如:
1.为什么AsyncTask的几个方法可以在子线程和主线程之间灵活转换呢?
2.AsyncTask内部的实现原理究竟是怎样的?

带着这样的疑问,我们跟进源码来看一下:首先我们是构建了一个AsyncTask的实例,我们看一下AsyncTask的构造方法。代码如下:

    public AsyncTask() {
        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);
                }
            }
        };
    }

可以看到在构造方法中并没有执行什么代码,只是构建了两个对象mWorker ,mFuture 两个对象,然后把
mWorker 对象作为参数传递到了mFuture 对象中。这段代码看不出来什么。然后跟进execute方法中看一下:

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

what… >–< 就一行代码,只能看executeOnExecutor方法了:

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

这段代码才点有意思了呀,可以看到这里先执行了 onPreExecute()方法,
所以onPreExecute方法执行在哪个线程呢,一定就是主线程?no , no,no, 太绝对了。 应该是AsyncTask.execute方法执行在哪个线程它就执行在哪个线程。然后我们看到代码执行了exec.execute(mFuture)方法,这个execute是何许人也呢? 回头看一下我们的调用栈,发现exec其实是我们传递过来的sDefaultExecutor对象。从源码我们知道sDefaultExecutor是AsyncTask中的一个静态变量,我们来看一下赋值过程:

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

赋值一通后,发现sDefaultExecutor其实就是一个SerialExecutor对象,那我们之前调用的sDefaultExecutor.execute方法,其实就是调用SerialExecutor的execute方法。来看一下execute方法的实现,可以看到这里是使用了一个ArrayDeque集合,将一个Runable对象压入到集合的尾部,但是这里有个比较精妙的地方,就是将Runable压入集合的时候,压入的是一个重新构建的Runable对象,然后在新的Runable的run方法中调用了传递进来Runable的run方法,而且finally方法块中执行了scheduleNext方法。

然后判断mActive是否为null,如果为null,就执行scheduleNext方法。 然后我们看一下scheduleNext方法,在这个方法中从ArrayDeque集合队首取一个Runable对象,然后丢进THREAD_POOL_EXECUTOR的
execute方法中。这里的这个THREAD_POOL_EXECUTOR又是什么呢?查看源码,我们可以知道,THREAD_POOL_EXECUTOR是一个线程池。

     /**
     * An {@link Executor} that can be used to execute tasks in parallel.
     */
    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

分析到这里,我们需要梳理一下思路,才能进一步向下分析。从源码看到SERIAL_EXECUTOR对象是一个静态变量,意味着SerialExecutor对象在AsyncTask类中只有一份。那就是说ArrayDeque集合也只有一份。所以不论我们构建多少个AsyncTask的实例执行execute方法,最后都会放入同一个ArrayDeque队列中。结合我们之前Runable对象压入ArrayDeque和取出的方法。可以知道,不论我们当前应用构建多少个AsyncTask的实例并执行execute方法,都是顺序执行的。 这种方法其实是一种很有意思的单线程实现机制!

继续分析:我们现在知道FutureTask的run方法是执行在子线程的,按照逻辑doInBackground也应该运行在这个方法中。我们来找一下,我们注意到FutureTask的run方法中执行了callable.call方法:

   public void run() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

这里的这个callback对象就是我们构建AsyncTask的时候创建的mWorker对象,所以我们再回到看一下mWorker对象的call方法:

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

果然,哈哈。doInBackground就在这里。所以doInBackground方法是执行在子线程中的。finally代码块中执行了postResult方法,跟进去看一下:

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

好眼熟呀,原来还是用了AsyncTask内部还是用了handler机制进行线程间的消息通信的,跟着源码我们很快就发现这个handler就是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;
            }
        }
    }

原来postResult发送消息,然后在handleMessage中执行finish方法,然后我们看一下finish方法:

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

先判断任务是否取消,如果任务已经取消就执行onCancelled,如果没有取消就回调onPostExecute,表示任务执行完成了。 既然onPostExecute是执行在handleMessage方法中,而这handler的looper对象又是Looper.getMainLooper()那么毫无疑问的onPostExecute就是执行在主线程中的。

然后我再看下publishProgress方法,

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

原来也是通过handler发送消息,然后在handleMessage中回调onProgressUpdate方法。

ok,分析到这里基本上我们就把AsyncTask的实现原理分析清楚了。 还记得前面我们说过现在的这个AsyncTask不论我们构建多少个AsyncTask实例执行execute方法,每个AsyncTask的后台任务(doInBackground)都是顺序依次执行的。 那有没有办法,让AsyncTask的后台任务并发执行呢?
当然,之前的分析我们知道因为AsyncTask是通过sDefaultExecutor来执行后台任务的,而sDefaultExecutor是SerialExecutor对象,SerialExecutor中的队列是顺序执行的,所以后台任务才会顺序执行。如果我把sDefaultExecutor替换为自己定制的线程池,不就可以实现并行执行任务了。
代码如下:

  new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 

通过AsyncTask中的这个线程池,我们可以并行执行任务。 这个线程池创建的规则是什么??

分析完成AsyncTask的源码后,我们总结一下重要的知识点,以便之后使用的时候灵活应用:
1.AsyncTask.execute方法执行在哪个线程onPreExecute就执行在哪个线程。
2.不论我们当前应用构建多少个AsyncTask的实例并执行execute方法,所有都是顺序执行的。
3.如果需要并行执行AsyncTask任务,我们需要手动配置线程池。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

AsyncTask源码梳理及总结 的相关文章

  • Android 错误 - close() 从未在数据库上显式调用

    我应该在代码的哪里调用 close LogCat 返回此错误 close 从未在数据库上显式调用 android database sqlite DatabaseObjectNotClosedException 应用程序未关闭此处打开的游标
  • 无法再转换为 LayerDrawable(升级 v7 后)

    我正在使用支持库的最新版本 22 1 1 我曾经这样去 mRatingBar RatingBar getActivity findViewById R id rating LayerDrawable layer LayerDrawable
  • FCM:无法实例化接收器 com.google.firebase.iid.FirebaseInstanceIdReceiver:

    仅在 Oreo 8 0 中接收推送通知时应用程序崩溃 java lang RuntimeException Unable to instantiate receiver com google firebase iid FirebaseIns
  • Moshi 无法解析 nullable

    你好 希望你能帮助我 使用 kotlin Retrofit2 moshi 我从 https api spacexdata com v3 launches 获取数据并解析它 一切都很顺利 我得到的属性如下 flight number miss
  • 错误:链接引用失败。 -> 排队作业

    我正在使用 Kotlin 学习 Android Material Design 一切都很顺利 直到我尝试使用 android support design widget FloatingActionButton 当我重建项目时 我收到以下错
  • 为什么抽屉布局中的视图强制缩放以填充屏幕

    这是我的测试代码
  • 如何解决 FireBase 数据库 Key 中的禁止字符或解决方法

    我有这个 FireBase 数据库结构 我真的很想拥有像这个例子这样的密钥 US name United States PATH TO STREETS US California Orange County Orange 3138 E Ma
  • 实施材质主题时遇到问题

    我在用this http antonioleiva com material design everywhere 作为在 Android 5 0 之前的设备上向现有应用程序实施 Material 主题的教程 我的问题是我得到了Null Po
  • Android 设备选择器在目标列中显示红色 X

    我最近构建了一个 Android 应用程序 minSdkVersion 为 7 targetSdkVersion 为 10 我现在正在使应用程序兼容平板电脑并添加操作栏 因此 我将 targetSdkVersion 更新为 15 并在项目属
  • 当 Android 上的脸部靠近屏幕时,以编程方式关闭屏幕

    我的应用程序是一个拨号器 当用户将手机靠近头部时 我需要关闭屏幕并防止单击控件 就像本机 Android 拨号器行为一样 我需要什么 API 级别以及如何以正确的方式做到这一点 我通过反汇编一个非常著名的 VoIP 应用程序找到了解决方案
  • 如何在应用程序中创建会话对象

    在我的应用程序中 我想创建一个用于登录和注销的会话 我不知道如何使用会话 任何人都可以通过提供一些示例来帮助我 我认为会话对象应该是在应用程序开始运行时声明和初始化的静态对象 我遇到了这个问题 并决定将我的会话对象放入 utils 类中 该
  • 长按 HOME 按钮菜单隐藏 Android 应用程序

    我想从 且仅从 完成后长时间按住 HOME 按钮时出现的菜单中隐藏我的 Android 应用程序 有没有办法做到这一点 以编程方式调用 finish 并不能解决问题 有很多关于从启动器和任务管理器隐藏应用程序的线程 但这不是我想要的 我只是
  • webView.loadUrl 显示空白屏幕

    我必须加载这些通过使用下面的代码 但最终显示空白屏幕作为输出 您可以检查url代码之间的链接位于last line WebPagerLoader java public class WebPageLoader extends Activit
  • 点击按钮时的 Admob 插页式广告

    我有一个应用程序 我正在使用 admob 横幅 现在我想在点击按钮时显示插页式广告 我的应用程序有 2 个活动 我想在第二个活动上显示插页式广告 第二个活动有一个返回第一个活动的按钮 我想在单击按钮后显示广告 我可以在单击按钮时显示广告 但
  • 短信管理器在少于 160 个字符时发送多部分消息

    我编写了一个使用短信管理器的应用程序 我用的方法sendTextMessage 但这行不通 现在我正在使用sendMutlipartTextMessage 这是工作 但当它大约 60 个字符时 它会发送多部分消息 这个是正常的 我读过的所有
  • 如何为移动应用程序创建无密码登录

    我有兴趣在移动应用程序和 API 之间构建某种无密码登录 假设我可以控制两者 动机是必须登录对用户来说非常烦人并且存在安全风险 例如 用户将重复使用现有密码 我希望用户能够立即开始使用该应用程序 我想知道是否有一些可行的技术 例如 在移动设
  • 找不到元素“android.support.constraint.ConstraintLayout”的声明

    我创建了一个名为的 xml 文件activity main sw50dp 但是当我尝试验证它时 它给了我错误 错误 4 42 cvc elt 1 a 找不到元素 android support constraint ConstraintLa
  • 数字时钟不改变时间

    我正在开发一个数字时钟小部件 我写了代码 但它没有更新时间 我没有使用任何服务并在模拟器中运行 我的代码如下 public class ExampleAppWidgetProvider extends AppWidgetProvider D
  • 新的 Android 项目未创建布局或 Java 文件

    这两天我一直在尝试简单地阅读 Big Nerd Ranch Android 编程书 第一章的前几页 我的问题的要点是 当我创建新的 Android 应用程序时 不会创建布局或 java 文件 我已经从 Android 开发站点安装了 ADT
  • 加载 highchart 时 Android 错误膨胀类

    我正在尝试加载highcharts via Dialog 下面是我的代码 Gradle implementation com highsoft highcharts highcharts 9 0 1 XML

随机推荐

  • socket编程之服务器端与客户端(代码实例)

    在我们学习的过程中 对TCP IP UDP Socket编程这些词应该有所了解了 随着网络技术的发展 这些词充斥着我们的耳朵 那么我想介绍一下 什么是TCP IP UDP socket在哪里呢 socket通信是什么呢 socket接口函数
  • 【算法刷题】算法题解题方法技巧及典例汇总

    关键词 算法 二分查找 DFS BFS 动态规划 滑动窗口 位运算 前言 Leetcode刷题目的 无疑是提高自己的编程和算法能力 算法是面试逃不过的环节 之前都是刷每日一题 然后也有大半年没刷了 感觉并未真正学到啥东西 也没记住啥 之后开
  • 从0开始springboot后台管理项目-mybatis-plus/druid链接数据库多数据源

    1 准备完成的功能 通过mybatis plus druid进行数据库链接 配置多数据源 使用mybatis plus的原因就是太强大 比如支持Lambda 依赖少等等优点 可以搜索一下mybatis plus和mybatis的区别 2 m
  • 如何安装pip3以及第三方python库(for Mac)

    环境 OS 10 12 3 16D32 macOS Sierra Python3 Python 3 6 1 什么是pip Python之所以强大 其中一个原因是其丰富的第三方库 pip则是python第三方库的包管理工具 由于在Mac上py
  • Win10多用户同时远程桌面,并各自操作互不干扰

    微软Server版操作系统默认是支持多用户登陆的 例如Windows Server 2012 而Win10操作系统正常情况下是不允许用户同时远程的 即一个用户远程进来会把另一个用户踢掉 因此需要破解才能使得多个用户同时登陆远程桌面 也即需要
  • 笔记22-1(C语言进阶 动态内存管理)

    目录 注 为什么存在动态内存分配 动态内存函数的介绍 malloc和free calloc realloc 常见的动态内存错误 1 对NULL指针的解引用操作 2 对动态开辟空间的越界访问 3 使用free函数释放了非动态开辟的空间 4 使
  • 多线程间的5种通信方式

    问题 有两个线程 A 线程向一个集合里面依次添加元素 abc 字符串 一共添加十次 当添加到第五次的时候 希望 B 线程能够收到 A 线程的通知 然后 B 线程执行相关的业务操作 线程间通信的模型有两种 共享内存和消息传递 以下方式都是基本
  • 西门子S7-200PLC的自锁

    自锁 百度 交流接触器通过自身的常开辅助触头使线圈总是处于得电状态的现象叫做自锁 在通常的电路中 按下开关 电路通电 松开开关 电路又断开了 一旦按下开关 就能够自动保持持续通电 直到按下其它开关使之断路为止 这样的电路 称为自锁电路 置位
  • CocosCreator 游戏小地图/地图雷达

    更多笔记和源码请关注 微信公众号 CocosCreator笔记 演示 ps 请注意左上角 技术摘要 大地图与小地图坐标转换 更新小地图中元素及视口位置 拖动小地图中视口位置 更新Main Camera位置 实现 01 小地图 图片 的宽高比
  • 1、CMM与CMMI的关系 2、软件全面质量管理的思想体系

    1 CMM与CMMI的关系 CMMI即CMM集成 是系统工程和软件工程的集成成熟度模型 CMMI更适合于信息系统集成企业 CMMI是在CMM基础上发展起来的 它继承并发扬了CMM的优良特性 借鉴了其他模型的优点 融入了新的理论和实际研究成果
  • React Dva项目引入antd UI框架

    上文 React 搭建DvaJS开发环境中我们大家了一个Dva的开发环境 那么 下面 我们就用dva项目引入一下antd 我们平时做react开发 主要也都会选择它 我们直接在项目终端执行 npm install antd 4 24 2 b
  • [转]基于TDOA声源定位算法仿真--MATLAB仿真

    转自 http t cn AiTjYCqD 关注微信公众号 通信小课堂 获取专业小知识 声源定位算法是利用麦克风阵列进行声音定位 属于宽带信号 传统的MUSIC和DOA算法并不适用该场景 本仿真主要用TDOA算法进行定位 常用的阵列信号定位
  • 深入MySQL查询过程底层原理,我找到了MySQL查询慢的根本原因!

    V xin ruyuanhadeng获得600 页原创精品文章汇总PDF 前言 接上一节 那么 一次查询的全过程是什么样的呢 这个时候 我们通过各种百度和Google 然后加上自己的理解 终于搞明白了MySQL一次查询的全过程了 首先 用户
  • MySQL 报错: Subquery returns more than 1 row

    MySQL 报错 Subquery returns more than 1 row 查询 demo SET tableNameList SELECT id FROM tableName 查询多条记录 SET tableNameList SE
  • Seata: 运行nacos-config.sh报Please check the cluster status的总结

    一 看所在的机器是否安装了curl并正常工作 二 检查nacos是否能正常访问 三 看如下命令结果是否异常 根据实际情况调整nacos地址 curl X POST http 127 0 0 1 8848 nacos v1 cs config
  • 27 openEuler管理网络-通过ifcfg文件配置网络

    文章目录 27 openEuler管理网络 通过ifcfg文件配置网络 27 1 配置静态网络 27 2 配置动态网络 27 3 配置默认网关 27 openEuler管理网络 通过ifcfg文件配置网络 说明 通过ifcfg文件配置的网络
  • MySQL-mysql 8.0.11安装教程

    转载 https www cnblogs com laumians notes p 9069498 html 另外在奉上我自己搜索到的两个mysql安装网站 1 https dev mysql com doc refman 8 0 en d
  • G - Five-In-a-Row

    B Five In a Row time limit per test 1 second memory limit per test 256 megabytes input standard input output standard ou
  • 好长时间不看,php魔术变量又忘了,赶紧总结下

    看到了wordpress的这行代码想起来的 define WP USE THEMES true Loads the WordPress Environment and Template require dirname FILE wp blo
  • AsyncTask源码梳理及总结

    结合Android 7 0源码 全面解析AsyncTask的源码 梳理AsyncTask使用过程中的一些注意事项 分析源码之前 我们先来梳理一下使用 AsyncTask使用示例 public class MainActivity exten