2013-01-15

【Android】AsyncTask - Thread 外的另一選擇

參考資料 ----
http://developer.android.com/reference/android/os/AsyncTask.html


AsyncTask (API level 3,所以幾乎所有目前在市面上流通的 Android 版本皆可使用)
是除 Thread 外的另一種選擇,Android 團隊鼓勵主執行緒(UI thread) 專注於操作 & 畫面的流暢呈現,其餘工作 (如網路資料傳輸、檔案/磁碟/資料存取) 最好都在背景執行;Thread 通常要搭配 Handler 使用,而 AsyncTask 用意在簡化背景執行 Thread 程式碼的撰寫。

如果您預期要執行的工作能在幾秒內完成,就可以選擇使用 AsyncTask,若執行的時間很長,Android 則強烈建議採用 Executor, ThreadPoolExecutorFutureTask

要使用 AsyncTask,必定要建立一個繼承自 AsyncTask 的子類別,並傳入 3 項資料:

Params -- 要執行 doInBackground() 時傳入的參數,數量可以不止一個
Progress -- doInBackground() 執行過程中回傳給 UI thread 的資料,數量可以不止一個
Rsesult -- 傳回執行結果, 若您沒有參數要傳入,則填入 Void (注意 V 為大寫)。

AsyncTask 的運作有 4 個階段:
onPreExecute -- AsyncTask 執行前的準備工作,例如畫面上顯示進度表,
doInBackground -- 實際要執行的程式碼就是寫在這裡,
onProgressUpdate -- 用來顯示目前的進度,
onPostExecute -- 執行完的結果 - Result 會傳入這裡。

除了 doInBackground,其他 3 個 method 都是在 UI thread 呼叫

官方範例:
 
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long>
{
    // 對照前面提到的 3 個傳入的參數
    // URL 就是 Params 參數的類別
    // Integer 就是 Progress 參數的類別
    // Long 就是 Result 參數的類別
    protected Long doInBackground(URL... urls)
    {
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++)
        {
            totalSize += Downloader.downloadFile(urls[i]);
            // 呼叫 publishProgress() 以更新 UI 畫面,
            // 可藉由此方式更新畫面上的進度表
            publishProgress((int) ((i / (float) count) * 100));
            // Escape early if cancel() is called
            if (isCancelled())
                break;
        }
        // 將 totalSize 傳給 onPostExecute()
        return totalSize;
    }

    protected void onProgressUpdate(Integer... progress)
    {
        // 這裡接收傳入的 progress 值, 並更新進度表畫面
        // 參數是 Integer 型態的陣列
        // 但是因為在 doInBackground() 只傳一個參數
        // 所以以 progress[0] 取得傳入參數
        setProgressPercent(progress[0]);
    }

    protected void onPostExecute(Long result)
    {
        showDialog("Downloaded " + result + " bytes");
    }
}
 
根據上面的範例,我們要建立一個名為 DownloadFilesTask 的類別,做為下載檔案用, URL 就是將來會傳入 doInBackground 的變數型態,再看 doInBackground() 這個 method 傳入參數寫法

doInBackgound(URL... urls)


表示傳入的 url 可以不止 1 個。


一旦建立好類別,要執行的方法很簡單:

new DownloadFilesTask().execute(url1, url2, url3); 

DownloadFilesTask() 便會經由 execute() 呼叫 doInBackground(),執行下載 url1, url2, url3 這 3 個檔案,而 doInBackground() 在處理過程中, 透過呼叫 publishProgress() 來傳送資料給 onProgressUpdate()onProgressUpdate() 更新畫面上的進度表(如果您有在您的 app UI 顯示進度表的話),doInBackground() 執行完畢後,會將結果傳給 onPostExecute()

注意,若您有工作要放在 onPostExecute() 處理,則 doInBackground() 必定要 return 非 null 的參數,否則 onPostExecute() 內的程式碼不會執行,如下:
 
doInBackground(Void...arg)
{
    ...
    ...
    return null;
}

// 不會執行
onPostExecute(Void arg)
{
    ...
    ...
}
 

關於 AsyncTask 的使用,有幾項原則必須遵守:
* AsyncTask 必須在 UI 主執行緒載入(JELLY_BEAN 版本開始會自動執行此事)。 
* 必須在 UI 主執行緒建立 AsyncTask。  
* 必須在 UI 主執行緒呼叫 AsyncTask.execute()。  
* 不要自行呼叫 onPreExecute(),onPostExecute(),doInBackground(), onProgressUpdate()。  
* AsyncTask 只能執行一次。

4 則留言:

  1. 寫得相當清楚
    得到許多幫助

    Thanks

    回覆刪除
  2. This article is very good for me,believe also can hope every need guys. thanks again.

    回覆刪除
  3. 請問, 何謂AsyncTask只能執行一次? 不能執行兩次以上嗎?還是有方法可以re-new呢?

    回覆刪除
  4. 官網就是這麼警示的, 我想意思應該是 AsyncTask 不能自己呼叫自己, 重覆執行吧.

    在我自己的程式, 通常是 AsyncTask 結束時通知 UI 主執行緒, 有需要時, 再由 UI 主執行緒呼叫.

    回覆刪除