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


133

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

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

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


5
@Blundellの応答と、高く評価された(そして非常に役立つ)回答を必ずお読みください。
Jedidja 2014年

回答:


183

バレーの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
}

5
@tasomaniac更新されました。これはJsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener, ErrorListener errorlistener)コンストラクタを使用します。RequestFuture<JSONObject>実装の両方Listener<JSONObject>ErrorListenerのインタフェース、それは最後の二つのパラメータとして使用することができます。
マット

21
それは永遠にブロックされています!
Mohammed Subhi Sheikh Quroush 2014年

9
ヒント:リクエストをリクエストキューに追加する前にfuture.get()を呼び出すと、永久にブロックされる可能性があります。
datayeah 2014年

3
あなたは接続エラーを持っているかもしれないのでそれは永遠にブロックされますBlundell Answerを読んでください
ミナガブリエル

4
メインスレッドではこれを実行しないでください。それは私には分かりませんでした。メインスレッドがによってブロックされてfuture.get()いる場合は、設定すると、アプリが停止するか、タイムアウトになる可能性があります。
r00tandy 2016年

125

@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;
    }

それはかなり重要なポイントです!なぜこの回答がより多くの賛成票を得ていないのかわかりません。
Jedidja 2014年

1
また、リクエストに渡したのと同じリスナーにExecutionExceptionを渡すと、例外を2回処理することにも注意してください。この例外は、リクエスト中にvolleyがerrorListenerに渡す例外が発生したときに発生します。
Stimsoni 2015年

@ブランデル私はあなたの返事を理解できません。リスナーがUIスレッドで実行される場合、バックグラウンドスレッドが待機状態にあり、notifyAll()を呼び出すUIスレッドがあるので問題ありません。配信が将来のget()でブロックされているのと同じスレッドで行われると、デッドロックが発生する可能性があります。だからあなたの反応は意味がないようです。
greywolf82

1
@ greywolf82 IntentServiceはシングルスレッドのスレッドプールエグゼキューターであるため、ループ内にあるためIntentServiceは永久にブロックされます
Blundell

@Blundellわかりません。通知が呼び出され、UIスレッドから呼び出されるまで待機します。スレッドが2つになるまで、デッドロックは見えません
greywolf82 '17年

9

おそらくFuturesを使用することをお勧めしますが、何らかの理由で使用したくない場合は、独自の同期ブロックを作成する代わりに、を使用する必要があり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スレッドが応答を待機しているLooperonCreate、配信を処理する前にが完了するのを待機しているため、これによりデッドロックが発生します。本当にこれを実行したい場合は、静的ヘルパーメソッドの代わりに、メインのUIスレッドとは異なるスレッドに関連付けられているを使用しRequestQueueて、独自のインスタンスを作成してインスタンス化します。ExecutorDeliveryHandlerLooper


このソリューションは私のスレッドを永久にブロックし、countDownLatchの代わりにThread.sleepを変更して問題を解決しました
snersesyan

この方法で失敗するコードの完全なサンプルを提供できれば、問題が何であるかを理解できるでしょう。カウントダウンラッチと組み合わせてスリープすることがどのように意味があるのか​​わかりません。
ティモ

申し分なく@VinojVetha状況を明確にするために答えを少し更新し、GitHubリポジトリを提供しました。これにより、簡単にクローンを作成し、コードを実際に試すことができます。さらに問題がある場合は、参照として問題を示すサンプルリポジトリのフォークを提供してください。
ティモ

これは、これまでの同期リクエストの優れたソリューションです。
ビクラム

2

@Blundellsと@Mathews答えの両方に対して相補的な観測として、私はわからない任意のコールが何に配信されますが飛来することにより、メインスレッド。

起源

RequestQueue実装を見てみると、RequestQueueを使用しNetworkDispatcherてリクエストを実行し、を使用してResponseDelivery結果を配信しているようResponseDeliveryです(はに注入されますNetworkDispatcher)。次に、メインスレッドからのスポーンを使用してResponseDelivery作成されHandlerます(RequestQueue実装の行112あたり)。

NetworkDispatcher実装の 135行目あたりのどこかで、成功した結果ResponseDeliveryもエラーと同じように配信さているようです。再び; ResponseDelivery基づいてHandler、メインスレッドから産卵。

根拠

リクエストがから行われるユースケースの場合IntentService、サービスのスレッドは、Volleyからの応答があるまでブロックする必要があると想定するのが妥当です(結果を処理するための有効なランタイムスコープを保証するため)。

推奨される解決策

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


1
カスタムResponseDelivery実装の実装はfinish()Requestクラス内のメソッドとRequestQueueクラスがパッケージプライベートであるため、リフレクションハックを使用することを除いて複雑です。これを回避する方法があるかどうかはわかりません。メイン(UI)スレッドで何も実行されないようにするために結局行ったのは、代替のLooperスレッドをセットアップし(を使用Looper.prepareLooper(); Looper.loop())、そのルーパーのハンドラーを使用してExecutorDeliveryインスタンスをRequestQueueコンストラクターに渡していました。あなたは別のルーパーのオーバーヘッドを持っていますが、メインスレッドには触れません
スティーブンジェームスハンド

1

私はその効果を達成するためにロックを使用していますが、誰かがコメントしたい私の方法が正しいかどうか疑問に思っていますか?

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

「未来」を使うとき、あなたは本質的にVolleyがすでに提供しているものを実装していると思います。
spaaarky21 2015年

1
catch (InterruptedException e)whileループの中に入れることをお勧めします。そうしないと、何らかの理由で中断された場合、スレッドは待機に失敗します
jayeffkay

@jayeffkay ** InterruptedException **がwhileループで発生した場合、catchはそれを処理してすでに例外をキャッチしています。
forcewill 2015

1

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

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

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

CacheDispatcherNetworkDispatcherクラスの両方がスレッドを拡張します。したがって、事実上、リクエストキューをデキューするために新しいワーカースレッドが生成され、内部的に実装された成功およびエラーリスナーに応答が返されます。RequestFutureます。

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

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


0

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

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();
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.