web-dev-qa-db-fra.com

Puis-je faire une demande synchrone avec volley?

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!

128
LocoMike

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
}
177
Matt

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;
    }
121
Blundell

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.

8
Timo

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.

2
dbm

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.

1
Vignatus

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();
            }
        }
1
forcewill

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();
0
Slimani Ibrahim