Best Practice für die Implementierung eines Retrofit-Rückrufs für neu erstellte Aktivitäten?

73

Ich wechsle zu Retrofit und versuche, die richtige Architektur für die Verwendung mit asynchronen Rückrufen zu verstehen.

Zum Beispiel habe ich eine Schnittstelle:

interface RESTService{
    @GET("/api/getusername")
    void getUserName(@Query("user_id") String userId, 
                     Callback<Response> callback);
}

Und ich führe dies von der Hauptaktivität aus:

RestAdapter restAdapter = new RestAdapter.Builder()
        .setServer("WEBSITE_URL")     
        .build();
RESTService api = restAdapter.create(RESTService.class);
api.getUserName(userId, new Callback<Response> {...});

Dann dreht der Benutzer das Gerät und ich habe eine neu erstellte Aktivität ... Was war hier passiert? Wie kann ich eine Antwort auf die neue Aktivität erhalten (ich gehe davon aus, dass der API-Aufruf im Hintergrund länger ausgeführt wird als das erste Aktivitätsleben). Vielleicht muss ich eine statische Instanz des Rückrufs verwenden oder was? Bitte zeig mir den richtigen Weg ...

Lordmegamax
quelle

Antworten:

42

Verwenden Sie otto . Es gibt viele Beispiele zum Mischen von Otto und Nachrüsten, zum Beispiel https://github.com/pat-dalberg/ImageNom/blob/master/src/com/dalberg/android/imagenom/async/FlickrClient.java

Oder lesen Sie diesen Beitrag http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.html Er beantwortet fast alle Fragen

Durchschn
quelle
Danke für einen anderen Weg! Was sind globale Unterschiede zwischen Otto und RoboSpice? Beide verwenden Listener, POJOs, Anforderungsklassen ... Wie kann ich eine Webanforderung abbrechen, wenn die Aktivität angehalten wird?
Lordmegamax
Ich habe Robospice nicht benutzt. Ich benutze Dinge von Quadrat. Wenn Ihre Aktivität unterbrochen wurde, entfernen Sie das Abonnement für Bus in der onPause-Methode. Also wird nichts kaputt gehen.
Durchschnitt
14
Dies ist in keiner Weise eine Antwort auf die Frage von OP: "Wie kann ich eine Antwort auf die neue Aktivität erhalten?". In dem Blog-Beitrag heißt es ausdrücklich: "Wenn eine Aktivität einen API-Aufruf initiiert und dann zerstört oder im Hintergrund ausgeführt wird, werden wir das resultierende Antwortereignis weiterhin veröffentlichen, aber niemand wird da sein, um zuzuhören. ... Wir fragen immer Daten ab, wann immer dies der Fall ist." unsere Tätigkeit wird wieder aufgenommen "
Gabor
@Gabor Das OP fragt nach der Gerätedrehung. In diesem Fall wird die Aktivität erneut abonniert und erhält die Antwort. Wenn es zu spät ist, kann es einen Produzenten am anderen Ende geben, der die Antwort zwischenspeichern kann. Schließlich sollte die erneute Abfrage des Datenproblems über den savedInstanceState erfolgen.
schwarz
Sie würden setRetainInstanceund Observable.cachedafür brauchen . Hier ist ein Beispiel, das ich geschrieben habe und das so ziemlich ein RxAndroid-Beispiel kopiert: github.com/ber4444/u2020-v2/blob/master/Application/src/main/…
Gabor
34

Für potenziell lange laufende Serveraufrufe verwende ich einen AsyncTaskLoader . Für mich ist der Hauptvorteil von Loadern die Handhabung des Aktivitätslebenszyklus. onLoadFinished wird nur aufgerufen, wenn Ihre Aktivität für den Benutzer sichtbar ist. Lader werden auch zwischen Aktivitäts- / Fragment- und Orientierungsänderungen geteilt.

Also habe ich einen ApiLoader erstellt, der synchrone Aufrufe in loadInBackground nachrüstet .

abstract public class ApiLoader<Type> extends AsyncTaskLoader<ApiResponse<Type>> {

    protected ApiService service;
    protected ApiResponse<Type> response;

    public ApiLoader(Context context) {
        super(context);
        Vibes app = (Vibes) context.getApplicationContext();
        service = app.getApiService();
    }

    @Override
    public ApiResponse<Type> loadInBackground() {
        ApiResponse<Type> localResponse = new ApiResponse<Type>();

        try {
            localResponse.setResult(callServerInBackground(service));
        } catch(Exception e) {
            localResponse.setError(e);
        }

        response = localResponse;
        return response;
    }

    @Override
    protected void onStartLoading() {
        super.onStartLoading();
        if(response != null) {
            deliverResult(response);
        }

        if(takeContentChanged() || response == null) {
            forceLoad();
        }
    }

    @Override
    protected void onReset() {
        super.onReset();
        response = null;
    }


    abstract protected Type callServerInBackground(SecondLevelApiService api) throws Exception;

}

In Ihrer Aktivität initiieren Sie diesen Loader wie folgt:

