it-swarm.com.de

Kann ich eine synchrone Anfrage mit Volley machen?

Stellen Sie sich vor, ich bin in einem Dienst, der bereits einen Hintergrund-Thread hat. Kann ich eine Anfrage mit Volley in demselben Thread stellen, sodass Rückrufe synchron erfolgen?

Dafür gibt es zwei Gründe: - Erstens brauche ich keinen weiteren Thread und es wäre eine Verschwendung, ihn zu erstellen. - Zweitens, wenn ich in einem ServiceIntent bin, wird die Ausführung des Threads vor dem Rückruf beendet, und daher werde ich keine Antwort von Volley erhalten. Ich weiß, dass ich meinen eigenen Dienst erstellen kann, der einen Thread mit einem Runloop hat, den ich steuern kann, aber es wäre wünschenswert, diese Funktionalität in Volley zu haben.

Vielen Dank!

128
LocoMike

Es sieht so aus, als wäre dies mit der Klasse RequestFuture von Volley möglich. Um beispielsweise eine synchrone JSON-HTTP-GET-Anforderung zu erstellen, können Sie folgende Aktionen ausführen:

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

Hinweis @Matthews Antwort ist korrekt, ABER wenn Sie sich in einem anderen Thread befinden und einen Volley-Anruf tätigen, wenn Sie kein Internet haben Ihr Fehler-Rückruf wird im Haupt-Thread aufgerufen, aber der Thread, in dem Sie sich befinden, wird FÜR IMMER blockiert . (Wenn es sich bei diesem Thread um einen IntentService handelt, können Sie ihm niemals eine andere Nachricht senden, und Ihr Dienst ist im Grunde genommen tot.).

Verwenden Sie die Version von get() mit einer Zeitüberschreitung future.get(30, TimeUnit.SECONDS), und fangen Sie den Fehler ab, um den Thread zu beenden.

Passend zu @Mathews Antwort:

        try {
            return future.get(30, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            // exception handling
        } catch (ExecutionException e) {
            // exception handling
        } catch (TimeoutException e) {
            // exception handling
        }

Unten habe ich es in eine Methode verpackt und benutze eine andere Anfrage:

   /**
     * 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

Es wird wahrscheinlich empfohlen, die Futures zu verwenden, aber wenn Sie aus irgendeinem Grund nicht möchten, sollten Sie anstelle Ihrer eigenen synchronisierten Blockierungssache einen Java.util.concurrent.CountDownLatch Verwenden. Also das würde so funktionieren ..

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

Da die Leute dies tatsächlich zu versuchen schienen und auf einige Probleme stießen, beschloss ich, tatsächlich eine "echte" Arbeitsprobe davon zur Verfügung zu stellen. Hier ist es https://github.com/timolehto/SynchronousVolleySample

Obwohl die Lösung jetzt funktioniert, gibt es einige Einschränkungen. Am wichtigsten ist, dass Sie es nicht im Haupt-UI-Thread aufrufen können. Volley führt die Anforderungen im Hintergrund aus, aber standardmäßig verwendet Volley das Haupt-Looper der Anwendung, um die Antworten zu senden. Dies führt zu einem Deadlock, da der Haupt-UI-Thread auf die Antwort wartet, aber der Looper auf den Abschluss von onCreate wartet, bevor die Übermittlung verarbeitet wird. Wenn Sie dies wirklich wirklich tun möchten, können Sie anstelle der statischen Hilfsmethoden Ihr eigenes RequestQueue instanziieren und es Ihr eigenes ExecutorDelivery übergeben, das mit einem Handler verknüpft ist Ein Looper, das an einen anderen Thread als den Haupt-UI-Thread gebunden ist.

8
Timo

Als ergänzende Beobachtung zu den Antworten von @Blundells und @Mathews bin ich mir nicht sicher, ob any der Anruf an irgendetwas zugestellt wird but der Haupt-Thread von Volley.

Die Quelle

Ein Blick auf die RequestQueue Implementierung Es scheint, dass RequestQueue ein NetworkDispatcher verwendet, um die Anfrage auszuführen, und ein ResponseDelivery, um Liefern Sie das Ergebnis (das ResponseDelivery wird in das NetworkDispatcher injiziert). Das ResponseDelivery wird wiederum mit einem Handler Spawn aus dem Haupt-Thread erstellt (irgendwo um Zeile 112 in der RequestQueue Implementierung).

Irgendwo in Zeile 135 in der Implementierung von NetworkDispatcher scheint es, als würden auch erfolgreiche Ergebnisse durch das gleiche ResponseDelivery wie etwaige Fehler geliefert. Nochmal; Ein ResponseDelivery basierend auf einem Handler Spawn aus dem Haupt-Thread.

Begründung

Für den Anwendungsfall, in dem eine Anfrage von einem IntentService gestellt werden soll, kann man davon ausgehen, dass der Thread des Dienstes blockiert werden sollte, bis wir eine Antwort von Volley erhalten (um einen lebenslangen Laufzeitbereich für die Verarbeitung des Ergebnisses zu gewährleisten) im).

Lösungsvorschläge

Ein Ansatz wäre, die Standardmethode a RequestQueue wird erstellt zu überschreiben, wobei stattdessen ein alternativer Konstruktor verwendet wird, der ein ResponseDelivery einfügt, das aus dem aktuell Thread statt des Hauptthreads. Ich habe die Auswirkungen jedoch nicht untersucht.

2
dbm

Ich möchte etwas zu Matthews akzeptierter Antwort hinzufügen. Möglicherweise führt RequestFuture einen synchronen Aufruf von dem Thread aus, den Sie erstellt haben, dies ist jedoch nicht der Fall. Stattdessen wird der Aufruf in einem Hintergrundthread ausgeführt.

Nach dem, was ich nach Durchlaufen der Bibliothek verstehe, werden Anforderungen in der RequestQueue -Methode start() ausgelöst:

    public void start() {
        ....
        mCacheDispatcher = new CacheDispatcher(...);
        mCacheDispatcher.start();
        ....
           NetworkDispatcher networkDispatcher = new NetworkDispatcher(...);
           networkDispatcher.start();
        ....
    }

Jetzt erweitern die Klassen CacheDispatcher und NetworkDispatcher den Thread. Auf diese Weise wird effektiv ein neuer Arbeitsthread für das Entreihen der Anforderungswarteschlange erstellt und die Antwort wird an die von RequestFuture intern implementierten Listener für Erfolg und Fehler zurückgegeben.

Ihr zweiter Zweck ist zwar erreicht, Ihr erster Zweck jedoch nicht, da immer ein neuer Thread erzeugt wird, unabhängig davon, von welchem ​​Thread Sie RequestFuture ausführen.

Kurz gesagt mit der Standard-Volley-Bibliothek ist keine echte synchrone Anforderung möglich. Korrigieren Sie mich, wenn ich falsch liege.

1
Vignatus

Ich verwende eine Sperre, um diesen Effekt zu erzielen. Ich frage mich, ob dies meiner Meinung nach richtig ist. Möchte jemand einen Kommentar abgeben?

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

Sie können die Synchronisierungsanforderung mit Volley ausführen, aber Sie müssen die Methode in einem anderen Thread aufrufen, oder Ihre laufende App wird blockiert. Dies sollte folgendermaßen aussehen:

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;


}

danach können Sie die Methode in thread aufrufen:

 Thread thread = new Thread(new Runnable() {
                                    @Override
                                    public void run() {

                                        String response = syncCall();

                                    }
                                });
                                thread.start();
0
Slimani Ibrahim