java - requestqueue - ¿Puedo hacer una solicitud sincrónica con volley?
return value with volley android (6)
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.
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 devolución de llamada, 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!
Probablemente se recomiende usar Futures, pero si por alguna razón no quieres, en lugar de cocinar tu propio bloqueo 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<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...
}
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 objetivo, su primer objetivo 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.
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 devolución de llamada 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<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;
}
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<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();
}
}
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<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
}