AsyncTask源码阅读笔记

写在前面

感觉最近自己需要多读书,所以在以后的一段时间里可能都是笔记形式的文了,希望自己能厚积薄发吧。

AsyncTask简介

AsyncTask是一个轻量级的异步任务类,允许你将一个耗时操作放在后台进行,并且会返回操作的结果给你。那么AsyncTask和Thread-Handler或者线程池有什么异同呢?

在AsyncTask的源码注释里这样描述:

/**
 * <p>AsyncTask enables proper and easy use of the UI thread. This class allows to
 * perform background operations and publish results on the UI thread without
 * having to manipulate threads and/or handlers.</p>
 */

AsyncTask能让更加简便的使用UI线程。这个类允许执行后台操作和将结果发送到UI线程而不必操作线程和handlers。

读了上面的注释让我们对AsyncTask有了一定的了解,这是个方便我们的类,让我们在后台执行操作结果而不必自己手动的去切换线程,那么这个类是否有其他的限制呢?

 /** <p>AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler}
  * and does not constitute a generic threading framework. AsyncTasks should ideally be
  * used for short operations (a few seconds at the most.) If you need to keep threads
  * running for long periods of time, it is highly recommended you use the various APIs
  * provided by the <code>java.util.concurrent</code> package such as {@link Executor},
  * {@link ThreadPoolExecutor} and {@link FutureTask}.</p>
  */

AsyncTask被设计作为Thread和Handler间的帮助类,并非构建线程框架的通用类。AsyncTask理想情况下应该被用来进行短时间的操作,如果你需要保证线程长期运行,那么强烈推荐你使用java.util.concurrentpackage提供的多种API,例如Executor、ThreadPoolExecutor和FutureTask。

简单的演示

上面简单的介绍了一下AsyncTask,下面看一下如何使用。AsyncTask提供了4个核心方法:

  • onPreExecute(),在主线程中执行,在后台任务执行之前,此方法会被调用

  • doInBackground(Params... params),此方法用于执行需要执行的异步任务

  • onProgressUpdata(Progress... values),在主线程中执行,当后台任务的执行进度发生改变时此方法会被调用

  • onPostExecute(Result result),在主线程中执行,返回操作结果,返回类型是doInBackground的返回值

        new AsyncTask<String, Integer, Bean>() {

            @Override
            protected void onPreExecute() {//做一些准备工作
                super.onPreExecute();
            }

            @Override
            protected Bean doInBackground(String... params) {//后台任务
                return null;
            }

            @Override
            protected void onProgressUpdate(Integer... values) {//后台任务执行进度发生变化
                super.onProgressUpdate(values);
            }

            @Override
            protected void onPostExecute(Bean aLong) {//异步任务执行完成返回结果
                super.onPostExecute(aLong);
            }
        }.execute(url1,url2);

上面的代码只是一个简单的实例,并非真的演示如何使用。以上代码中的三个参数可以理解为url、进度和自定义的数据类型。带入我们平时的开发中就是根据url拿到数据,然后在界面上更新进度。在方法中这样String... params表示不定数量的参数,是数组型的参数。上面的方法实际上运行的效果是串行执行的,你可能会说AsyncTask内部不是封装了一个线程池吗?为毛会是串行的?这个问题先留着,先把结论摆在这,而且我也在AsyncTask的源码中看到了如下的注释(原文不放了,感兴趣的请自己去看):

调度任务是用队列单独的调度一个后台线程还是用线程池取决于平台版本。刚发布的时候,AsyncTask是以串行线程的方式执行的。从Android DONUT(1.6)开始允许多任务并发执行。在Android HONEYCOMB(3.0)之后又变成了单任务串行执行,这是为了避免由于并发操作可能带来的错误。如果你真的想要并发执行,你可以使用excuteOnExecutor和THREAD_POOL_EXECUTOR。

好了,读到这,终于对AsyncTask有了一些了解了,带着一些问题去看看源码吧。

源码笔记

读源码先从AsyncTask的入口execute()开始看:

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

调了一个方法,没啥好说的,跟进去看就行了,这里返回值是AsyncTask,方便我们持有一个AsyncTask的引用。

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

这里先看mStatus,这东西是个枚举类型,里面仨值分别是:

  • PENDING:任务还没有被执行过,表示可以被执行
  • RUNNING:任务正在执行
  • FINISHED:任务已经完成

从代码中可以看出来只有这个值是PENDING的时候才会被执行,其他的值都会报异常,这就是AsyncTask对象只能运行一次的由来了,每次执行任务都需要新建一个AsyncTask对象(准确的来说是子类对象)。

在运行之后将mStatus的值改为RUNNING,之后这个对象就不能在其他的地方被执行了。可以看到在任务真正被执行之前调用了onPreExecute()方法,这就是这个方法可以做一些准备工作的原因。

之后先获取参数,再执行。这里先简单的说说,要弄懂最后这句exec.execute(mFuture);代码还需要结合前面的代码来看。

之前在前面说了AsyncTask内部有两个线程池,那么他要干啥为毛要两个线程池呢?因为一个线程池是串行的线程池,一个进程中的所有待执行的任务都会在这个串行的线程池中排队执行。接下来看一下AsyncTask内部的俩线程池在代码里长啥样:

    /**
     * 可以并发执行任务
     */
    public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

    /**
     * 在串行命令下一次执行一个任务。
     */
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

