AsyncTask源碼深度解析

相信有很多同學(xué)都使用過AsyncTask,都知道onPreExecute,onProgressUpdate,onPostExecute方法均運行在主線程腮出,doInBackground方法運行在子線程,可以在其中做一些耗時操作,在doInBackground方法中也可以調(diào)用publishProgress方法來更新UI(之后會調(diào)用到onProgressUpdate方法)芙沥。不知道大家有沒有好奇過,為什么onPreExecute,onProgressUpdate,onPostExecute方法會運行在主線程呢?為什么doInBackground方法會運行在子線程呢?子線程調(diào)用publishProgress之后為什么能進(jìn)而調(diào)用到主線程的onProgressUpdate宿亡?AsyncTask的使用真的有想象中那么安全嗎?

不用多想纳令,上面的幾個問題挽荠,只有源碼君才能告訴我們答案,所以平绩,今天就帶領(lǐng)大家從底層源碼的角度深入剖析AsyncTask的內(nèi)部實現(xiàn)機制圈匆,讓大家對AsyncTask的工作原理有一個透徹的理解,大家是不是有點小期待了呢_

隨著Android版本的變遷捏雌,AsyncTask在任務(wù)執(zhí)行方面有著較大的差異跃赚。當(dāng)一開始推出時,諸多任務(wù)是在一個單個的后臺線程上串行執(zhí)行的性湿。從Android 1.6(API 4)開始,任務(wù)是在一個線程池中并發(fā)執(zhí)行的纬傲。從Android3.0(API 11)開始,任務(wù)又變?yōu)樵谝粋€單個的線程上串行執(zhí)行窘奏。本篇文章基于Android 4.1.2的源碼進(jìn)行分析嘹锁。

