AsyncTask源码深度解析

相信有很多同学都使用过AsyncTask,都知道onPreExecute,onProgressUpdate,onPostExecute方法均运行在主线程,doInBackground方法运行在子线程,可以在其中做一些耗时操作,在doInBackground方法中也可以调用publishProgress方法来更新UI(之后会调用到onProgressUpdate方法)。不知道大家有没有好奇过,为什么onPreExecute,onProgressUpdate,onPostExecute方法会运行在主线程呢?为什么doInBackground方法会运行在子线程呢?子线程调用publishProgress之后为什么能进而调用到主线程的onProgressUpdate?AsyncTask的使用真的有想象中那么安全吗?

不用多想,上面的几个问题,只有源码君才能告诉我们答案,所以,今天就带领大家从底层源码的角度深入剖析AsyncTask的内部实现机制,让大家对AsyncTask的工作原理有一个透彻的理解,大家是不是有点小期待了呢_

随着Android版本的变迁,AsyncTask在任务执行方面有着较大的差异。当一开始推出时,诸多任务是在一个单个的后台线程上串行执行的。从Android 1.6(API 4)开始,任务是在一个线程池中并发执行的。从Android3.0(API 11)开始,任务又变为在一个单个的线程上串行执行。本篇文章基于Android 4.1.2的源码进行分析。

首先来看AsyncTask的构造方法,代码如下:

 /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     */
    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                return postResult(doInBackground(mParams));
            }
        };

        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 occured while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

AsyncTask的构造方法中做的事情不多,就是初始化了两个成员变量,一个是WorkerRunnable类型的mWorker(WorkerRunnable实现了Callable接口),一个是FutureTask类型的mFuture,并且将mWorker作为构造参数传入mFuture中(对Callable,Future,FutureTask不太了解的同学请先移步至http://www.cnblogs.com/dolphin0520/p/3949310.html

接下来我们去看AsyncTask的execute方法:

   /**
     * Executes the task with the specified parameters. The task returns
     * itself (this) so that the caller can keep a reference to it.
     * 
     * <p>Note: this function schedules the task on a queue for a single background
     * thread or pool of threads depending on the platform version.  When first
     * introduced, AsyncTasks were executed serially on a single background thread.
     * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
     * to a pool of threads allowing multiple tasks to operate in parallel. Starting
     * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being
     * executed on a single thread to avoid common application errors caused
     * by parallel execution.  If you truly want parallel execution, you can use
     * the {@link #executeOnExecutor} version of this method
     * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings
     * on its use.
     *
     * <p>This method must be invoked on the UI thread.
     *
     * @param params The parameters of the task.
     *
     * @return This instance of AsyncTask.
     *
     * @throws IllegalStateException If {@link #getStatus()} returns either
     *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
     *
     * @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
     * @see #execute(Runnable)
     */
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

execute方法竟然如此简单,仅仅是去调用了executeOnExecutor方法,我们赶紧去看一下executeOnExecutor方法:

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

在executeOnExecutor中,会先去判断当前AsyncTask的状态,如果不为PENDING,则抛异常。如果是PENDING,则将当前状态变为RUNNING,再去调用onPreExecute方法,由于此时的executeOnExecutor是运行在主线程的,所以onPreExecute方法也是运行在主线程的。之后再去对mWorker的mParams字段进行赋值并利用exec去执行mFuture。

我们基本可以推断出,任务执行的逻辑应该是在** exec.execute(mFuture)中,观察一下exec所对应的实参,是一个名为sDefaultExecutor**的Executor。这个sDefaultExecutor具体又是个什么样的Executor呢?看下面两行代码:


    /**
     * An {@link Executor} that executes tasks one at a time in serial
     * order.  This serialization is global to a particular process.
     */
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

我们发现,sDefaultExecutor其实就是一个SerialExecutor类型的常量,根据注释,SerialExecutor在执行任务时,会以一个串行的顺序,一次仅执行一个任务。由于SERIAL_EXECUTOR是一个常量,所以在一个特定的进程之内,串行化是具有全局效果的。

我们赶紧去看一下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);
            }
        }
    }

首先,SerialExecutor中有两个很重要的成员变量,一个是ArrayDeque<Runnable>类型的mTasks,它是一个双端队列,存储了我们要执行的任务。还有一个是Runnable类型的mActive,初始值为null。

接下来就到了最为关键的execute方法了。execute方法接收的参数是一个Runnable对象,到这里,有的同学可能感觉到有点奇怪,在executeOnExecutor方法中,我们的Executor的execute方法接收的是一个FutureTask的对象,为什么两个execute方法接收的参数不匹配呢?其实是这样的,FutureTask本身就实现了RunnableFuture接口,RunnableFuture接口又继承了Runnable, Future接口,所以,FutureTask完全可以当作一个Runnable来用。我们继续去看SerialExecutor的execute方法,首先,新建了一个Runnable任务,在其run方法中有一个try,finally结构,try中直接去调用了传入的Runnable对象的run方法,finally中调用了scheduleNext方法。之后将这个新建立的Runnable任务放入双端队列的尾部。接下来会去判断mActive这个变量是否为null,若为null,执行scheduleNext方法。第一个任务到来时mActive肯定是null,所以肯定会去执行scheduleNext方法。我们再去看一下scheduleNext方法,它会从双端队列的队头取出一个元素,赋给mActive变量,如果此时的mActive变量不为null,则利用线程池THREAD_POOL_EXECUTOR来执行这个任务。

