詳解Android中AsyncTask的使用

發表於2015-04-10

在Android中實現非同步任務機制有兩種方式,Handler和AsyncTask。

Handler模式需要為每一個任務建立一個新的執行緒,任務完成後通過Handler例項向UI執行緒傳送訊息,完成介面的更新,這種方式對於整個過程的控制比較精細,但也是有缺點的,例如程式碼相對臃腫,在多個任務同時執行時,不易對執行緒進行精確的控制。關於Handler的相關知識,前面也有所介紹,不清楚的朋友們可以參照一下。

為了簡化操作,Android1.5提供了工具類android.os.AsyncTask,它使建立非同步任務變得更加簡單,不再需要編寫任務執行緒和Handler例項即可完成相同的任務。

先來看看AsyncTask的定義:

三種泛型型別分別代表“啟動任務執行的輸入引數”、“後臺任務執行的進度”、“後臺計算結果的型別”。在特定場合下,並不是所有型別都被使用,如果沒有被使用,可以用java.lang.Void型別代替。

一個非同步任務的執行一般包括以下幾個步驟:

1.execute(Params… params),執行一個非同步任務,需要我們在程式碼中呼叫此方法,觸發非同步任務的執行。

2.onPreExecute(),在execute(Params… params)被呼叫後立即執行,一般用來在執行後臺任務前對UI做一些標記。

3.doInBackground(Params… params),在onPreExecute()完成後立即執行,用於執行較為費時的操作,此方法將接收輸入引數和返回計算結果。在執行過程中可以呼叫publishProgress(Progress… values)來更新進度資訊。

4.onProgressUpdate(Progress… values),在呼叫publishProgress(Progress… values)時,此方法被執行,直接將進度資訊更新到UI元件上。

5.onPostExecute(Result result),當後臺操作結束時,此方法將會被呼叫,計算結果將做為引數傳遞到此方法中,直接將結果顯示到UI元件上。

在使用的時候,有幾點需要格外注意:

1.非同步任務的例項必須在UI執行緒中建立。

2.execute(Params… params)方法必須在UI執行緒中呼叫。

3.不要手動呼叫onPreExecute(),doInBackground(Params… params),onProgressUpdate(Progress… values),onPostExecute(Result result)這幾個方法。

4.不能在doInBackground(Params… params)中更改UI元件的資訊。

5.一個任務例項只能執行一次,如果執行第二次將會丟擲異常。

接下來,我們來看看如何使用AsyncTask執行非同步任務操作,我們先建立一個專案,結構如下:

結構相對簡單一些,讓我們先看看MainActivity.java的程式碼:

佈局檔案main.xml程式碼如下:

因為需要訪問網路,所以我們還需要在AndroidManifest.xml中加入訪問網路的許可權:

我們來看一下執行時的介面:

以上幾個截圖分別是初始介面、執行非同步任務時介面、執行成功後介面、取消任務後介面。執行成功後,整個過程日誌列印如下:

如果我們在執行任務時按下了“cancel”按鈕,日誌列印如下:

可以看到onCancelled()方法將會被呼叫,onPostExecute(Result result)方法將不再被呼叫。

上面介紹了AsyncTask的基本應用,有些朋友也許會有疑惑,AsyncTask內部是怎麼執行的呢,它執行的過程跟我們使用Handler又有什麼區別呢?答案是:AsyncTask是對Thread+Handler良好的封裝,在android.os.AsyncTask程式碼裡仍然可以看到Thread和Handler的蹤跡。下面就向大家詳細介紹一下AsyncTask的執行原理。

我們先看一下AsyncTask的大綱檢視:

我們可以看到關鍵幾個步驟的方法都在其中,doInBackground(Params… params)是一個抽象方法,我們繼承AsyncTask時必須覆寫此方法;onPreExecute()、onProgressUpdate(Progress… values)、onPostExecute(Result result)、onCancelled()這幾個方法體都是空的,我們需要的時候可以選擇性的覆寫它們;publishProgress(Progress… values)是final修飾的,不能覆寫,只能去呼叫,我們一般會在doInBackground(Params… params)中呼叫此方法;另外,我們可以看到有一個Status的列舉類和getStatus()方法,Status列舉類程式碼段如下:

