[Android] AsyncTask-耗時工作設計

Android應用程式中若想設計網路連線的程式功能,除了Java原本就有的Thread、Executor等類別外,可使用AsyncTask類別

UI執行緒與耗時工作

應用程式在手機上執行時,主要負責與使用者互動,在執行並與使用者互動的過程,是以一個專屬的執行緒進行,這個執行緒稱為「UI thread」或「main thread」,如下圖,假設橫向箭頭是UI執行緒,從開始到應用程式結束,由同一個執行緒負責與使用者互動:

但如果應用程式中設計執行較耗費時間的工作,如網路連線、儲存大量資料,或必須等待回應的工作,在執行耗時工作的當下,若由UI thread負責處理工作的執行時,應用程式上的元件就無法與使用者互動。此時會發生元件不動的凍結狀態,稱之為「ANR,Application Not Response」應用程式無法回應。這類情形經常出在Android 2.3之前的應用程式設計,因此,Android系統後來硬性規定在UI thread中禁止撰寫如網路連線相關的程式碼,就是為了要避免這類問題發生,ANR的發生情境如下圖:

耗時工作處理-AsyncTask類別

在Android應用程式中若想設計網路連線的程式功能,除了Java原本就有的ThreadExecutor等類別外,可使用AsyncTask類別,在字義上,Async是非同步「Asynchronized」的簡寫,它允許開發人員設計能在背景執行的工作,並提供方法能夠與UI thread溝通互動,適合進行較短時間(數秒)的耗時工作,它的運作如下圖:

上圖在執行過程中啟動一個AsyncTask進行耗時工作,而原本的UI thread(橫向箭頭)仍然在耗時工作執行過程中可以讓元件與使用者互動。

設計AsyncTask

1. 設計AsyncTask的子類別並訂定規範

設計一個類別並繼承android.os.AsyncTask,類別設計完成後才能使用,類別可以是內部類別(在某Activity內)或獨立的類別。通常使用AsyncTask是因為它能夠與UI元件互通,因此大都以內部類別方式設計,如下:

public class XX {
   class TestTask extends AsyncTask<Void, Void, Void>{
      …
   }
}

使用extends語法繼承AsyncTask之後的Java一般化(Generic)語法必需定義這個類別的傳入值型態、更新進度資料型態與回傳值型態,如下:

AsyncTask<傳入值型態, 更新進度型態, 結果型態>

  • 傳入值型態

代表本工作是否需要傳入資料,若需要,則要先定義資料的型態,此處只能使用參照資料型態,如IntegerStringBoolean等,若不需要任何傳入值可使用Void(大寫的V)類別。

  • 更新進度型態

在背景工作執行過程中的更新(或回報)資料的型態,例如下載工作的進度可使用Integer(35%)或Float(52.3%)這類回報資料的型態,若不需要更新資料則使用Void類別。

  • 結果(回傳)型態

背景工作完成後回傳的結果資料型態,例如下載工作完成後回傳檔名可使用String類別,或是回傳檔案的大小則使用Long類別,若不需要回傳資料則使用Void類別。

類別定義完成後,該類別內的方法中使用的傳入參數與回傳值還必須與類別義相符合,例如接下來必須實作的doInBackground方法。

2. 將工作寫在doInBackground方法中

AsyncTask是抽象類別,因此繼承它的子類別都必須實作其抽象方法,也就是唯一的doInBackground方法,字面上即是在背景工作的方法。實作方法只需要將游標停在類別定義行,按下「Alt+Enter」,如下圖:

如上圖的「Implement methods」實作方法,按下Enter後會出現方法選擇對話框,點擊實作doInBackground方法或選擇後按下Enter,如下圖:

Android Studio會自動在類別中實作方法,如下圖:

因為第33行定義了傳入值與回傳值都是Void,因此自動產生的doInBackground方法的規格也符合規範,開發人員可在此方法中設計耗時工作的程式碼。到目前為止,最簡單的AsyncTask設計完成了。而doInBackground方法的參數使用的是Java 1.5開始提供的陣列參數語法,在編譯時會轉換為陣列,第36行最後參數型態(與AsyncTask的傳入值型態配合)若是字串時,params[0]即代表陣列的第一個字串值,如下。

@Override
protected Void doInBackground(String… params){
	Log.d("PARAM", params[0]);
	…
}

3. 產生類別並呼叫execute()

接著可依照需求,在程式碼中呼叫TestTask內部類別的建構子產生物件,再呼叫execute()方法,即可產生另一個執行緒,並在背景執行doInBackground方法內的程式碼,如下:

new TestTask().execute();

如果後續需要再使用這個物件,也可以將物件儲存後再呼叫execute方法,如下:

TestTask task = new TestTask();
task.execute();

因為TestTask已定義傳入值型態為Void,因此呼叫execute方法可以不用給予參數,若是有定義傳入值型態,則exectute方法內就應該給予值入值。

提示: 呼叫execute方法後,AsyncTask會自動執行必要的方法後自動執行doInBackground方法,而不是由開發人員直接去呼叫doInBackground方法,這樣不會以背景執行緒執行。

與UI thread互通的方法

一個AsyncTask的執行流程有四個階段,四個執行的方法分別是onPreExecute、doInBackground、onProgressUpdate與onPostExecute,除了doInBackground方法之外,AsyncTask類別定義了另外三個方法能夠與UI Thread或UI元件互動,依其用途說明如下:

1. onPreExecute-之前

在背景工作執行之前會自動執行的方法,開發人員可覆寫onPreExecute,在方法內撰寫程式碼。

2. onProgressUpdate-過程

在下載過程中可以手動呼叫的方法,在doInBackground中呼叫「publishProgress方法」會自動執行onProgressUpdate方法內的程式碼。

3. onPostExecute-之後

在背景工作執行完成後會自動執行的方法,開發人員可覆寫onPostExecute,在方法內撰寫工作完成後必要的程式碼。

提示: 在類別中按下覆寫方法快速鍵「Ctrl+O」可快速選擇欲覆寫的方法,如下圖

除了doInBackground,三個方法內都能與UI Thread或元件互動,例如在方法中以findViewById方法取得元件後,再設定元件屬性,如下圖與箭頭所示:

初步瞭解AsyncTask的設計後,別忙了練習,可參考接續的文章:
[Android] AsyncTask-實作練習

 

JavaEE 企業Web程式設計 教你清楚瞭解企業專案程式開發