Imaginez que je suis dans un service qui a déjà un fil d’arrière-plan. Puis-je faire une demande en utilisant volley dans le même thread, afin que les rappels se produisent de manière synchrone?
Il y a 2 raisons à cela: - Premièrement, je n'ai pas besoin d'un autre fil et ce serait un gaspillage de le créer. - Deuxièmement, si je suis dans un ServiceIntent, l'exécution du thread se terminera avant le rappel et, par conséquent, Volley ne répondra pas. Je sais que je peux créer mon propre service qui a un fil avec un runloop que je peux contrôler, mais il serait souhaitable d’avoir cette fonctionnalité en volée.
Je vous remercie!
Il semble que cela soit possible avec la classe RequestFuture
de Volley. Par exemple, pour créer une demande JSON HTTP GET synchrone, vous pouvez procéder comme suit:
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
}
Note @Matthews answer est correct MAIS si vous êtes sur un autre fil et que vous effectuez un appel de volée lorsque vous n'avez pas Internet, votre rappel d'erreur sera appelé sur le fil principal, mais le fil sur lequel vous vous trouvez sera bloqué POUR TOUJOURS . (Par conséquent, si ce fil est un IntentService, vous ne pourrez jamais lui envoyer un autre message et votre service sera pratiquement mort).
Utilisez la version de get()
qui a un délai d'attente future.get(30, TimeUnit.SECONDS)
et attrapez l'erreur de quitter votre thread.
Pour correspondre à la réponse de @Mathews:
try {
return future.get(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// exception handling
} catch (ExecutionException e) {
// exception handling
} catch (TimeoutException e) {
// exception handling
}
Ci-dessous je l'ai enveloppé dans une méthode et utiliser une demande différente:
/**
* 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;
}
Il est probablement recommandé d’utiliser les Futures, mais si pour une raison quelconque vous ne voulez pas, au lieu de faire votre propre blocage bloquant, vous devriez utiliser un Java.util.concurrent.CountDownLatch
. Donc, cela fonctionnerait comme ça ..
//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...
}
Étant donné que les gens semblaient vraiment essayer de le faire et rencontraient des problèmes, j'ai décidé de leur fournir un échantillon réel de ce qu'ils utilisaient. La voici https://github.com/timolehto/SynchronousVolleySample
Maintenant, même si la solution fonctionne, elle présente certaines limites. Plus important encore, vous ne pouvez pas l'appeler sur le fil principal de l'interface utilisateur. Volley exécute les requêtes en arrière-plan, mais par défaut, Volley utilise le principal Looper
de l'application pour envoyer les réponses. Cela provoque un blocage, car le thread d'interface utilisateur principal attend la réponse, mais le Looper
attend que onCreate
se termine avant de traiter la livraison. Si vous voulez vraiment faire cela, vous pouvez, au lieu des méthodes d'assistance statique, instancier votre propre RequestQueue
en lui passant votre propre ExecutorDelivery
lié à un Handler
en utilisant un Looper
qui est lié à un thread différent du thread principal de l'interface utilisateur.
Comme observation complémentaire aux réponses @Blundells et @Mathews, je ne suis pas sûr aucune appel est livré à quoi que ce soit mais le fil principal de Volley.
La source
En regardant RequestQueue
implementation il semble que le RequestQueue
utilise un NetworkDispatcher
pour exécuter la requête et un ResponseDelivery
pour livre le résultat (le ResponseDelivery
est injecté dans le NetworkDispatcher
). Le ResponseDelivery
est à son tour créé avec un spawn Handler
du thread principal (quelque part autour de la ligne 112 dans l'implémentation RequestQueue
).
Quelque part à propos de la ligne 135 de la NetworkDispatcher
implémentation , il semble que les résultats obtenus soient également transmis via le même ResponseDelivery
que toutes les erreurs éventuelles. Encore; un ResponseDelivery
basé sur un spawn Handler
du thread principal.
Justification
Pour le cas d'utilisation où une requête doit être faite à partir d'un IntentService
, il est juste de supposer que le thread du service doit rester bloqué jusqu'à ce que nous recevions une réponse de Volley (afin de garantir une portée d'exécution réelle pour gérer le résultat dans).
Solutions suggérées
Une approche consisterait à remplacer le mode par défaut RequestQueue
est créé , lorsqu'un constructeur alternatif est utilisé à la place, en injectant un ResponseDelivery
qui est généré à partir de courant thread plutôt que le thread principal. Je n'ai pas étudié les implications de cela, cependant.
Je veux ajouter quelque chose à la réponse acceptée de Matthew. Bien que RequestFuture
puisse sembler effectuer un appel synchrone à partir du thread que vous l'avez créé, ce n'est pas le cas. Au lieu de cela, l'appel est exécuté sur un thread en arrière-plan.
D'après ce que j'ai compris après avoir parcouru la bibliothèque, les requêtes de la méthode RequestQueue
sont réparties dans sa méthode start()
:
public void start() {
....
mCacheDispatcher = new CacheDispatcher(...);
mCacheDispatcher.start();
....
NetworkDispatcher networkDispatcher = new NetworkDispatcher(...);
networkDispatcher.start();
....
}
Maintenant, les deux classes CacheDispatcher
et NetworkDispatcher
étendent le thread. Donc, efficacement, un nouveau thread de travail est créé pour la file d'attente des demandes et la réponse est renvoyée aux écouteurs de succès et d'erreur implémentés en interne par RequestFuture
.
Bien que votre deuxième objectif soit atteint, mais que votre objectif premier ne le soit pas, car un nouveau thread est toujours créé, quel que soit le thread sur lequel vous exécutez RequestFuture
.
En bref, la vraie demande synchrone n'est pas possible avec la bibliothèque Volley par défaut. Corrigez-moi si je me trompe.
J'utilise un verrou pour obtenir cet effet maintenant je me demande si c'est correct ma façon de commenter?
// 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();
}
}
Vous pouvez faire une demande de synchronisation avec volley mais vous devez appeler la méthode dans un autre thread ou votre application en cours d'exécution va bloquer, cela devrait ressembler à ceci:
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;
}
après cela, vous pouvez appeler la méthode dans le fil:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
String response = syncCall();
}
});
thread.start();