141 votes

Puis-je faire une demande synchrone avec volley ?

Imaginez que je suis dans un service qui a déjà un fil de fond. Puis-je faire une demande en utilisant volley dans ce même thread, de sorte que les rappels soient synchronisés ?

Il y a deux raisons à cela :

  • Premièrement, je n'ai pas besoin d'un autre fil de discussion 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 callback, et je n'aurai donc pas de réponse de Volley. Je sais que je peux créer mon propre Service qui possède un thread avec un runloop que je peux contrôler, mais il serait souhaitable d'avoir cette fonctionnalité dans Volley.

6 votes

Ne manquez pas de lire la réponse de @Blundell ainsi que la réponse très remontée (et très utile).

194voto

Matthew Points 1650

On dirait que c'est possible avec la méthode de Volley. RequestFuture classe. Par exemple, pour créer une requête GET HTTP JSON 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
}

0 votes

Êtes-vous sûr que JSONObjectRequest possède ce nouveau constructeur JsonObjectRequest(URL, futur, futur) ?

5 votes

@tasomaniac Mise à jour. Ceci utilise le JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener, ErrorListener errorlistener) constructeur. RequestFuture<JSONObject> met en œuvre à la fois le Listener<JSONObject> y ErrorListener il peut donc être utilisé comme les deux derniers paramètres.

24 votes

Il est bloqué pour toujours !

132voto

Blundell Points 28342

Note @Matthews la réponse est correcte MAIS si vous êtes sur un autre fil et que vous faites un appel de volée quand vous n'avez pas internet, votre rappel d'erreur sera appelé sur le thread principal, mais le thread sur lequel vous êtes sera bloqué POUR TOUJOURS. (Par conséquent, si ce thread est un IntentService, vous ne serez jamais en mesure de 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 attraper l'erreur pour quitter votre fil.

Pour répondre à 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 utilisé une requête 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;
    }

1 votes

C'est un point assez important ! Je ne sais pas pourquoi cette réponse n'a pas obtenu plus de votes positifs.

1 votes

Il convient également de noter que si vous transmettez l'ExecutionException au même écouteur que celui utilisé pour la demande, l'exception sera traitée deux fois. Cette exception survient lorsqu'une exception s'est produite au cours de la requête, que volley transmettra à l'écouteur errorListener pour vous.

0 votes

@Blundell Je ne comprends pas votre réponse. Si le listener est exécuté sur le thread de l'interface utilisateur, vous avez un thread d'arrière-plan en attente et le thread de l'interface utilisateur qui appelle notifyAll() donc c'est ok. Un blocage peut se produire si la livraison est effectuée sur le même thread que celui qui est bloqué avec le futur get(). Votre réponse semble donc dénuée de sens.

9voto

Timo Lehto Points 1603

Il est probablement recommandé d'utiliser les Futures, mais si, pour une raison ou une autre, vous ne voulez pas le faire, au lieu de créer votre propre blocage synchronisé, vous devriez utiliser un fichier de type java.util.concurrent.CountDownLatch . Donc, cela fonctionnerait comme ceci..

//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...
}

Puisque des personnes semblent avoir essayé de le faire et ont rencontré des problèmes, j'ai décidé de fournir un exemple concret de son utilisation. Le voici https://github.com/timolehto/SynchronousVolleySample

Or, même si cette solution fonctionne, elle présente certaines limites. La plus importante est que vous ne pouvez pas l'appeler sur le thread principal de l'interface utilisateur. Volley exécute les requêtes en arrière-plan, mais par défaut, Volley utilise le thread principal de l'interface utilisateur. Looper de l'application pour distribuer les réponses. Cela provoque un blocage, car le thread principal de l'interface utilisateur attend la réponse, mais le thread de l'application ne l'attend pas. Looper attend onCreate à terminer avant de traiter la livraison. Si vous voulez vraiment faire cela, vous pouvez, au lieu des méthodes statiques d'aide, instancier votre propre RequestQueue le faire passer pour le vôtre ExecutorDelivery lié à un Handler en utilisant un Looper qui est lié à un thread différent du thread principal de l'interface utilisateur.

0 votes

Cette solution bloque mon thread pour toujours, j'ai changé Thread.sleep au lieu de countDownLatch et le problème est résolu.

0 votes

Si vous pouvez fournir un exemple complet d'un code qui échoue de cette manière, nous pourrons peut-être déterminer quel est le problème. Je ne vois pas en quoi le fait de dormir en conjonction avec des loquets de compte à rebours a un sens.

0 votes

Très bien @VinojVetha J'ai mis à jour la réponse pour clarifier la situation et j'ai fourni un repo GitHub que vous pouvez facilement cloner et essayer le code en action. Si vous avez d'autres problèmes, veuillez fournir un fork de l'exemple de repo démontrant votre problème comme référence.

3voto

murgupluoglu Points 631

Vous y parvenez avec les coroutines de Kotlin.

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7"

private suspend fun request(context: Context, link : String) : String{
   return suspendCancellableCoroutine { continuation ->
      val queue = Volley.newRequestQueue(context)
      val stringRequest = StringRequest(Request.Method.GET, link,
         { response ->
            continuation.resumeWith(Result.success(response))
         },
          {
            continuation.cancel(Exception("Volley Error"))
         })

      queue.add(stringRequest)
   }
}

Et appelez avec

CoroutineScope(Dispatchers.IO).launch {
    val response = request(CONTEXT, "https://www.google.com")
    withContext(Dispatchers.Main) {
       Toast.makeText(CONTEXT, response,Toast.LENGTH_SHORT).show()
   }
}

2voto

Ogre_BGR Points 4799

[Les informations de cette réponse n'étaient PAS correctes mais je ne peux pas la supprimer car elle est marquée comme "acceptée". Veuillez utiliser la réponse de @Matthew.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X