すでにバックグラウンドスレッドを持っているサービスにいると想像してください。同じスレッドでvolleyを使用してリクエストを行うことで、コールバックを同期的に実行できますか?
これには2つの理由があります。-最初に、別のスレッドは必要ありません。作成するのは無駄です。 -次に、ServiceIntentを使用している場合、コールバックの前にスレッドの実行が終了するため、Volleyからの応答がありません。制御可能なランループを備えたスレッドを持つ独自のサービスを作成できることは知っていますが、この機能をボレーで使用することが望ましいでしょう。
ありがとうございました!
VolleyのRequestFuture
クラスで可能だと思われます。たとえば、同期JSON HTTP GETリクエストを作成するには、次を実行できます。
RequestFuture<JSONObject> future = RequestFuture.newFuture();
JsonObjectRequest request = new JsonObjectRequest(URL, new JSONObject(), future, future);
requestQueue.add(request);
try {
JSONObject response = future.get(); // this will block
} catch (InterruptedException e) {
// exception handling
} catch (ExecutionException e) {
// exception handling
}
注:@Matthewsの答えは正しいですが、別のスレッドを使用していて、インターネットがないときにボレーコールを行うと、エラーコールバックはメインスレッドで呼び出されますが、現在のスレッドは永遠にブロックされます。(したがって、そのスレッドがIntentServiceの場合、別のメッセージを送信することはできず、サービスは基本的に停止します)。
タイムアウトget()
を持つfuture.get(30, TimeUnit.SECONDS)
のバージョンを使用し、エラーをキャッチしてスレッドを終了します。
@Mathewsの回答に一致させるには:
try {
return future.get(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// exception handling
} catch (ExecutionException e) {
// exception handling
} catch (TimeoutException e) {
// exception handling
}
以下にメソッドでラップし、別のリクエストを使用します。
/**
* Runs a blocking Volley request
*
* @param method get/put/post etc
* @param url endpoint
* @param errorListener handles errors
* @return the input stream result or exception: NOTE returns null once the onErrorResponse listener has been called
*/
public InputStream runInputStreamRequest(int method, String url, Response.ErrorListener errorListener) {
RequestFuture<InputStream> future = RequestFuture.newFuture();
InputStreamRequest request = new InputStreamRequest(method, url, future, errorListener);
getQueue().add(request);
try {
return future.get(REQUEST_TIMEOUT, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Log.e("Retrieve cards api call interrupted.", e);
errorListener.onErrorResponse(new VolleyError(e));
} catch (ExecutionException e) {
Log.e("Retrieve cards api call failed.", e);
errorListener.onErrorResponse(new VolleyError(e));
} catch (TimeoutException e) {
Log.e("Retrieve cards api call timed out.", e);
errorListener.onErrorResponse(new VolleyError(e));
}
return null;
}
Futureを使用することをお勧めしますが、何らかの理由で同期したくない場合は、Java.util.concurrent.CountDownLatch
を使用する必要があります。だからそれはこのように動作します。
//I'm running this in an instrumentation test, in real life you'd ofc obtain the context differently...
final Context context = InstrumentationRegistry.getTargetContext();
final RequestQueue queue = Volley.newRequestQueue(context);
final CountDownLatch countDownLatch = new CountDownLatch(1);
final Object[] responseHolder = new Object[1];
final StringRequest stringRequest = new StringRequest(Request.Method.GET, "http://google.com", new Response.Listener<String>() {
@Override
public void onResponse(String response) {
responseHolder[0] = response;
countDownLatch.countDown();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
responseHolder[0] = error;
countDownLatch.countDown();
}
});
queue.add(stringRequest);
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (responseHolder[0] instanceof VolleyError) {
final VolleyError volleyError = (VolleyError) responseHolder[0];
//TODO: Handle error...
} else {
final String response = (String) responseHolder[0];
//TODO: Handle response...
}
人々は実際にこれを行おうとしており、トラブルに遭遇したように思えたので、使用中のこの「実際の」作業サンプルを実際に提供することにしました。ここにあります https://github.com/timolehto/SynchronousVolleySample
現在、ソリューションは機能していますが、いくつかの制限があります。最も重要なことは、メインUIスレッドで呼び出すことはできません。 Volleyはリクエストをバックグラウンドで実行しますが、デフォルトではVolleyはアプリケーションのメインLooper
を使用して応答をディスパッチします。これにより、メインUIスレッドが応答を待機しているときにデッドロックが発生しますが、Looper
は配信を処理する前にonCreate
が終了するのを待機しています。本当にこれをやりたい場合は、静的ヘルパーメソッドの代わりに、独自のRequestQueue
をインスタンス化して、ExecutorDelivery
に関連付けられた独自のHandler
をLooper
を使用して渡します。メインUIスレッドとは異なるスレッドに関連付けられています。
@Blundellsと@Mathewsの両方の回答に対する補足的な観察として、私はany呼び出しがVolleyによるメインスレッドbutに配信されるかどうかわかりません。
ソース
RequestQueue
implementation を見ると、RequestQueue
はNetworkDispatcher
を使用してリクエストを実行し、ResponseDelivery
を使用して結果を配信しているようです( ResponseDelivery
がNetworkDispatcher
に挿入されます。 ResponseDelivery
は、メインスレッドからのHandler
スポーンで作成されます(RequestQueue
実装の112行目付近)。
NetworkDispatcher
implementation の行135のどこかで、エラーと同じResponseDelivery
を介して成功した結果が配信されるようです。再び;メインスレッドからのResponseDelivery
スポーンに基づくHandler
.
根拠
リクエストがIntentService
から行われるユースケースでは、Volleyからの応答があるまでサービスのスレッドがブロックされると仮定するのが妥当です(実行中の有効範囲が結果を処理することを保証するため) )。
推奨ソリューション
1つのアプローチは、 RequestQueue
が作成される のデフォルトの方法をオーバーライドすることです。代わりに代替コンストラクターが使用され、currentから生成されるResponseDelivery
を注入しますメインスレッドではなくスレッド。ただし、この影響については調査していません。
マシューの受け入れられた答えに何かを加えたい。 RequestFuture
は、作成したスレッドから同期呼び出しを行うように見えるかもしれませんが、そうではありません。代わりに、呼び出しはバックグラウンドスレッドで実行されます。
ライブラリを通過した後の理解から、RequestQueue
のリクエストはstart()
メソッドでディスパッチされます。
public void start() {
....
mCacheDispatcher = new CacheDispatcher(...);
mCacheDispatcher.start();
....
NetworkDispatcher networkDispatcher = new NetworkDispatcher(...);
networkDispatcher.start();
....
}
これで、CacheDispatcher
クラスとNetworkDispatcher
クラスの両方がスレッドを拡張します。そのため、要求キューをデキューするために新しいワーカースレッドが効率的に生成され、RequestFuture
によって内部的に実装された成功およびエラーリスナーに応答が返されます。
2番目の目的は達成されますが、1番目の目的は、どのスレッドからRequestFuture
を実行しても、常に新しいスレッドが生成されるためです。
つまり、デフォルトのVolleyライブラリでは真の同期要求は不可能です。間違っている場合は修正してください。
私はロックを使用してその効果を達成しましたが、今は誰かがコメントしたいのですか?
// as a field of the class where i wan't to do the synchronous `volley` call
Object mLock = new Object();
// need to have the error and success listeners notifyin
final boolean[] finished = {false};
Response.Listener<ArrayList<Integer>> responseListener = new Response.Listener<ArrayList<Integer>>() {
@Override
public void onResponse(ArrayList<Integer> response) {
synchronized (mLock) {
System.out.println();
finished[0] = true;
mLock.notify();
}
}
};
Response.ErrorListener errorListener = new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
synchronized (mLock) {
System.out.println();
finished[0] = true;
System.out.println();
mLock.notify();
}
}
};
// after adding the Request to the volley queue
synchronized (mLock) {
try {
while(!finished[0]) {
mLock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
ボレーで同期要求を行うことはできますが、別のスレッドでメソッドを呼び出す必要があります。そうしないと、実行中のアプリがブロックされます。次のようになります。
public String syncCall(){
String URL = "http://192.168.1.35:8092/rest";
String response = new String();
RequestQueue requestQueue = Volley.newRequestQueue(this.getContext());
RequestFuture<JSONObject> future = RequestFuture.newFuture();
JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, URL, new JSONObject(), future, future);
requestQueue.add(request);
try {
response = future.get().toString();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
return response;
}
その後、スレッドでメソッドを呼び出すことができます:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
String response = syncCall();
}
});
thread.start();