なので、今回はそれを簡単に実装できるAsyncTaskクラスを使って通信処理を書き直してみる。
AsyncTaskとは
「エイシンクタスク」って読むらしい。 このクラスはexecute()メソッドを実行することで、下記の順でメソッドが呼ばれる。メソッドごとに実行スレッドが違うのが特徴メソッド名 | 意味 | 使用例 |
---|---|---|
onPreExecute | 非同期処理する前に何かする場合に使用 | ダイアログで通信中を表示 |
doInBackground | 非同期処理 | 通信処理、DB処理、データ読み込み |
onProgressUpdate | 非同期処理中にUI更新する時使用 publishProgress()でこの処理を実行 | %とかバーで処理状態表示 |
onPostExecute | 非同期処理で得られた結果を元にUI更新 | ダイアログ消去 通信処理結果表示 |
使用上の注意
めっちゃ便利そうだけど、何でもかんでもAsyncTaskで平行処理するのはよくないUIが絡むとそれだけコストがかかる。このクラスは必ずUIスレッドを経由するため、UIスレッドを絡めない処理は無難にThreadクラスで処理した方が良い
UI関係の起こりやすく、発見しにくかったバグを2つ紹介する
- AsyncTaskを複数使用するときの処理
- Android2.3以前 execute()メソッドを実行する度に並列処理されている
- Android3.0以降 execute()メソッドを実行すると現在実行中のexecute()が終了してから処理が実行される
- 処理をキャンセルするときの処理 通信中画面遷移したりすると、通信キャンセルせざるおえない場合がある
- Android2.3以前 すぐAsyncTask #onCancelled()が呼ばれる
- Android3.0以降 doInBackground()が終了してからAsyncTask #onCancelled()が呼ばれる
キャンセル処理をAsyncTask #onCancelled()にオーバライドしておき、
AsyncTask #cansel()を実行することで、上記メソッドが呼ばれキャンセルできる仕組みだが、 非同期処理中 ( doInBackground() ) にキャンセルすると下記の2パターンに分岐する
画面遷移のため非同期処理をキャンセルする場合は、onProgressUpdate()にも注意。doInBackground()は途中で終わらないので、画面遷移後の存在しないView更新でヌルポしたりして痛い目にあうw
対処方はdoInBackground()処理にisCancelled()メソッドでこまめに今キャンセル中か確認すること!
実装方法
AsyncTask<Params、Progress、Result>で表される。ジェネリクスはそれぞれ下記の意味
型 | 意味 | 使用箇所 |
---|---|---|
Params | 非同期通信の引数になる。 | execute()の引数で渡す → doInBackground()の引数 |
Progress | 非同期処理中UIスレッド更新に渡す引数 | publishProgress()の引数で渡す → onProgressUpdate()の引数 |
Result | 非同期処理結果のクラス | doInBackground()の戻り値 → onPostExecute()の引数 |
- Params : リクエストするJSONのURL → String
- Progress : 使用しない → Void
- Result : レスポンスのJSONを文字列にしたもの → String
AsyncTask<String, Void, String> task = new AsyncTask<String, Void, String>() { @Override protected String doInBackground(String... params) { // TODO !非同期処理はアブストラクトメソッドなので必須! return null; } };で、あとは非同期処理中に通信処理。通信処理後にListViewの更新かける処理記述
AsyncTask<String, Void, String> task = new AsyncTask<String, Void, String>() { /**通信スレッド処理. * 引数のURLからリクエストし、レスポンスをJSONデータ文字列に変換 * @param url リスエスト先 * @return JSONデータの文字列 */ @Override protected String doInBackground(String... url) { return getAnimeInfo(url[0]); } /**UIスレッドの処理. * 引数のJSON文字列からタイトル配列を取得し、ListViewに表示 * @param JSON文字列 */ @Override protected void onPostExecute(String json) { mListView.setAdapter(new ArrayAdapter<String>( MainActivity.this, android.R.layout.simple_expandable_list_item_1, getTitleForJSON(json))); } };最後に、Activityでexecute()を呼べば完成!※行削減の為コメ消去
public class MainActivity extends Activity{ ListView mListView = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(mListView = new ListView(this)); task.execute("http://animemap.net/api/table/osaka.json"); } AsyncTask<String, Void, String> task = new AsyncTask<String, Void, String>() { @Override protected String doInBackground(String... url) { return getAnimeInfo(url[0]); } @Override protected void onPostExecute(String json) { mListView.setAdapter(new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_expandable_list_item_1,getTitleForJSON(json))); } }; public static String getAnimeInfo(String url) { AndroidHttpClient client = AndroidHttpClient.newInstance(null); HttpGet get = new HttpGet(url); StringBuilder sb = new StringBuilder(); try { HttpResponse response = client.execute(get); BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); String line = null; while ((line = br.readLine()) != null) sb.append(line); } catch (IOException e) { e.printStackTrace(); } finally { client.close(); } return sb.toString(); } public static String[] getTitleForJSON(String jsonInfo) { final ArrayList<String> tmp = new ArrayList<String>(); try { JSONArray json = new JSONObject(jsonInfo) .getJSONObject("response") .getJSONArray("item"); for (int i = 0; i < json.length(); i++) tmp.add(((JSONObject) json.get(i)).getString("title")); } catch (JSONException e){ e.printStackTrace(); } return tmp.toArray(new String[0]); } }あ、マニフェストの書き忘れ注意!