Android 源码分析 - AsyncTask

  不知道为什么,最近感觉喜欢上了分析源码了。哈哈,今天我们分析一下AsyncTask的源码,来了解一下这个异步类的原理。
  本文参考资料:
  1. Java线程(七):Callable和Future
  2.任玉刚老师的《Android 开发艺术探索》
  由于AsyncTask类的源码设计Callable、Futrue类,可能难于理解(其实我也不太懂!),大家可以参考:Java线程(七):Callable和Future

1.概述

  大家对于AsyncTask应该都不陌生,通常来说在异步加载的成功可能会使用到它,当然现在可能有很多的新东西可以来替代它。不过,我们来了解一下Google爸爸帮我们实现的功能,还是不错的。
  在AsyncTask中有4个核心方法,分别是:

1.onPreExecute():在异步任务执行之前,这个方法会被回调,一般在这个方法里面初始化一些东西。这个方法在主线程中调用。

2.doInBackground(Params...params):在子线程中调用,此方法用来执行异步任务。在这个里面可以通过调用publishProgress方法来更新任务的进度,publishProgress会调用onPressUpdate方法。此方法需要返回结果给onPostExecute方法。

3.onProngressUpdate(Progress...values):当异步任务的进度发生改变时,会被调用。

4.onPostExecute(Result result):在异步执行完成之后,此方法会被调用,其中result参数是异步任务返回的结果,即doInBackground的返回值。

  4个方法的执行顺序是:onPreExecute -> doInBackground ->onProngressUpdate ->onPostExecute。
  简单的说了一下AsyncTask几个方法的作用,接下来将会从源码的角度上来看看AsyncTask的工作原理。

2.AsyncTask的分析

  通常来说,我们在使用AsyncTask的时候,基本是如下的操作:

        new AsyncTaskImpl().execute(...);

  所以,我们先来AsyncTask的构造方法里面给我们创建了什么东西。

(1).构造方法

  不管是哪个构造方法,最终会调用到这里来:

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

  我们还是那样,逐一的分析,首先来看看,创建的Handler:

        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);

  这一句非常的简单就是给我们创建一个Handler,其中这个Handler带的Looper属于主线程。这里不对Handler作过多的解释,毕竟终点不是在这里。如果有不懂Handler的朋友,可以自行到网上去看看,你们在这里就把这个Handler当成我们在Activity中创建的一个Handler。
  然后我们在看看这一句:

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

  首先,这里的WorkerRunnable是实现了Callable。至于这里的Callable是什么东西,大家可以参考: Java线程(七):Callable和Future。这里的简单的解释一下,Callable跟Runnable非常的像,都是用于线程的,只不过,Runnable的执行的run方法并且没有返回值,Callable执行的是call方法,并且有返回值;同时Runnable执行直接用于Thread来执行,Callable需要使用Futrue来封装一下。具体的用法,大家可以上面的文章。
  
  不懂Callable接口的老哥也不用担心,这里我们只需要将它当成一个Runnable就行了,call方法就是线程需要执行的方法。我们在这个call方法看到了这一句:

                    result = doInBackground(mParams);

  从这一句我们就知道,当一个线程启动了,调用了call方法了,就会调用doInBackground方法,执行我们的任务了。
  最后再来看看Futrue的创建:

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

  可能又有老哥懵逼了,这特么又是什么东西。不急不急,我们分析一下FutrueTask是什么东西:

public class FutureTask<V> implements RunnableFuture<V>

  额,还是不是很懂,我们再来RunnableFutrue接口:

