¿Puedo hacer una solicitud sincrónica con volley?

Imagina que estoy en un servicio que ya tiene un hilo de fondo. ¿Puedo hacer una solicitud usando volley en ese mismo hilo, para que las devoluciones de llamadas ocurran de forma sincronizada?

Hay dos razones para esto: – Primero, no necesito otro hilo y sería un desperdicio crearlo. – En segundo lugar, si estoy en un ServiceIntent, la ejecución del hilo terminará antes de la callback, y por lo tanto no tendré respuesta de Volley. Sé que puedo crear mi propio servicio que tiene algo de hilo con un runloop que puedo controlar, pero sería deseable tener esta funcionalidad en voley.

¡Gracias!

Parece que es posible con la clase RequestFuture de Volley. Por ejemplo, para crear una solicitud JET HTTP GET sincrónica, puede hacer lo siguiente:

 RequestFuture 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 } 

Tenga en cuenta que la respuesta de @Matthews es correcta PERO si tiene otro hilo y realiza una llamada de volley cuando no tiene Internet, se llamará a su error de callback en el hilo principal, pero el hilo en el que se encuentra se bloqueará PARA SIEMPRE. (Por lo tanto, si ese hilo es un servicio de intención, nunca podrá enviar otro mensaje y su servicio estará básicamente muerto).

Usa la versión de get() que tiene un timeout future.get(30, TimeUnit.SECONDS) y future.get(30, TimeUnit.SECONDS) el error para salir de tu hilo.

Para que coincida con la respuesta de @Mathews:

  try { return future.get(30, TimeUnit.SECONDS); } catch (InterruptedException e) { // exception handling } catch (ExecutionException e) { // exception handling } catch (TimeoutException e) { // exception handling } 

A continuación lo envolví en un método y utilicé una solicitud diferente:

  /** * 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 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; } 

Probablemente se recomiende usar Futures, pero si por alguna razón no quieres, en lugar de cocinar tu propio locking sincronizado deberías usar java.util.concurrent.CountDownLatch . Entonces eso funcionaría así …

 //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() { @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... } 

Como observación complementaria de las respuestas de @Blundells y @Mathews, no estoy seguro de que Devley reciba ninguna llamada, excepto Volley.

La fuente

Al observar la implementación de RequestQueue , parece que RequestQueue utiliza un NetworkDispatcher para ejecutar la solicitud y una ResponseDelivery para entregar el resultado (el ResponseDelivery se inyecta en el NetworkDispatcher ). El ResponseDelivery se crea a su vez con un engendro Handler del hilo principal (en algún lugar alrededor de la línea 112 en la implementación de RequestQueue ).

En algún lugar sobre la línea 135 en la implementación de NetworkDispatcher parece que los resultados exitosos se entregan a través de la misma entrega de ResponseDelivery que cualquier error. De nuevo; una ResponseDelivery basada en un engendro del controlador del hilo principal.

Razón fundamental

Para el caso de uso en el que se realiza una solicitud desde un servicio de IntentService , es justo suponer que el hilo del servicio debe bloquearse hasta que tengamos una respuesta de Volley (para garantizar un ámbito de tiempo de ejecución vivo para manejar el resultado).

Las soluciones sugeridas

Un enfoque sería anular la forma predeterminada en que se crea un RequestQueue , donde se usa un constructor alternativo, inyectando un ResponseDelivery que se genera a partir del hilo actual en lugar del hilo principal. No he investigado las implicaciones de esto, sin embargo.

Uso un candado para lograr ese efecto. Ahora me pregunto si es correcto, ¿alguien quiere hacer un comentario?

 // 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> responseListener = new Response.Listener>() { @Override public void onResponse(ArrayList 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(); } } 

Quiero agregar algo a la respuesta aceptada de Matthew. Aunque RequestFuture parece hacer una llamada sincrónica desde el hilo que usted creó, no es así. En cambio, la llamada se ejecuta en un hilo de fondo.

Por lo que entiendo después de pasar por la biblioteca, las solicitudes en RequestQueue se envían en su método start() :

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

Ahora las clases CacheDispatcher y NetworkDispatcher extienden el hilo. Entonces efectivamente se genera un nuevo hilo de trabajo para dequear la cola de solicitudes y la respuesta se devuelve a las escuchas de éxito y error implementadas internamente por RequestFuture .

A pesar de que se ha alcanzado su segundo objective, su primer objective no lo es, ya que siempre se genera un nuevo hilo, sin importar de qué hilo ejecute RequestFuture .

En resumen, la solicitud síncrona verdadera no es posible con la biblioteca Volley predeterminada. Corrígeme si estoy equivocado.