Android AsyncTask徹底解析,帶你從源碼的角度完全理解

轉載請註明出處:http://blog.csdn.net/guolin_blog/article/details/11711405android

咱們都知道,Android UI是線程不安全的,若是想要在子線程裏進行UI操做,就須要藉助Android的異步消息處理機制。以前我也寫過了一篇文章從源碼層面分析了Android的異步消息處理機制,感興趣的朋友能夠參考 Android Handler、Message徹底解析,帶你從源碼的角度完全理解 。安全

不過爲了更加方便咱們在子線程中更新UI元素,Android從1.5版本就引入了一個AsyncTask類,使用它就能夠很是靈活方便地從子線程切換到UI線程,咱們本篇文章的主角也就正是它了。異步

AsyncTask很早就出如今Android的API裏了,因此我相信大多數朋友對它的用法都已經很是熟悉。不過今天我仍是準備從AsyncTask的基本用法開始講起,而後咱們再來一塊兒分析下AsyncTask源碼,看看它是如何實現的,最後我會介紹一些關於AsyncTask你所不知道的祕密。ide

AsyncTask的基本用法函數


首先來看一下AsyncTask的基本用法,因爲AsyncTask是一個抽象類,因此若是咱們想使用它,就必需要建立一個子類去繼承它。在繼承時咱們能夠爲AsyncTask類指定三個泛型參數,這三個參數的用途以下:post

1. Paramsthis

在執行AsyncTask時須要傳入的參數,可用於在後臺任務中使用。.net

2. Progress線程

後臺任務執行時,若是須要在界面上顯示當前的進度,則使用這裏指定的泛型做爲進度單位。對象

3. Result

當任務執行完畢後,若是須要對結果進行返回,則使用這裏指定的泛型做爲返回值類型。

所以,一個最簡單的自定義AsyncTask就能夠寫成以下方式:

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
    ……
}
這裏咱們把AsyncTask的第一個泛型參數指定爲Void,表示在執行AsyncTask的時候不須要傳入參數給後臺任務。第二個泛型參數指定爲Integer,表示使用整型數據來做爲進度顯示單位。第三個泛型參數指定爲Boolean,則表示使用布爾型數據來反饋執行結果。

固然,目前咱們自定義的DownloadTask仍是一個空任務,並不能進行任何實際的操做,咱們還須要去重寫AsyncTask中的幾個方法才能完成對任務的定製。常常須要去重寫的方法有如下四個:

1. onPreExecute()

這個方法會在後臺任務開始執行之間調用,用於進行一些界面上的初始化操做,好比顯示一個進度條對話框等。

2. doInBackground(Params...)

這個方法中的全部代碼都會在子線程中運行,咱們應該在這裏去處理全部的耗時任務。任務一旦完成就能夠經過return語句來將任務的執行結果進行返回,若是AsyncTask的第三個泛型參數指定的是Void,就能夠不返回任務執行結果。注意,在這個方法中是不能夠進行UI操做的,若是須要更新UI元素,好比說反饋當前任務的執行進度,能夠調用publishProgress(Progress...)方法來完成。

3. onProgressUpdate(Progress...)

當在後臺任務中調用了publishProgress(Progress...)方法後,這個方法就很快會被調用,方法中攜帶的參數就是在後臺任務中傳遞過來的。在這個方法中能夠對UI進行操做,利用參數中的數值就能夠對界面元素進行相應的更新。

4. onPostExecute(Result)

當後臺任務執行完畢並經過return語句進行返回時,這個方法就很快會被調用。返回的數據會做爲參數傳遞到此方法中,能夠利用返回的數據來進行一些UI操做,好比說提醒任務執行的結果,以及關閉掉進度條對話框等。