上面那个线程池是真正用来执行任务的,下面的是用来排队等待的。可以清楚的看到这俩是静态的,所以是全局共享这个就不做过多的解释了。那么按顺序来,从下面的线程池的execute()来看:

    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();
                    }
                }
            });
            //如果当前没有正在执行的任务,那么执行下一个AsyncTask任务
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {//取出这个任务
                //放入并发线程池中执行
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

从上面代码的流程我们可以认识到在我们不指定线程池的情况下,的确,我们的代码是以串行的方式被执行的。关于mFuture其真实类型是FutureTask,对此我们不需要再做更多的了解(其实我了解的也不多...),当然了如果你对Java的并发编程感兴趣可以自己去做更多的了解。在这我们需要知道的就是mFuture的run方法会调用mWorker的call方法,因此mWorker的call方法最终会在线程池中执行。

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

以上可以看到我们希望在后台执行的代码被调用了,并且结果被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里面是怎么处理的:

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

在这可以看到这个Handler是一个静态类,静态类会在类被加载的时候就被初始化,而Handler的初始化时需要looper()的,所以这就需要你在主线程中使用AsyncTask。否则要么出错,要么AsyncTask就被你废了。好了,继续看,在对应的情况底下调用了AsyncTask的finish方法,看下是啥:

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

如果被取消,就调取消的方法,不然的话就回调这个onPostExecute(result),由于是经过handler发送的,所以线程已经切换到了AsyncTask调用的线程中去了,我们就可以在主线程中开心的使用这个结果去更新UI了。

小结

  • AsyncTask用起来比较方便,但是特别耗时的操作并不适合用它来执行。
  • AsyncTask默认是串行执行,但是你可以通过指定执行的线程池来让他并发执行。
  • AsyncTask对象只能被执行一次。
  • AsyncTask使用Handler来切换线程。
  • AsyncTask一边情况下都是需要在主线程被实现和调用的。
  • AsyncTask在不同版本的Android上可能会有不同的表现,但是现在用户Android版本普遍在4.0以上,这个就无需考虑了。

【使用教程】 一、环境配置 1、建议下载anaconda和pycharm 在anaconda中配置好环境,然后直接导入到pycharm中,在pycharm中运行项目 anaconda和pycharm安装及环境配置参考网上博客,有很多博主介绍 2、在anacodna中安装requirements.txt中的软件包 命令为:pip install -r requirements.txt 或者改成清华源后再执行以上命令,这样安装要快一些 软件包都安装成功后才算成功 3、安装好软件包后,把anaconda中对应的python导入到pycharm中即可(不难,参考网上博客) 二、环境配置好后,开始训练(也可以训练自己数据集) 1、数据集准备 需要准备yolo格式的目标检测数据集,如果不清楚yolo数据集格式,或者有其他数据训练需求,请看博主yolo格式各种数据集集合链接:https://blog.csdn.net/DeepLearning_/article/details/127276492 里面涵盖了上百种yolo数据集,且在不断更新,基本都是实际项目使用。来自于网上收集、实际场景采集制作等,自己使用labelimg标注工具标注的。数据集质量绝对有保证! 本项目所使用的数据集,见csdn该资源下载页面中的介绍栏,里面有对应的下载链接,下载后可直接使用。 2、数据准备好,开始修改配置文件 参考代码中data文件夹下的banana_ripe.yaml,可以自己新建一个不同名称的yaml文件 train:训练集的图片路径 val:验证集的图片路径 names: 0: very-ripe 类别1 1: immature 类别2 2: mid-ripe 类别3 格式按照banana_ripe.yaml照葫芦画瓢就行,不需要过多参考网上的 3、修改train_dual.py中的配置参数,开始训练模型 方式一: 修改点: a.--weights参数,填入'yolov9-s.pt',博主训练的是yolov9-s,根据自己需求可自定义 b.--cfg参数,填入 models/detect/yolov9-c.yaml c.--data参数,填入data/banana_ripe.yaml,可自定义自己的yaml路径 d.--hyp参数,填入hyp.scratch-high.yaml e.--epochs参数,填入100或者200都行,根据自己的数据集可改 f.--batch-size参数,根据自己的电脑性能(显存大小)自定义修改 g.--device参数,一张显卡的话,就填0。没显卡,使用cpu训练,就填cpu h.--close-mosaic参数,填入15 以上修改好,直接pycharm中运行train_dual.py开始训练 方式二: 命令行方式,在pycharm中的终端窗口输入如下命令,可根据自己情况修改参数 官方示例:python train_dual.py --workers 8 --device 0 --batch 16 --data data/coco.yaml --img 640 --cfg models/detect/yolov9-c.yaml --weights '' --name yolov9-c --hyp hyp.scratch-high.yaml --min-items 0 --epochs 500 --close-mosaic 15 训练完会在runs/train文件下生成对应的训练文件及模型,后续测试可以拿来用。 三、测试 1、训练完,测试 修改detect_dual.py中的参数 --weights,改成上面训练得到的best.pt对应的路径 --source,需要测试的数据图片存放的位置,代码中的test_imgs --conf-thres,置信度阈值,自定义修改 --iou-thres,iou阈值,自定义修改 其他默认即可 pycharm中运行detect_dual.py 在runs/detect文件夹下存放检测结果图片或者视频 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值