Могу ли я сделать синхронный запрос с volley?
представьте, что я нахожусь в службе, которая уже имеет фоновый поток. Могу ли я сделать запрос с помощью volley в том же потоке, так что обратные вызовы происходят синхронно?
есть 2 причины для этого:
- Во-первых, мне не нужна еще одна нить, и было бы бесполезно ее создавать.
- Во-вторых, если я нахожусь в ServiceIntent, выполнение потока завершится до обратного вызова, и поэтому у меня не будет ответа от Volley. Я знаю, что могу создать свой собственный сервис, который имеет некоторый поток с runloop я могу управлять, но хотелось бы иметь эту функциональность в volley.
спасибо!
6 ответов:
похоже, что это возможно с залпом
RequestFutureкласса. Например, чтобы создать синхронный запрос HTTP GET JSON, можно выполнить следующие действия: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; }
вероятно, рекомендуется использовать фьючерсы, но если по какой-либо причине вы не хотите, вместо того, чтобы готовить свою собственную синхронизированную блокирующую вещь, вы должны использовать
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... }
как дополнительное наблюдение к ответам @Blundells и @Mathews, я не уверен любой вызов доставляется на что угодно но основной поток залпом.
Источник
взглянув на
RequestQueueреализация кажетсяRequestQueueиспользуетNetworkDispatcherвыполнить запрос и AResponseDeliveryчтобы доставить результат (theResponseDeliveryвводят вNetworkDispatcher). ЭлементResponseDeliveryв свою очередь создано с помощьюHandlerикру из основного потока (где-то около линии 112 вRequestQueueреализации).где-то около строки 135 в
NetworkDispatcherреализация похоже, что и успешные результаты доставляются через то же самоеResponseDeliveryкак и любой ошибки. Снова;ResponseDeliveryна основеHandlerикра из основного потока.обоснование
для случая использования, когда запрос должен быть сделан из
IntentServiceэто справедливо предположить, что поток службы должен блокироваться до тех пор, пока мы не получим ответ от Volley (чтобы гарантировать живую область выполнения для обработки результата).предлагаемые решения
один из подходов заключается, чтобы переопределить значение по умолчанию, как
RequestQueueсоздана, где вместо этого используется альтернативный конструктор, вводящийResponseDeliveryкоторый порождает из настоящее поток, а не основной поток. Я не однако исследовал последствия этого.
Я использую блокировку для достижения этого эффекта теперь мне интересно, если его правильный мой путь кто хочет высказаться ?
// 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(); } }
Я хочу добавить что-то к принятому ответу Мэтью. В то время как
RequestFutureможет показаться, что синхронный вызов из потока, который вы создали, это не так. Вместо этого вызов выполняется в фоновом потоке.из того, что я понимаю после прохождения библиотеки, запросы в
RequestQueueотправляется вstart()способ:public void start() { .... mCacheDispatcher = new CacheDispatcher(...); mCacheDispatcher.start(); .... NetworkDispatcher networkDispatcher = new NetworkDispatcher(...); networkDispatcher.start(); .... }теперь
CacheDispatcherиNetworkDispatcherклассы расширяют поток. Таким образом, эффективно создается новый рабочий поток для удаления очереди очередь запросов и ответ возвращаются слушателям success и error, реализованным внутриRequestFuture.хотя ваша вторая цель достигнута, но вы не первая цель, так как новый поток всегда порождается, независимо от того, из какого потока вы выполняете
RequestFuture.короче, истинный синхронный запрос невозможен с библиотекой волейбола по умолчанию. поправьте меня, если я ошибаюсь.
Comments