所以,一個比較完整的自定義AsyncTask就能夠寫成以下方式:

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
 
    @Override
    protected void onPreExecute() {
        progressDialog.show();
    }
 
    @Override
    protected Boolean doInBackground(Void... params) {
        try {
            while (true) {
                int downloadPercent = doDownload();
                publishProgress(downloadPercent);
                if (downloadPercent >= 100) {
                    break;
                }
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }
 
    @Override
    protected void onProgressUpdate(Integer... values) {
        progressDialog.setMessage("當前下載進度:" + values[0] + "%");
    }
 
    @Override
    protected void onPostExecute(Boolean result) {
        progressDialog.dismiss();
        if (result) {
            Toast.makeText(context, "下載成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(context, "下載失敗", Toast.LENGTH_SHORT).show();
        }
    }
}
這裏咱們模擬了一個下載任務,在doInBackground()方法中去執行具體的下載邏輯,在onProgressUpdate()方法中顯示當前的下載進度,在onPostExecute()方法中來提示任務的執行結果。若是想要啓動這個任務,只須要簡單地調用如下代碼便可:
new DownloadTask().execute();
以上就是AsyncTask的基本用法,怎麼樣,是否是感受在子線程和UI線程之間進行切換變得靈活了不少?咱們並不需求去考慮什麼異步消息處理機制,也不須要專門使用一個Handler來發送和接收消息,只須要調用一下publishProgress()方法就能夠輕鬆地從子線程切換到UI線程了。

分析AsyncTask的源碼


雖然AsyncTask這麼簡單好用,但你知道它是怎樣實現的嗎?那麼接下來,咱們就來分析一下AsyncTask的源碼,對它的實現原理一探究竟。注意這裏我選用的是Android 4.0的源碼,若是你查看的是其它版本的源碼,可能會有一些出入。

從以前DownloadTask的代碼就能夠看出,在啓動某一個任務以前,要先new出它的實例,所以,咱們就先來看一看AsyncTask構造函數中的源碼,以下所示:

public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            return postResult(doInBackground(mParams));
        }
    };
    mFuture = new FutureTask<Result>(mWorker) {
        @Override
        protected void done() {
            try {
                final Result result = get();
                postResultIfNotInvoked(result);
            } 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);
            } catch (Throwable t) {
                throw new RuntimeException("An error occured while executing "
                        + "doInBackground()", t);
            }
        }
    };
}
這段代碼雖然看起來有點長,但實際上並無任何具體的邏輯會獲得執行,只是初始化了兩個變量,mWorker和mFuture,並在初始化mFuture的時候將mWorker做爲參數傳入。mWorker是一個Callable對象,mFuture是一個FutureTask對象,這兩個變量會暫時保存在內存中,稍後纔會用到它們。


接着若是想要啓動某一個任務,就須要調用該任務的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;
}
果真,這裏的代碼看上去才正常點。能夠看到,在第15行調用了onPreExecute()方法,所以證實了onPreExecute()方法會第一個獲得執行。但是接下來的代碼就看不明白了,怎麼沒見到哪裏有調用doInBackground()方法呢?彆着急,慢慢找總會找到的,咱們看到,在第17行調用了Executor的execute()方法,並將前面初始化的mFuture對象傳了進去,那麼這個Executor對象又是什麼呢?查看上面的execute()方法,原來是傳入了一個sDefaultExecutor變量,接着找一下這個sDefaultExecutor變量是在哪裏定義的,源碼以下所示:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
……
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
能夠看到,這裏先new出了一個SERIAL_EXECUTOR常量,而後將sDefaultExecutor的值賦值爲這個常量,也就是說明,剛纔在executeOnExecutor()方法中調用的execute()方法,其實也就是調用的SerialExecutor類中的execute()方法。那麼咱們天然要去看看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類中也有一個execute()方法,這個方法裏的全部邏輯就是在子線程中執行的了,注意這個方法有一個Runnable參數,那麼目前這個參數的值是什麼呢?固然就是mFuture對象了,也就是說在第9行咱們要調用的是FutureTask類的run()方法,而在這個方法裏又會去調用Sync內部類的innerRun()方法,所以咱們直接來看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
    }
}
能夠看到,在第8行調用了callable的call()方法,那麼這個callable對象是什麼呢?其實就是在初始化mFuture對象時傳入的mWorker對象了,此時調用的call()方法,也就是一開始在AsyncTask的構造函數中指定的,咱們把它單獨拿出來看一下,代碼以下所示:
public Result call() throws Exception {
    mTaskInvoked.set(true);
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    return postResult(doInBackground(mParams));
}
在postResult()方法的參數裏面,咱們終於找到了doInBackground()方法的調用處,雖然通過了不少週轉,但目前的代碼仍然是運行在子線程當中的,因此這也就是爲何咱們能夠在doInBackground()方法中去處理耗時的邏輯。接着將doInBackground()方法返回的結果傳遞給了postResult()方法,這個方法的源碼以下所示:
private Result postResult(Result result) {
    Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}
若是你已經熟悉了異步消息處理機制,這段代碼對你來講必定很是簡單吧。這裏使用sHandler對象發出了一條消息,消息中攜帶了MESSAGE_POST_RESULT常量和一個表示任務執行結果的AsyncTaskResult對象。這個sHandler對象是InternalHandler類的一個實例,那麼稍後這條消息確定會在InternalHandler的handleMessage()方法中被處理。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;
        }
    }
}
這裏對消息的類型進行了判斷,若是這是一條MESSAGE_POST_RESULT消息,就會去執行finish()方法,若是這是一條MESSAGE_POST_PROGRESS消息,就會去執行onProgressUpdate()方法。那麼finish()方法的源碼以下所示:
private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}
能夠看到,若是當前任務被取消掉了,就會調用onCancelled()方法,若是沒有被取消,則調用onPostExecute()方法,這樣當前任務的執行就所有結束了。


