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原本就有的Thread、Executor等類別外,可使用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<傳入值型態, 更新進度型態, 結果型態>
- 傳入值型態
代表本工作是否需要傳入資料,若需要,則要先定義資料的型態,此處只能使用參照資料型態,如Integer、String、Boolean等,若不需要任何傳入值可使用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方法內就應該給予值入值。
與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-實作練習