可以看到,AsyncTask的初始狀態為PENDING,代表待定狀態,RUNNING代表執行狀態,FINISHED代表結束狀態,這幾種狀態在AsyncTask一次生命週期內的很多地方被使用,非常重要。

介紹完大綱檢視相關內容之後,接下來,我們會從execute(Params… params)作為入口,重點分析一下AsyncTask的執行流程,我們來看一下execute(Params… params)方法的程式碼段:

程式碼中涉及到三個陌生的變數:mWorker、sExecutor、mFuture,我們也會看一下他們的廬山真面目:

關於sExecutor,它是java.util.concurrent.ThreadPoolExecutor的例項,用於管理執行緒的執行。程式碼如下:

mWorker實際上是AsyncTask的一個的抽象內部類的實現物件例項,它實現了Callable<Result>介面中的call()方法,程式碼如下:

而mFuture實際上是java.util.concurrent.FutureTask的例項,下面是它的FutureTask類的相關資訊:

可以看到FutureTask是一個可以中途取消的用於非同步計算的類。

下面是mWorker和mFuture例項在AsyncTask中的體現:

我們看到上面的程式碼中,mFuture例項物件的done()方法中,如果捕捉到了CancellationException型別的異常,則傳送一條“MESSAGE_POST_CANCEL”的訊息;如果順利執行,則傳送一條“MESSAGE_POST_RESULT”的訊息,而訊息都與一個sHandler物件關聯。這個sHandler例項實際上是AsyncTask內部類InternalHandler的例項,而InternalHandler正是繼承了Handler,下面我們來分析一下它的程式碼:

我們看到,在處理訊息時,遇到“MESSAGE_POST_RESULT”時,它會呼叫AsyncTask中的finish()方法,我們來看一下finish()方法的定義:

原來finish()方法是負責呼叫onPostExecute(Result result)方法顯示結果並改變任務狀態的啊。

另外,在mFuture物件的done()方法裡,構建一個訊息時,這個訊息包含了一個AsyncTaskResult型別的物件,然後在sHandler例項物件的handleMessage(Message msg)方法裡,使用下面這種方式取得訊息中附帶的物件:

這個AsyncTaskResult究竟是什麼呢,它又包含什麼內容呢?其實它也是AsyncTask的一個內部類,是用來包裝執行結果的一個類,讓我們來看一下它的程式碼結構:

看以看到這個AsyncTaskResult封裝了一個AsyncTask的例項和某種型別的資料集,我們再來看一下構建訊息時的程式碼:

在處理訊息時是如何使用這個物件呢,我們再來看一下:

概括來說,當我們呼叫execute(Params… params)方法後,execute方法會呼叫onPreExecute()方法,然後由ThreadPoolExecutor例項sExecutor執行一個FutureTask任務,這個過程中doInBackground(Params… params)將被呼叫,如果被開發者覆寫的doInBackground(Params… params)方法中呼叫了publishProgress(Progress… values)方法,則通過InternalHandler例項sHandler傳送一條MESSAGE_POST_PROGRESS訊息,更新進度,sHandler處理訊息時onProgressUpdate(Progress… values)方法將被呼叫;如果遇到異常,則傳送一條MESSAGE_POST_CANCEL的訊息,取消任務,sHandler處理訊息時onCancelled()方法將被呼叫;如果執行成功,則傳送一條MESSAGE_POST_RESULT的訊息,顯示結果,sHandler處理訊息時onPostExecute(Result result)方法被呼叫。

經過上面的介紹,相信朋友們都已經認識到AsyncTask的本質了,它對Thread+Handler的良好封裝,減少了開發者處理問題的複雜度,提高了開發效率,希望朋友們能多多體會一下。

相關文章