web-dev-qa-db-ja.com

ボレーで同期リクエストを行うことはできますか?

すでにバックグラウンドスレッドを持っているサービスにいると想像してください。同じスレッドでvolleyを使用してリクエストを行うことで、コールバックを同期的に実行できますか?

これには2つの理由があります。-最初に、別のスレッドは必要ありません。作成するのは無駄です。 -次に、ServiceIntentを使用している場合、コールバックの前にスレッドの実行が終了するため、Volleyからの応答がありません。制御可能なランループを備えたスレッドを持つ独自のサービスを作成できることは知っていますが、この機能をボレーで使用することが望ましいでしょう。

ありがとうございました!

128
LocoMike

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
}
177
Matt

注:@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;
    }
121
Blundell

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に関連付けられた独自のHandlerLooperを使用して渡します。メインUIスレッドとは異なるスレッドに関連付けられています。

8
Timo

@Blundellsと@Mathewsの両方の回答に対する補足的な観察として、私はany呼び出しがVolleyによるメインスレッドbutに配信されるかどうかわかりません。

ソース

RequestQueue implementation を見ると、RequestQueueNetworkDispatcherを使用してリクエストを実行し、ResponseDeliveryを使用して結果を配信しているようです( ResponseDeliveryNetworkDispatcherに挿入されます。 ResponseDeliveryは、メインスレッドからのHandlerスポーンで作成されます(RequestQueue実装の112行目付近)。

NetworkDispatcher implementation の行135のどこかで、エラーと同じResponseDeliveryを介して成功した結果が配信されるようです。再び;メインスレッドからのResponseDeliveryスポーンに基づくHandler.

根拠

リクエストがIntentServiceから行われるユースケースでは、Volleyからの応答があるまでサービスのスレッドがブロックされると仮定するのが妥当です(実行中の有効範囲が結果を処理することを保証するため) )。

推奨ソリューション

1つのアプローチは、 RequestQueueが作成される のデフォルトの方法をオーバーライドすることです。代わりに代替コンストラクターが使用され、currentから生成されるResponseDeliveryを注入しますメインスレッドではなくスレッド。ただし、この影響については調査していません。

2
dbm

マシューの受け入れられた答えに何かを加えたい。 RequestFutureは、作成したスレッドから同期呼び出しを行うように見えるかもしれませんが、そうではありません。代わりに、呼び出しはバックグラウンドスレッドで実行されます。

ライブラリを通過した後の理解から、RequestQueueのリクエストはstart()メソッドでディスパッチされます。

    public void start() {
        ....
        mCacheDispatcher = new CacheDispatcher(...);
        mCacheDispatcher.start();
        ....
           NetworkDispatcher networkDispatcher = new NetworkDispatcher(...);
           networkDispatcher.start();
        ....
    }

これで、CacheDispatcherクラスとNetworkDispatcherクラスの両方がスレッドを拡張します。そのため、要求キューをデキューするために新しいワーカースレッドが効率的に生成され、RequestFutureによって内部的に実装された成功およびエラーリスナーに応答が返されます。

2番目の目的は達成されますが、1番目の目的は、どのスレッドからRequestFutureを実行しても、常に新しいスレッドが生成されるためです。

つまり、デフォルトのVolleyライブラリでは真の同期要求は不可能です。間違っている場合は修正してください。

1
Vignatus

私はロックを使用してその効果を達成しましたが、今は誰かがコメントしたいのですか?

// 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();
            }
        }
1
forcewill

ボレーで同期要求を行うことはできますが、別のスレッドでメソッドを呼び出す必要があります。そうしないと、実行中のアプリがブロックされます。次のようになります。

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();
0
Slimani Ibrahim