public interface RunnableFuture<V> extends Runnable, Future<V> 

  这里应该差不多了,FutureTask根本就是一个Runnable,当放在Thread里面的时候,它的run方法肯定会被回调。但是Future又是干嘛用的,Futrue主要用来标记我们可以利用FutureTask来获取线程执行的结果。但是我们知道run返回值是void, 怎么能从run方法里面获取结果呢?实际上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 = null;
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

  上面的代码是FutrueTask里面的run方法,我们看到它调用了Callable的call方法。这个就能理解,为什么之前的call方法里面调用doInBackground方法了。
  在FutrueTask的run方法里面还有这么一句代码:

                if (ran)
                    set(result);

  我们来看看set方法里面到底在干嘛了。

    protected void set(V v) {
        if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
            outcome = v;
            U.putOrderedInt(this, STATE, NORMAL); // final state
            finishCompletion();
        }
    }

  好嘛,又调用了finishCompletion方法,我们再来看看这个方法里面在干嘛:

    private void finishCompletion() {
        // assert state > COMPLETING;
        for (WaitNode q; (q = waiters) != null;) {
            if (U.compareAndSwapObject(this, WAITERS, q, null)) {
                for (;;) {
                    Thread t = q.thread;
                    if (t != null) {
                        q.thread = null;
                        LockSupport.unpark(t);
                    }
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }
        done();
        callable = null;        // to reduce footprint
    }

  这里需要注意这一句:

        done();

  哈哈,我们终于看到done方法了。也就是当在FutrueTask的run方法成功的获取执行的结果之后,随后会调用done方法。
  到这里,可能有老哥有点懵逼了。我来解释一下:
  1.首先,创建一个Callable的对象,在Callable对象的call方法里面执行了真正需要执行的任务。
  2.然后我们创建Runnable的一个实现类的对象,在这个类的run方法实际上调用了Callable的call方法来执行任务,然后将结果返回,最后调用done方法表示任务结束了。
  这里我画一个图来解释一下整个流程:



  AsyncTask的构造方法我们理解的差不多了,在这里,做一个简单的总结:

  1.创建了一个带主线程的LooperHandler。这里的作用很明显,就是进行线程的调度。
  2.创建Callable对象,并且在call方法里面调用,这个方法真正执行了任务,因为在call方法里面调用了doInBackground方法。
  3.创建了FutrueTask对象,在它的done方法里面调用了FutrueTask的get方法,来获取任务执行的结果。

  整个任务的传递流程在上面的已经解释的比较清楚了,如果还有不懂的老哥,建议大家看看源码,这样比较直观。

(2).execute方法

  在构造方法里面,已经为我们创建了三个对象供我们使用。我们需要执行任务,真正调用的execute方法。现在我们来看看execute方法:

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

  好嘛,调用了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;
    }

  这里我们需要注意地方。我们来一一分析:

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

  意思很明白,只要AsyncTask的状态不是PENDING,也就是预备状态,都会跑出异常。这个就能解释为什么AsyncTask只能使用一次。
  在这里,我们还看到了另一个核心方法:

        onPreExecute();

  由于这里还是在主线程,所以,我们就能理解onPreExecute在主线程执行。
  最后调用的是:

        exec.execute(mFuture);

  特么的,这个exec又是什么东西。我们还是看看调用这个构造方法到底传入的是什么参数。从execute方法里面,我们得到的信息是,exec就是sDefaultExecutor。这个sDefaultExecutor又是什么东西:

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

  这里,可能对Executor接口不熟悉。这里不需要深入的理解Executor 是什么东西,暂且将它当成一个线程池。所以,实际上sDefaultExecutor 是一个线程池。
  在这里线程池里面,我们需要注意的是,线程池持有一个ArrayDeque的队列,在execute方法里面我们可以看到,实际上将这个Runnable的入队了。
  同时,如果mActive 为null的话,会调用scheduleNext方法从队列里面去取任务来执行。

            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }

  这个THREAD_POOL_EXECUTOR又是什么,我们从这个变量的初始化阶段来看看:

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

  实际上,THREAD_POOL_EXECUTOR 也是一个线程池。而这里调用线程池的execute方法就是将任务提交到线程池里面去,让线程池来执行我们的任务。
  因此,我们从AsyncTask的executeOnExecutor方法里面可以得到信息。当用户提交一个任务过来时,实际上是让线程池来帮我们执行的。
  在这里,我来画一个图来解释整个的执行流程:



  这里还是做一个简单的总结:

  1.整个execute的流程中,有两个线程池,一个线程池使用了队列来存储任务,另一个线程池来执行任务。
  2.执行任务实际上调用的是Callable的call方方法,进而执行doInBackground方法
  3.当doInBackGround方法执行完毕之后,会调用done方法。

(3).onPostExecute方法

  核心方法我们已经看到了两个了,onPreExecute方法在executeOnExecutor方法中调用,doInBackground方法在Callable的call方法里面被调用,还有两个方法我们不知道。这里我们先来看看onPostExecute方法,因为onProgressUpdate方法需要我们在doInBackground方法里面调用publishProgress方法才会被调用,这里先不对其进行分析。
  我们知道,当一个任务执行完毕之后,会调用FutrueTask的done方法,我们再来看看done方法:

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

  最后我们在postResult方法里发现,它通过Handler将这个resutl发送出去了。由于这个操作会回调Handler里面去,我们来看看Handler:

    private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }

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

  我们发现,只要表示任务结束的话,会调用AsyncTask的finish方法:

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

  哈哈,我们终于知道onPostExecute的执行流程了。总结一下就是,当一个任务执行完毕之后,会通过Handler来进行线程的调度,切换到主线程,从而在主线程调用onPostExecute方法。

(4).onProgressUpdate方法

  最后,我们再来看看onProgressUpdate的方法的执行流程。由于onProgressUpdate执行时机是,在doInBackground方法调用了publishProgress方法,所以我们还是先来看看publishProgress方法:

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

  哎呀,简单到不能再简单了,就是通过Handler来发送Message。在这个之前,我们在Handler的handlerMessage方法里面看到:

                    result.mTask.onProgressUpdate(result.mData);

  这里就完成了onProgressUpdate的回调。

3.总结

  总的来说,AsyncTask还是比较简单的,最后对AsyncTask做一个简单的总结:

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

推荐阅读更多精彩内容