想象一下,如果在第一个任务执行的过程中,又来了第二个任务,会发生什么事情呢?首先,依然是对我们传入的Runnable任务进行重新封装,入队,之后会再去判断mActive是否为null,此时,mActive是不为null的,所以不会再去执行scheduleNext方法。那我们第二个任务就永远得不到执行了吗?其实不是的,我们回到之前的try,finally结构,我们发现,当try中的任务逻辑执行完成之后,会在finally中调用scheduleNext方法,也就是说,当我们第一个任务执行完成之后,会再去调用scheduleNext方法,在scheduleNext方法中,会从双端队列中取出第二个任务,交给线程池去执行,由此,任务的执行变成串行化了。

我们再回过头看一下SerialExecutor的execute方法,看到r.run()这一句。我们知道,execute方法的参数表面上是一个Runnable对象,实际上我们传递给它的是一个FutureTask对象,那么r.run()自然也是执行的FutureTask对象的run方法。FutureTask对象的run方法会去调用Sync内部类的innerRun方法,我们来看一下Sync内部类的innerRun方法:

void innerRun() {  
    if (!compareAndSetState(READY, RUNNING))  
        return;  
    runner = Thread.currentThread();  
    if (getState() == RUNNING) { // recheck after setting thread  
        V result;  
        try {  
            result = callable.call();  
        } catch (Throwable ex) {  
            setException(ex);  
            return;  
        }  
        set(result);  
    } else {  
        releaseShared(0); // cancel  
    }  
}  

在Sync内部类的innerRun方法中,会去调用callable的call方法。这个callable是什么呢?其实就是我们在AsyncTask构造方法中初始化的mWorker变量,我们再回顾一下mWorker的初始化代码:

mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                return postResult(doInBackground(mParams));
            }
        };

注意,此时的call方法是在子线程运行的。我们看到return postResult(doInBackground(mParams))这一句,终于,我们发现了doInBackground方法,由于当前的call方法是在子线程运行的,所以doInBackground方法也是在子线程运行的。

我们继续去看一下postResult方法:

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

大家有没有感觉到这段代码很熟悉呢?没错,这就是我们Android的异步消息处理机制。首先,利用sHandler去获取一条消息,消息的what字段是MESSAGE_POST_RESULT,消息的obj字段是new AsyncTaskResult<Result>(this, result),AsyncTaskResult中封装了当前的AsyncTask任务以及需要传递的数据。之后调用message的sendToTarget方法将这条消息发送给sHandler。

我们看下sHandler的定义:

private static final InternalHandler sHandler = new InternalHandler();
private static class InternalHandler extends Handler {
        @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;
            }
        }
    }

可以看到,sHandler是一个InternalHandler类型的常量。由于我们的AsyncTask只能在主线程初始化,所以sHandler在初始化时用的是主线程的Looper,其handleMessage方法自然也是运行在主线程的。在handleMessage方法中,首先取出消息的obj字段并强转为AsyncTaskResult类型,之后会去判断消息的what字段,如果是MESSAGE_POST_RESULT,则执行result.mTask.finish(result.mData[0])。其中result.mTask代表当前的AsyncTask对象,result.mData[0]代表doInBackground(mParams)的执行结果,我们继续去看一下AsyncTask的finish方法:

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

finish方法的逻辑很简单,如果我们的任务已经通过调用cancel(boolean)方法取消了,那么会去执行onCancelled(result)方法,否则执行onPostExecute(result)方法。最后,会将当前的AsyncTask对象的状态置为FINISHED。由于之前的handleMessage方法是运行在主线程的,所以finish方法也是运行在主线程的,finish方法中的 onCancelled(result),onPostExecute(result)方法自然也是运行在主线程的。

在handleMessage中,还有一种what字段为MESSAGE_POST_PROGRESS的消息,那么什么时候会收到这种类型的消息呢,猜一下也知道,应该是我们调用publishProgress的时候。我们去看一下publishProgress的源代码:

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

当任务尚未取消时,会向sHandler发送一条what字段为MESSAGE_POST_PROGRESS,obj字段为new AsyncTaskResult<Progress>(this, values)的消息。回到handleMessage方法,因为handleMessage方法是运行在主线程中的,所以onProgressUpdate方法也是运行在主线程中的。

好了,AsyncTask的源码到这里已经基本分析完毕了,下面向大家介绍的是AsyncTask使用的缺陷问题。

可能有同学会想,AsyncTask这么牛X的一个工具,能有啥缺陷?
其实,AsyncTask还真的有缺陷,而且,这个缺陷和Android的版本息息相关,在某些Android版本下,如果AsyncTask使用不慎,甚至有可能造成我们的应用程序崩溃。
前面我们曾经提及,从Android 1.6(API 4)开始,任务是在一个线程池中并发执行的。从Android3.0(API 11)开始,任务又变为在一个单个的线程上串行执行。问题就出在这个并发执行上,这个并发执行所使用的线程池,最大支持128个任务的并发,10个任务的等待。也就是说,同时执行138个任务是没问题的,但是同时执行139个任务则会抛出异常:java.util.concurrent.RejectedExecutionException。而在Android 1.6之下,Android3.0及其之上的版本中,所有的任务都是串行执行的,同时执行再多的任务都不会有问题。

看到这里,相信大家已经对AsyncTask的底层原理有了一个较为深入的理解了,想不到小小的AsyncTask的内部竟然隐藏着如此美妙的天地,着实值得我们去探索与回味啊~~~

参考:
http://blog.csdn.net/lmj623565791/article/details/38614699
http://blog.csdn.net/guolin_blog/article/details/11711405
http://www.cnblogs.com/dolphin0520/p/3949310.html

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

推荐阅读更多精彩内容