なので、今回はそれを簡単に実装できる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]);
}
}
あ、マニフェストの書き忘れ注意!