咱們注意到,在剛纔InternalHandler的handleMessage()方法裏,還有一種MESSAGE_POST_PROGRESS的消息類型,這種消息是用於當前進度的,調用的正是onProgressUpdate()方法,那麼何時纔會發出這樣一條消息呢?相信你已經猜到了,查看publishProgress()方法的源碼,以下所示:

protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
        sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
}
很是清晰了吧!正因如此,在doInBackground()方法中調用publishProgress()方法才能夠從子線程切換到UI線程,從而完成對UI元素的更新操做。其實也沒有什麼神祕的,由於說到底,AsyncTask也是使用的異步消息處理機制,只是作了很是好的封裝而已。

讀到這裏,相信你對AsyncTask中的每一個回調方法的做用、原理、以及什麼時候會被調用都已經搞明白了吧。

關於AsyncTask你所不知道的祕密


不得不說,剛纔咱們在分析SerialExecutor的時候,其實並無分析的很仔細,僅僅只是關注了它會調用mFuture中的run()方法,可是至於何時會調用咱們並無進一步地研究。其實SerialExecutor也是AsyncTask在3.0版本之後作了最主要的修改的地方,它在AsyncTask中是以常量的形式被使用的,所以在整個應用程序中的全部AsyncTask實例都會共用同一個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對象的,若是咱們一次性啓動了不少個任務,首先在第一次運行execute()方法的時候,會調用ArrayDeque的offer()方法將傳入的Runnable對象添加到隊列的尾部,而後判斷mActive對象是否是等於null,第一次運行固然是等於null了,因而會調用scheduleNext()方法。在這個方法中會從隊列的頭部取值,並賦值給mActive對象,而後調用THREAD_POOL_EXECUTOR去執行取出的取出的Runnable對象。以後如何又有新的任務被執行,一樣還會調用offer()方法將傳入的Runnable添加到隊列的尾部,可是再去給mActive對象作非空檢查的時候就會發現mActive對象已經再也不是null了,因而就不會再調用scheduleNext()方法。

那麼後面添加的任務豈不是永遠得不處處理了?固然不是,看一看offer()方法裏傳入的Runnable匿名類,這裏使用了一個try finally代碼塊,並在finally中調用了scheduleNext()方法,保證不管發生什麼狀況,這個方法都會被調用。也就是說,每次當一個任務執行完畢後,下一個任務纔會獲得執行,SerialExecutor模仿的是單一線程池的效果,若是咱們快速地啓動了不少任務,同一時刻只會有一個線程正在執行,其他的均處於等待狀態。Android照片牆應用實現,再多的圖片也不怕崩潰 這篇文章中例子的運行結果也證明了這個結論。

不過你可能還不知道,在Android 3.0以前是並無SerialExecutor這個類的,那個時候是直接在AsyncTask中構建了一個sExecutor常量,並對線程池總大小,同一時刻可以運行的線程數作了規定,代碼以下所示:

private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 10;
……
private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
        MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
能夠看到,這裏規定同一時刻可以運行的線程數爲5個,線程池總大小爲128。也就是說當咱們啓動了10個任務時,只有5個任務可以馬上執行,另外的5個任務則須要等待,當有一個任務執行完畢後,第6個任務纔會啓動,以此類推。而線程池中最大能存放的線程數是128個,當咱們嘗試去添加第129個任務時,程序就會崩潰。


所以在3.0版本中AsyncTask的改動仍是挺大的,在3.0以前的AsyncTask能夠同時有5個任務在執行,而3.0以後的AsyncTask同時只能有1個任務在執行。爲何升級以後能夠同時執行的任務數反而變少了呢?這是由於更新後的AsyncTask已變得更加靈活,若是不想使用默認的線程池,還能夠自由地進行配置。好比使用以下的代碼來啓動任務:

Executor exec = new ThreadPoolExecutor(15, 200, 10,         TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); new DownloadTask().executeOnExecutor(exec); 這樣就可使用咱們自定義的一個Executor來執行任務,而不是使用SerialExecutor。上述代碼的效果容許在同一時刻有15個任務正在執行,而且最多可以存儲200個任務。

相關文章
相關標籤/搜索