Androidの非同期処理
では、WeatherInfoActivityに非同期でお天気情報を取得する処理を追加していきましょう。
非同期処理3ステップ
Androidで非同期処理を行うには、先述の通りAsyncTaskを使用します。使い方は以下の3ステップです。
- AsyncTaskを継承したクラスを作成。
- 上記クラス内のdoInBackground()メソッドに非同期で行いたい処理を記述。
- Activityクラス内でこのクラスをnewし、execute()メソッドを実行する。
このステップに従って、WeatherInfoActivityにソースコードを追記していきましょう。
1)AsyncTaskを継承したクラスを作成
まず、ステップ1)です。WeatherInfoActivityに以下のメンバクラスを追加してください。
public class WeatherInfoActivity extends AppCompatActivity {
~省略~
private class WeatherInfoReceiver extends AsyncTask<String, String, String> { // (1)
@Override
public String doInBackground(String... params) { // (2)
}
@Override
public void onPostExecute(String result) { // (3)
}
}
}
AsyncTaskを継承したクラスですが、トップレベルクラスとして作成してもいいですが、WeatherInfoActivityでしか使用しないクラスなので、メンバクラスとしてWeatherInfoReceiverを作成しています(リスト1の(1))。
その際、AsyncTaskにジェネリクスを3個指定する必要があります。これは、AsyncTaskを継承したクラス内のメソッドの引数や戻り値の型を指定するためです。それぞれの役割は以下の通りです。なお、()内はそれぞれのジェネリクス名です。
-
1個目(Params):execute()、doInBackground()の引数の型
- これらのメソッドは、可変長引数となっており、上記ステップ3)でexecute()メソッドを実行時に渡された引数が、そのままdoInBackground()の引数として横流しされるような仕組みになっています。
-
2個目(Progress):publishProgress()、onProgressUpdate()の引数の型
- これらのメソッドの使い方は後述しますが、publishProgress()実行時に渡された引数が、そのままonProgressUpdate()の引数として横流しされるような仕組みになっています。
-
3個目(Result):doInBackground()の戻り値の型、および、onPostExecute()、onCancelled()メソッドの引数の型
- こちらも後述しますが、doInBackground()の戻り値がそのままonPostExecute()、およびonCancelled()の引数としてわたります。
WeatherInfoReceiverでは
AsyncTask<String, String, String>
とすべてStringを指定していますので、doInBackground()の引数も戻り値もStringであり(リスト1の(2))、onPostExecute()の引数もStringです(リスト1の(3))。
2)doInBackground()に処理を記述
次に、ステップ2)です。doInBackground()メソッドに非同期で行いたい処理を記述します。なお、doInBackground()メソッドはAsyncTaskクラスでは抽象メソッドとして定義されていますので、必ず実装する必要があります。
このメソッド内で、インターネットにアクセスしてデータを取得する処理を記述します。
public String doInBackground(String... params) {
String id = params[0]; // (1)
String urlStr = "http://weather.livedoor.com/forecast/webservice/json/v1?city=" + id; // (2)
HttpURLConnection con = null; // (3)
InputStream is = null; // (3)
String result = ""; // (3)
try {
URL url = new URL(urlStr); // (4)
con = (HttpURLConnection) url.openConnection(); // (5)
con.setRequestMethod("GET"); // (6)
con.connect(); // (7)
is = con.getInputStream(); // (8)
result = is2String(is); // (9)
}
catch(MalformedURLException ex) {
}
catch(IOException ex) {
}
finally {
if(con != null) {
con.disconnect(); // (10)
}
if(is != null) {
try {
is.close(); // (11)
}
catch(IOException ex) {
}
}
}
return result; // (12)
}
インターネットにアクセスするには例外がつきものなので、例外処理を行って少しソースコードが見にくくなっていますが、上記ソースコードが定型の処理となっています。次にその解説を行います。
httpアクセス
インターネットにアクセスするにはAndroidでもhttpプロトコルを使うため、httpアクセスと呼ぶことができます。Androidでhttpアクセスを行うには、APIレベル9以前であればApache HttpClientを使用していました。現在ではHttpURLConnectionを使用します。使い方は以下の手順です。
url文字列からURLオブジェクトを作成します(リスト2の(4))。このurl文字列ですが、ライブドアの仕様に従ったURLをまず文字列として生成しています(リスト2の(2))。その際、都市IDが必要なので、これをdoInBackground()の引数として受け取ります(リスト2の(1))。
URLオブジェクトからHttpURLConnectionオブジェクトを取得します(リスト2の(5))。これは、URLオブジェクトのopenConnection()メソッドを使用します。ただ、このメソッドの戻り値の型は、URLConnectionなので、HttpURLConnectionにキャストする必要があります。
POSTかGETを設定します(リスト2の(6))。これは、HttpURLConnectionのsetRequestMethod()メソッドに引数として「POST」か「GET」の文字列を渡します。今回はGETなのでそのようにしています。
http接続を行います(リスト2の(7)))。これは、HttpURLConnectionのconnect()メソッドを使います。このメソッドを実行したときに、httpアクセスを行い、レスポンスデータの取得まで行います。したがって、メソッド実行後にはHttpURLConnectionオブジェクト内にレスポンスデータが格納されています。
この格納されたレスポンスデータを取得します(リスト2の(8))。ただ、これはInputStreamオブジェクトとして格納されているので、getInputStream()メソッドを実行して取得します。
HttpURLConnectionオブジェクトを解放します(リスト2の(10))。これは、disconnect()メソッドを使いますが、こういう接続関係のオブジェクトはリソースを必要としますので、確実に開放しておく必要があります。そのため、finallyブロック内で解放します。その際、未接続の状態でfinallyブロックが実行される可能性もありますので、あらかじめcon(HttpURLConnectionオブジェクト)のnullチェックを行っています。
あとは、取得したInputStreamオブジェクトを必要な形に変換して、必要データを取り出したり、ファイルとして保存したりします。今回はレスポンスデータがJSON文字列なので、InputStreamオブジェクトを文字列に変換する必要があります。それが、リスト2の(9)です。ここで呼び出しているis2String()はInputStreamオブジェクトを文字列に変換する定型処理を記述したprivateメソッドです。このメソッドのソースコードはダウンロードサンプルを参照してください。
なお、InputStreamオブジェクトも確実に解放する必要がありますので、finallyブロックでclose()を行っています(リスト2の(11))
また、このように、tryブロック内で取得したオブジェクトをtry外で解放などの処理を行うため、あらかじめtry外で宣言する必要があります。それが、リスト2の(3)です。
最後に、httpアクセスの結果、取得したJSON文字列(ここに天気情報が格納されている)をdoInBackground()の戻り値として返します(リスト2の(12))。
インターネット接続許可
これで、ひととおりhttpアクセスのソースコードが記述できたことになりますが、このままではhttpアクセスができません。Androidではアプリがインターネットにアクセスするには、その許可を与える必要があります。これは、AndroidManifest.xmlに以下の2行を記述することで可能となります。
<manifest …>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
~省略~
3)非同期処理の実行
最後に、ステップ3)です。Activityクラス内でこの非同期処理クラスをnewし、execute()メソッドを実行します。WeatherInfoActivityのonCreate()メソッドに以下のソースコードを追加してください。
protected void onCreate(Bundle savedInstanceState) {
~省略~
Intent intent = getIntent(); // (1)
String cityName = intent.getStringExtra("cityName"); // (1)
String cityId = intent.getStringExtra("cityId"); // (1)
WeatherInfoReceiver receiver = new WeatherInfoReceiver(); // (2)
receiver.execute(cityId); // (3)
}
非同期処理を実行する前に、まず都市リスト画面からの引継ぎデータを取得します(リスト4の(1))。その上で、非同期処理クラスであるWeatherInfoReceiverをnewし(リスト4の(2))、execute()メソッドを実行しています(リスト4の(3))。