getSupportLoaderManager().initLoader(1, null, new LoaderManager.LoaderCallbacks<ApiResponse<DAO>>() {
        @Override
        public Loader<ApiResponse<DAO>> onCreateLoader(int id, Bundle args) {
            spbProgress.setVisibility(View.VISIBLE);

            return new ApiLoader<DAO>(getApplicationContext()) {
                @Override
                protected DAO callServerInBackground(ApiService api) throws Exception {
                    return api.requestDAO();
                }
            };
        }

        @Override
        public void onLoadFinished(Loader<ApiResponse<DAO>> loader, ApiResponse<DAO> data) {
            if (!data.hasError()) {
                DAO dao = data.getResult();
                //handle data
            } else {
                Exception error = data.getError();
                //handle error
            }
        }

        @Override
        public void onLoaderReset(Loader<ApiResponse<DAO>> loader) {}
    });

Wenn Sie Daten mehrmals anfordern möchten, verwenden Sie restartLoader anstelle von initLoader .

Benjamin
quelle
5
Ich habe ein vollständiges Beispiel basierend auf Ihrer Idee erstellt: github.com/rciovati/retrofit-loaders-example
rciovati
Sehen Sie sich die Google IO 2013 Volley-Präsentation eines Google-Ingenieurs an. Lader sind schlecht für Netzwerkanforderungen.
JBeckton
@JBeckton: Wenn ich mir das Transkript der Volley-Präsentation ansehe , sehe ich keine Erwähnung des Loader-Frameworks (nur des "Image Loader" von Volley) ...
corsair992
@Benjamin warum benutzt du nicht Serviceoder IntentServicestatt AsyncTask?
Mahdi Pishguy
22

Ich habe eine Art MVP-Implementierung (ModelViewPresenter) für meine Android-Apps verwendet. Für die Nachrüstanforderung habe ich die Aktivitätsaufrufe des jeweiligen Präsentators getätigt, wodurch wiederum die Nachrüstanforderung ausgeführt wird, und als Parameter sende ich einen Rückruf mit einem daran angehängten benutzerdefinierten Listener (vom Präsentator implementiert). Wenn der Rückruf erreicht onSuccessoder onFailureMethoden, rufe ich die jeweiligen Methoden des Listeners auf, die den Präsentator und dann die Aktivitätsmethoden aufrufen: P.

Wenn der Bildschirm jetzt gedreht wird und meine Aktivität neu erstellt wird, hängt sie sich selbst an den Präsentator an. Dies erfolgt mithilfe einer benutzerdefinierten Implementierung der Android-Anwendung, in der die Instanz des Präsentators gespeichert wird, und mithilfe einer Karte zum Wiederherstellen des richtigen Präsentators gemäß der Klasse der Aktivität.

Ich weiß nicht, ob es der beste Weg ist, vielleicht ist die Antwort von @pareshgoel besser, aber es hat bei mir funktioniert: D.

Beispiele:

public abstract interface RequestListener<T> {

    void onSuccess(T response);

    void onFailure(RetrofitError error);
}

...

public class RequestCallback<T> implements Callback<T> {

    protected RequestListener<T> listener;

    public RequestCallback(RequestListener<T> listener){
        this.listener = listener;
    }

    @Override
    public void failure(RetrofitError arg0){
        this.listener.onFailure(arg0);
    }

    @Override
    public void success(T arg0, Response arg1){
        this.listener.onSuccess(arg0);
    }

}

Implementieren Sie den Listener irgendwo auf dem Präsentator, und rufen Sie auf den überschriebenen Methoden die Methode eines Präsentators auf, mit der die Aktivität aufgerufen wird. Und rufen Sie den Moderator an, wo immer Sie möchten, um alles zu initiieren: P.

Request rsqt = restAdapter.create(Request.class);
rsqt.get(new RequestCallback<YourExpectedObject>(listener));

Hoffe es hilft dir.

LeoFarage
quelle
5
Ähnlich wie ein Beobachter für mich. Verwenden Sie einfach RxJava
Dimitri
2
Rückrufe sind hässlich. Sie können stattdessen RxJava (RxAndroid) oder Otto (EventBus) verwenden.
Chris.Zou
1
so etwas wollte ich immer
Jolson Da Costa
Hallo @LeoFarage können Sie bitte diese Struktur mit Nachrüstung 2
pks
Hallo @pks. Ich habe retrofit2 mit MVP implementiert. Sie können hier sehen und überprüfen. github.com/receme/Retrofit2WithMVP Hallo LeoFarage, danke für diese Antwort. Es hat mir sehr geholfen.
Md. Tahmid Mozaffar
10

Erstens leckt Ihre Aktivität hier, weil diese Zeile: api.getUserName (Benutzer-ID, neuer Rückruf {...}) eine anonyme Rückrufklasse erstellt, die einen starken Verweis auf Ihre MainActivity enthält. Wenn das Gerät gedreht wird, bevor der Rückruf aufgerufen wird, wird die MainActivity nicht durch Müll gesammelt. Abhängig davon, was Sie in Callback.call () tun, kann Ihre App zu undefiniertem Verhalten führen.

