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调用的线程中去了(关于这个如果你有不明白可以看我的Android消息机制浅析),我们就可以在主线程中开心的使用这个结果去更新UI了。

小结

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

参考资料:
《Android开发艺术探索》
源码版本:Android 7.0 api 24

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,565评论 25 707
  • Android Handler机制系列文章整体内容如下: Android Handler机制1之ThreadAnd...
    隔壁老李头阅读 3,085评论 1 15
  • 美图欣赏 Java、Android知识点汇集 Java集合类 ** Java集合相关的博客** java面试相关 ...
    ElvenShi阅读 1,688评论 0 2
  • 虽说现在做网络请求有了Volley全家桶和OkHttp这样好用的库,但是在处理其他后台任务以及与UI交互上,还是需...
    weishu阅读 3,298评论 10 55
  • 时间飞快 忽尔已入秋 这一年不知不觉已过大半 用照片记录些许点滴 和这个夏天告别 一个人在这世上从最初的很多离不开...
    陈叔深阅读 266评论 0 0