こんにちは、中山です。
Android用の通信ライブラリである「Volley」。
ウェブで検索すると、いろいろな解説記事が見つかります。
複雑になってしまいがちな通信関連処理が、簡単に実装できるようになるこの「Volley」ライブラリ。
もちろん、通信のタイムアウトや、通信失敗時のリトライ回数なども、簡単に設定することができます。
しかしこのリトライ回数を増やしたとき、ちょっと想像していなかった挙動になりましたので、紹介してみたいと思います。
まずはVolleyライブラリが動作するサンプルアプリケーションを作成します。
今回は、Android Studioから「MyVolleySample」という新規プロジェクトを作成しました。
「build.gradle」ファイルを開いて、以下のようにVolleyライブラリの設定を追加します。(dependenciesブロック内に「compile 'com.mcxiaoke.volley:library:1.0+'」を追加します。)
※「com.mcxiaoke.volley」は、非公式のVolleyのミラーリポジトリではありますが、今回は手順簡略化のため、こちらを利用しています。
正式な導入方法はこちらなどの情報をご覧ください。
またアプリからのインターネット通信を可能とするため、AndroidManifest.xmlにパーミッションを追加します。
Volleyライブラリの機能は、こちらの情報にあるとおり、Singletonクラスとして提供することとします。
今回のサンプルアプリでは、「MyVolleySingleton」というクラスを作成しました。
import android.content.Context; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.toolbox.Volley; public class MyVolleySingleton { private final Context mCtx; private final RequestQueue mRequestQueue; private static MyVolleySingleton mInstance = null; private MyVolleySingleton(Context context) { mCtx = context; // getApplicationContext() is key, it keeps you from leaking the // Activity or BroadcastReceiver if someone passes one in. mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext()); } public static synchronized MyVolleySingleton getInstance(Context context) { if (mInstance == null) { mInstance = new MyVolleySingleton(context); } return mInstance; } public <T> void addToRequestQueue(Request<T> req) { mRequestQueue.add(req); } }
また今回は、シンプルな、文字列としてレスポンスを受け取るためのStringRequestクラスを使って、リクエストを実行するようにします。
準備はだいたい整いつつあるのですが、肝心の、タイムアウトとリトライの設定は、いったいどこで行えばよいのでしょうか?
この答えは、今回利用するStringRequestクラスのスーパークラスである、「Request」クラスのコンストラクタ内にあります。
public Request(int method, String url, Response.ErrorListener listener) { mMethod = method; mUrl = url; mErrorListener = listener; setRetryPolicy(new DefaultRetryPolicy()); // タイムアウト、リトライの設定 mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url); }
上記のように、「setRetryPolicy」メソッドを呼び出して設定しているようです。
この例では、setRetryPolicyメソッドのパラメータに「DefaultRetryPolicy」クラスの、パラメータ無しコンストラクタを指定しています。これはつまり、タイムアウト、リトライ回数は、このDefaultRetryPolicyクラスに定義されている初期値をそのまま使う、ということになるようです。
DefaultRetryPolicyクラスの中身を見てみますと、それぞれの初期値は以下のように、
/** The default socket timeout in milliseconds */ public static final int DEFAULT_TIMEOUT_MS = 2500; /** The default number of retries */ public static final int DEFAULT_MAX_RETRIES = 1;
タイムアウト初期値は2500ミリ秒(=2.5秒)、リトライ回数初期値は1回(=最初に通信失敗した後、1回だけリトライする)に設定されているようです。
これらの値を変更する場合は、次のようにパラメータ付きのDefaultRetryPolicyクラスのコンストラクタを用いて、
setRetryPolicy(new DefaultRetryPolicy(5000, 3, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
のようにします。
この例では、タイムアウトを5000ミリ秒(=5秒)、リトライ回数を3回に設定しています。(※コンストラクタの3番目のパラメータは、タイムアウト値に適用する係数です。今回は初期値のまま(=1)としています。)
今回は、(いろいろやりかたはあるとは思いますが)StringRequestクラスを継承した「MyStringRequest」クラスを新たに作成して、そのコンストラクタで、「setRetryPolicy」メソッドを呼び出し、タイムアウト、リトライ回数を設定するようにしました。
MyStringRequestクラスは以下のようになりました。
import com.android.volley.DefaultRetryPolicy; import com.android.volley.Response; import com.android.volley.toolbox.StringRequest; public class MyStringRequest extends StringRequest { // タイムアウト値(5000ミリ秒) private static final int TIMEOUT_MS = 5000; // リトライ回数(初回リクエスト失敗後、3回リトライ) private static final int MAX_RETRIES = 3; public MyStringRequest(int method, String url, Response.Listener<String> listener, Response.ErrorListener errorListener) { super(method, url, listener, errorListener); // タイムアウト値、リトライ回数をリトライポリシーに設定する setRetryPolicy(new DefaultRetryPolicy(TIMEOUT_MS, MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); } }
ここまでの実装を踏まえまして、Volleyライブラリを用いた通信処理の呼び出しは、以下のようになります。
@Override protected void onResume() { super.onResume(); // リクエスト先のURL(エミュレータから見たホストマシンのローカルホストのPHPファイルを指定) String url ="http://10.0.2.2/delay.php"; // リクエストオブジェクトのインスタンスを生成 MyStringRequest stringRequest = new MyStringRequest(Request.Method.GET, url, new Response.Listener<String>() { @Override public void onResponse(String response) { // 正常にリクエストが完了した場合、そのレスポンスをToast表示(先頭20文字のみ) Toast.makeText(MainActivity.this, "Response is: " + response.substring(0, 20), Toast.LENGTH_SHORT).show(); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // リクエストが失敗した場合 Log.i("MyVolleySample", "Failed."); Toast.makeText(MainActivity.this, "That didn't work!", Toast.LENGTH_SHORT).show(); } } ); // リクエストキューにリクエストを追加(=リクエスト実行) MyVolleySingleton.getInstance(this).addToRequestQueue(stringRequest); }
リクエスト先のサーバモジュール(PHPモジュール)では、タイムアウトやリトライを試せるように、レスポンスを返すまでに60秒かかるように設定しました。
この状態でリクエストを実行したとき、サーバ側のアクセスログには以下のように出力されていました。
127.0.0.1 - - [25/May/2015:17:13:10 +0900] "GET /delay.php HTTP/1.1" 200 41 127.0.0.1 - - [25/May/2015:17:13:15 +0900] "GET /delay.php HTTP/1.1" 200 41 127.0.0.1 - - [25/May/2015:17:13:25 +0900] "GET /delay.php HTTP/1.1" 200 41 127.0.0.1 - - [25/May/2015:17:13:45 +0900] "GET /delay.php HTTP/1.1" 200 41
また、アプリ側のLogCatには以下のような出力がありました。
05-25 17:14:25.811: I/MyVolleySample(2475): Failed.
ここでちょっとおもしろい結果が見られました。処理の流れを時間ごとに追ってみますと、
13分10秒 初回リクエスト。
(5秒後)
13分15秒 初回リクエストタイムアウト。1回目のリトライ。
(10秒後)
13分25秒 1回目のリトライリクエストタイムアウト。2回目のリトライ。
(20秒後)
13分45秒 2回目のリトライリクエストタイムアウト。3回目のリトライ。
(40秒後)
14分25秒 3回目のリトライリクエストタイムアウト。通信失敗がアプリに通知される。
このように、リトライのたびに、タイムアウト間隔が倍、倍になっていっているのです。
これは、VolleyライブラリのDefaultRetryPolicyクラスの「retry」メソッドの中で、リトライが実施されるたびに、タイムアウト値を2倍しているためのようです。
※正確には、タイムアウト値(=mCurrentTimeoutMsフィールド)に係数(=mBackoffMultiplierフィールド。この場合、値は1です)を掛けたものを元のタイムアウト値に加算しているようです。
public void retry(VolleyError error) throws VolleyError { mCurrentRetryCount++; mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier); if (!hasAttemptRemaining()) { throw error; } }
したがいまして、リトライ回数を多く設定したときに、一緒にタイムアウト値も大きく設定してしまうと、どんどんタイムアウト時間が長くなってしまい、いつまでたってもレスポンスが返ってこない、ということにもなりかねません。この点は、少し注意が必要かもしれませんね。
以上、便利なVolleyライブラリの、知っておくとちょっと役に立つ情報でした。