¿Puedo hacer una petición síncrona con volley?


Imagine que estoy en un Servicio que ya tiene un hilo en segundo plano. ¿Puedo hacer una solicitud usando volley en ese mismo hilo, para que las devoluciones de llamada se realicen de forma sincrónica?

Hay 2 razones para esto: - Primero, no necesito otro hilo y sería un desperdicio crearlo. - Segundo, si estoy en un ServiceIntent, la ejecución del hilo terminará antes de la devolución de llamada, y por lo tanto no tendré respuesta de Volley. Sé que puedo crear mi propio Servicio que tiene algún hilo con un runloop puedo controlar, pero sería deseable tener esta funcionalidad en volley.

¡Gracias!

Author: Ankit, 2013-06-03

6 answers

Parece que es posible con la clase de Volley RequestFuture. Por ejemplo, para crear una solicitud GET HTTP JSON síncrona, puede hacer lo siguiente:

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
}
 170
Author: Matthew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-10 17:30:54

Nota @Matthews la respuesta es correcta PERO si estás en otro hilo y haces una llamada volley cuando no tienes Internet, tu callback de error será llamado en el hilo principal, pero el hilo en el que estás será bloqueado PARA SIEMPRE. (Por lo tanto, si ese hilo es un IntentService, nunca podrá enviarle otro mensaje y su servicio estará básicamente muerto).

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

A match @ Mathews respuesta:

        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 use 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<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;
    }
 108
Author: Blundell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-22 13:52:26

Probablemente se recomienda usar los Futuros, pero si por alguna razón no quieres, en lugar de cocinar tu propia cosa de bloqueo sincronizado deberías usar un java.util.concurrent.CountDownLatch. Así que 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<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...
}
 7
Author: Timo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-30 08:00:04

Como observación complementaria a las respuestas de @Blundells y @Mathews, no estoy seguro de que cualquier llamada se entregue a cualquier cosa pero el hilo principal de Volea.

La Fuente

Echando un vistazo a la RequestQueue implementación parece que el RequestQueue está usando un NetworkDispatcher para ejecutar la solicitud y un ResponseDelivery para entregar el resultado (el ResponseDelivery se inyecta en el NetworkDispatcher). El ResponseDelivery se crea a su vez con un spawn Handler del hilo principal (en algún lugar alrededor de la línea 112 en la implementación RequestQueue).

En algún lugar de la línea 135 en el NetworkDispatcher implementación parece que también se entregan resultados exitosos a través del mismo ResponseDelivery que cualquier error. De nuevo; un ResponseDelivery basado en un Handler spawn del hilo principal.

Justificación

Para el caso de uso donde una solicitud debe hacerse desde un IntentService es justo asumir 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).

Soluciones propuestas

Un enfoque sería anular la forma predeterminada a RequestQueue se crea , donde se utiliza un constructor alternativo en su lugar, inyectando un ResponseDelivery que se genera desde el hilo actual en lugar del hilo principal. Sin embargo, no he investigado las implicaciones de esto.

 2
Author: dbm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-20 09:28:00

Uso un candado para lograr ese efecto ahora me pregunto si es correcto a mi manera ¿alguien quiere comentar ?

// 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
Author: forcewill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-11-06 17:21:20

Quiero añadir algo a la respuesta aceptada de Mateo. Mientras que RequestFuture podría parecer hacer una llamada sincrónica desde el hilo que lo creó, no lo hace. En su lugar, la llamada se ejecuta en un subproceso de fondo.

Por lo que entiendo después de pasar por la biblioteca, las solicitudes en el 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. De manera tan efectiva se genera un nuevo hilo de trabajo para desquejar la cola de solicitudes y la respuesta es devuelto a los oyentes de éxito y error implementados internamente por RequestFuture.

Aunque tu segundo propósito se alcanza, tu primer propósito no lo es ya que siempre se genera un nuevo hilo, sin importar desde qué hilo ejecutes RequestFuture.

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

 1
Author: Vignatus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-17 06:42:08