Die allgemeine Idee, mit solchen Szenarien umzugehen, lautet:

  1. Erstellen Sie niemals eine nicht statische innere Klasse (oder eine anonyme Klasse, wie im Problem erwähnt).
  2. Erstellen Sie stattdessen eine statische Klasse, die eine WeakReference <> für die Aktivität / das Fragment enthält.

Das Obige verhindert nur Lecks. Es hilft Ihnen immer noch nicht, den Retrofit-Rückruf zu Ihrer Aktivität zurückzubekommen.

Um die Ergebnisse auch nach einer Konfigurationsänderung wieder in Ihre Komponente (in Ihrem Fall Aktivität) zu übertragen, möchten Sie möglicherweise ein kopfloses, beibehaltenes Fragment verwenden, das an Ihre Aktivität angehängt ist und Retrofit aufruft. Lesen Sie hier mehr über Retained Fragment - http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)

Die allgemeine Idee ist, dass sich das Fragment bei Konfigurationsänderungen automatisch an die Aktivität anfügt.

Pareshgoel
quelle
1
Ich lese über Kontextlecks und wie man sie vermeidet . Ich kann Synchronisierungsaufrufe in Retrofit immer ohne Rückruf in AsyncTask verwenden, Aktivität festlegen / abrufen, prüfen, ob sie nicht null ist, und erst dann einige Arbeiten ausführen, aber wie kann ich die asynchrone Methode mit Rückruf von Retrofit richtig verwenden , um dies oder mich zu tun? kippen?
Lordmegamax
5

Ich empfehle Ihnen dringend , dieses Video bei Google I / O anzusehen .

Es wird erläutert, wie REST-Anforderungen erstellt werden, indem sie an einen Dienst delegiert werden (der fast nie beendet wird). Wenn die Anforderung abgeschlossen ist, wird sie sofort in der integrierten Datenbank von Android gespeichert, sodass die Daten sofort verfügbar sind, wenn Ihre Aktivität fertig ist.

Mit diesem Ansatz müssen Sie sich nie um den Lebenszyklus der Aktivität kümmern, und Ihre Anforderungen werden viel entkoppelter behandelt.

In dem Video geht es nicht speziell um Nachrüstung, aber Sie können die Nachrüstung leicht an dieses Paradigma anpassen.

Martin Konecny
quelle
5
Dies war vor 5 Jahren. Ich vermute, dass sich die Technologie seitdem verbessert hat und die Nutzung von Diensten derzeit möglicherweise nicht der beste Weg ist.
JBeckton
2

Verwenden Sie Robospice

Alle Komponenten in Ihrer App, die Daten benötigen, registrieren sich beim Gewürzdienst. Der Dienst kümmert sich um das Senden Ihrer Anfrage an den Server (auf Wunsch per Nachrüstung). Wenn die Antwort zurückkommt, werden alle registrierten Komponenten benachrichtigt. Wenn einer von ihnen nicht mehr verfügbar ist (wie eine Aktivität, die aufgrund einer Rotation gekickt wurde), wird sie einfach nicht benachrichtigt.

Vorteil: Eine einzige Anfrage, die nicht verloren geht, egal ob Sie Ihr Gerät drehen, neue Dialoge / Fragmente öffnen usw.

stoefln
quelle
Robospice ist jetzt veraltet
AbdelHady
2

Verwenden von Retrofit2 zur Behandlung von Orientierungsänderungen. Ich wurde in einem Vorstellungsgespräch danach gefragt und abgelehnt, weil ich es damals nicht wusste, aber hier ist es jetzt.

public class TestActivity extends AppCompatActivity {
Call<Object> mCall;
@Override
    public void onDestroy() {
        super.onDestroy();
        if (mCall != null) {
            if (mCall.isExecuted()) {
                //An attempt will be made to cancel in-flight calls, and
                // if the call has not yet been executed it never will be.
                mCall.cancel();
            }
        }
    }
    }
John61590
quelle
3
Warum sollten wir Ihrer Meinung nach Anfragen bei einer zerstörten Aktivität stornieren?
CoolMind
@CoolMind Die Anfrage wurde nicht beendet, daher starten wir sie erneut in onStart () / onResume (). Ich habe diesen Teil leider nicht hinzugefügt. Wir verwenden IntentServices hier nicht, also nicht "feuern und vergessen". Verwenden Sie auch keine kopflosen Fragmente oder AsyncTaskLoaders.
John61590
Sie haben Recht, der Lebenszyklus einer Aktivität ist schwierig zu verwalten, daher gibt es einige architektonische Tricks wie MVP / MVVM mit dem Speichern / Wiederherstellen von Daten, möglicherweise Loader.
CoolMind
Sie sollten einen anderen Weg finden, als den Anruf abzubrechen. Sie können nicht jeden API-Aufruf aufgrund der Gerätedrehung wirklich abbrechen.
Der beste Künstler