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 呼叫

官方範例:
  1.  
  2. private class DownloadFilesTask extends AsyncTask<URL, Integer, Long>
  3. {
  4. // 對照前面提到的 3 個傳入的參數
  5. // URL 就是 Params 參數的類別
  6. // Integer 就是 Progress 參數的類別
  7. // Long 就是 Result 參數的類別
  8. protected Long doInBackground(URL... urls)
  9. {
  10. int count = urls.length;
  11. long totalSize = 0;
  12. for (int i = 0; i < count; i++)
  13. {
  14. totalSize += Downloader.downloadFile(urls[i]);
  15. // 呼叫 publishProgress() 以更新 UI 畫面,
  16. // 可藉由此方式更新畫面上的進度表
  17. publishProgress((int) ((i / (float) count) * 100));
  18. // Escape early if cancel() is called
  19. if (isCancelled())
  20. break;
  21. }
  22. // 將 totalSize 傳給 onPostExecute()
  23. return totalSize;
  24. }
  25.  
  26. protected void onProgressUpdate(Integer... progress)
  27. {
  28. // 這裡接收傳入的 progress 值, 並更新進度表畫面
  29. // 參數是 Integer 型態的陣列
  30. // 但是因為在 doInBackground() 只傳一個參數
  31. // 所以以 progress[0] 取得傳入參數
  32. setProgressPercent(progress[0]);
  33. }
  34.  
  35. protected void onPostExecute(Long result)
  36. {
  37. showDialog("Downloaded " + result + " bytes");
  38. }
  39.  
根據上面的範例,我們要建立一個名為 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() 內的程式碼不會執行,如下:
  1.  
  2. doInBackground(Void...arg)
  3. {
  4. ...
  5. ...
  6. return null;
  7. }
  8.  
  9. // 不會執行
  10. onPostExecute(Void arg)
  11. {
  12. ...
  13. ...
  14. }
  15.  

關於 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 主執行緒呼叫.

    回覆刪除