首先來看AsyncTask的構(gòu)造方法葫录,代碼如下:

 /**
     * 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的構(gòu)造方法中做的事情不多着裹,就是初始化了兩個成員變量,一個是WorkerRunnable類型的mWorker(WorkerRunnable實現(xiàn)了Callable接口),一個是FutureTask類型的mFuture米同,并且將mWorker作為構(gòu)造參數(shù)傳入mFuture中(對Callable,Future,FutureTask不太了解的同學(xué)請先移步至http://www.cnblogs.com/dolphin0520/p/3949310.html骇扇。

接下來我們?nèi)タ碅syncTask的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方法竟然如此簡單,僅僅是去調(diào)用了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中少孝,會先去判斷當(dāng)前AsyncTask的狀態(tài),如果不為PENDING熬苍,則拋異常稍走。如果是PENDING袁翁,則將當(dāng)前狀態(tài)變?yōu)镽UNNING,再去調(diào)用onPreExecute方法婿脸,由于此時的executeOnExecutor是運行在主線程的,所以onPreExecute方法也是運行在主線程的粱胜。之后再去對mWorker的mParams字段進(jìn)行賦值并利用exec去執(zhí)行mFuture。

我們基本可以推斷出狐树,任務(wù)執(zhí)行的邏輯應(yīng)該是在** exec.execute(mFuture)中焙压,觀察一下exec所對應(yīng)的實參,是一個名為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;

我們發(fā)現(xiàn),sDefaultExecutor其實就是一個SerialExecutor類型的常量在塔,根據(jù)注釋幻件,SerialExecutor在執(zhí)行任務(wù)時,會以一個串行的順序蛔溃,一次僅執(zhí)行一個任務(wù)傲武。由于SERIAL_EXECUTOR是一個常量,所以在一個特定的進(jìn)程之內(nèi)城榛,串行化是具有全局效果的揪利。

我們趕緊去看一下SerialExecutor的源代碼,重點關(guān)注它是如何將任務(wù)的執(zhí)行變成串行化的:

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疟位,它是一個雙端隊列,存儲了我們要執(zhí)行的任務(wù)喘垂。還有一個是Runnable類型的mActive甜刻,初始值為null。

接下來就到了最為關(guān)鍵的execute方法了正勒。execute方法接收的參數(shù)是一個Runnable對象得院,到這里,有的同學(xué)可能感覺到有點奇怪章贞,在executeOnExecutor方法中祥绞,我們的Executor的execute方法接收的是一個FutureTask的對象,為什么兩個execute方法接收的參數(shù)不匹配呢鸭限?其實是這樣的蜕径,F(xiàn)utureTask本身就實現(xiàn)了RunnableFuture接口,RunnableFuture接口又繼承了Runnable, Future接口败京,所以兜喻,F(xiàn)utureTask完全可以當(dāng)作一個Runnable來用。我們繼續(xù)去看SerialExecutor的execute方法赡麦,首先朴皆,新建了一個Runnable任務(wù)帕识,在其run方法中有一個try,finally結(jié)構(gòu),try中直接去調(diào)用了傳入的Runnable對象的run方法,finally中調(diào)用了scheduleNext方法遂铡。之后將這個新建立的Runnable任務(wù)放入雙端隊列的尾部渡冻。接下來會去判斷mActive這個變量是否為null,若為null,執(zhí)行scheduleNext方法忧便。第一個任務(wù)到來時mActive肯定是null族吻,所以肯定會去執(zhí)行scheduleNext方法。我們再去看一下scheduleNext方法珠增,它會從雙端隊列的隊頭取出一個元素超歌,賦給mActive變量,如果此時的mActive變量不為null蒂教,則利用線程池THREAD_POOL_EXECUTOR來執(zhí)行這個任務(wù)巍举。

想象一下,如果在第一個任務(wù)執(zhí)行的過程中凝垛,又來了第二個任務(wù)懊悯,會發(fā)生什么事情呢?首先梦皮,依然是對我們傳入的Runnable任務(wù)進(jìn)行重新封裝炭分,入隊,之后會再去判斷mActive是否為null,此時剑肯,mActive是不為null的捧毛,所以不會再去執(zhí)行scheduleNext方法。那我們第二個任務(wù)就永遠(yuǎn)得不到執(zhí)行了嗎让网?其實不是的呀忧,我們回到之前的try,finally結(jié)構(gòu),我們發(fā)現(xiàn)溃睹,當(dāng)try中的任務(wù)邏輯執(zhí)行完成之后而账,會在finally中調(diào)用scheduleNext方法,也就是說因篇,當(dāng)我們第一個任務(wù)執(zhí)行完成之后泞辐,會再去調(diào)用scheduleNext方法,在scheduleNext方法中惜犀,會從雙端隊列中取出第二個任務(wù)铛碑,交給線程池去執(zhí)行狠裹,由此虽界,任務(wù)的執(zhí)行變成串行化了。

我們再回過頭看一下SerialExecutor的execute方法涛菠,看到r.run()這一句莉御。我們知道撇吞,execute方法的參數(shù)表面上是一個Runnable對象,實際上我們傳遞給它的是一個FutureTask對象礁叔,那么r.run()自然也是執(zhí)行的FutureTask對象的run方法牍颈。FutureTask對象的run方法會去調(diào)用Sync內(nèi)部類的innerRun方法,我們來看一下Sync內(nèi)部類的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內(nèi)部類的innerRun方法中琅关,會去調(diào)用callable的call方法煮岁。這個callable是什么呢?其實就是我們在AsyncTask構(gòu)造方法中初始化的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))這一句新症,終于步氏,我們發(fā)現(xiàn)了doInBackground方法,由于當(dāng)前的call方法是在子線程運行的徒爹,所以doInBackground方法也是在子線程運行的荚醒。

我們繼續(xù)去看一下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中封裝了當(dāng)前的AsyncTask任務(wù)以及需要傳遞的數(shù)據(jù)禀晓。之后調(diào)用message的sendToTarget方法將這條消息發(fā)送給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字段并強轉(zhuǎn)為AsyncTaskResult類型,之后會去判斷消息的what字段帽芽,如果是MESSAGE_POST_RESULT删掀,則執(zhí)行result.mTask.finish(result.mData[0])。其中result.mTask代表當(dāng)前的AsyncTask對象导街,result.mData[0]代表doInBackground(mParams)的執(zhí)行結(jié)果披泪,我們繼續(xù)去看一下AsyncTask的finish方法:

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

finish方法的邏輯很簡單,如果我們的任務(wù)已經(jīng)通過調(diào)用cancel(boolean)方法取消了搬瑰,那么會去執(zhí)行onCancelled(result)方法款票,否則執(zhí)行onPostExecute(result)方法控硼。最后,會將當(dāng)前的AsyncTask對象的狀態(tài)置為FINISHED艾少。由于之前的handleMessage方法是運行在主線程的卡乾,所以finish方法也是運行在主線程的,finish方法中的 onCancelled(result)缚够,onPostExecute(result)方法自然也是運行在主線程的幔妨。

在handleMessage中,還有一種what字段為MESSAGE_POST_PROGRESS的消息谍椅,那么什么時候會收到這種類型的消息呢陶冷,猜一下也知道,應(yīng)該是我們調(diào)用publishProgress的時候毯辅。我們?nèi)タ匆幌聀ublishProgress的源代碼:

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

當(dāng)任務(wù)尚未取消時埂伦,會向sHandler發(fā)送一條what字段為MESSAGE_POST_PROGRESS,obj字段為new AsyncTaskResult<Progress>(this, values)的消息思恐≌疵眨回到handleMessage方法,因為handleMessage方法是運行在主線程中的胀莹,所以onProgressUpdate方法也是運行在主線程中的基跑。

好了,AsyncTask的源碼到這里已經(jīng)基本分析完畢了描焰,下面向大家介紹的是AsyncTask使用的缺陷問題媳否。

可能有同學(xué)會想,AsyncTask這么牛X的一個工具荆秦,能有啥缺陷篱竭?
其實,AsyncTask還真的有缺陷步绸,而且掺逼,這個缺陷和Android的版本息息相關(guān),在某些Android版本下瓤介,如果AsyncTask使用不慎吕喘,甚至有可能造成我們的應(yīng)用程序崩潰。
前面我們曾經(jīng)提及刑桑,從Android 1.6(API 4)開始,任務(wù)是在一個線程池中并發(fā)執(zhí)行的氯质。從Android3.0(API 11)開始,任務(wù)又變?yōu)樵谝粋€單個的線程上串行執(zhí)行祠斧。問題就出在這個并發(fā)執(zhí)行上闻察,這個并發(fā)執(zhí)行所使用的線程池,最大支持128個任務(wù)的并發(fā),10個任務(wù)的等待蜓陌。也就是說觅彰,同時執(zhí)行138個任務(wù)是沒問題的吩蔑,但是同時執(zhí)行139個任務(wù)則會拋出異常:java.util.concurrent.RejectedExecutionException钮热。而在Android 1.6之下,Android3.0及其之上的版本中烛芬,所有的任務(wù)都是串行執(zhí)行的隧期,同時執(zhí)行再多的任務(wù)都不會有問題。

看到這里赘娄,相信大家已經(jīng)對AsyncTask的底層原理有了一個較為深入的理解了,想不到小小的AsyncTask的內(nèi)部竟然隱藏著如此美妙的天地仆潮,著實值得我們?nèi)ヌ剿髋c回味啊~~~

參考:
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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市遣臼,隨后出現(xiàn)的幾起案子性置,更是在濱河造成了極大的恐慌,老刑警劉巖揍堰,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鹏浅,死亡現(xiàn)場離奇詭異,居然都是意外死亡屏歹,警方通過查閱死者的電腦和手機隐砸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蝙眶,“玉大人季希,你說我怎么就攤上這事∮姆祝” “怎么了式塌?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長友浸。 經(jīng)常有香客問我珊搀,道長,這世上最難降的妖魔是什么尾菇? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任境析,我火速辦了婚禮,結(jié)果婚禮上派诬,老公的妹妹穿的比我還像新娘劳淆。我一直安慰自己,他們只是感情好默赂,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布沛鸵。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪曲掰。 梳的紋絲不亂的頭發(fā)上疾捍,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音栏妖,去河邊找鬼乱豆。 笑死,一個胖子當(dāng)著我的面吹牛吊趾,可吹牛的內(nèi)容都是我干的宛裕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼论泛,長吁一口氣:“原來是場噩夢啊……” “哼揩尸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起屁奏,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤岩榆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后坟瓢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勇边,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年载绿,在試婚紗的時候發(fā)現(xiàn)自己被綠了粥诫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡崭庸,死狀恐怖怀浆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怕享,我是刑警寧澤执赡,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站函筋,受9級特大地震影響沙合,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜跌帐,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一首懈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谨敛,春花似錦究履、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽藐俺。三九已至,卻和暖如春泥彤,著一層夾襖步出監(jiān)牢的瞬間欲芹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工吟吝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留菱父,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓爸黄,卻偏偏與公主長得像滞伟,于是被迫代替她去往敵國和親揭鳞。 傳聞我的和親對象是個殘疾皇子炕贵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容