wake-up-neo.com

Wie wiederholt man HTTP-Anfragen mit OkHttp/Retrofit?

Ich verwende Retrofit/OkHttp (1.6) in meinem Android-Projekt. 

Ich finde keinen Anforderungswiederholungsmechanismus für beide. Auf der Suche nach mehr lese ich, dass OkHttp scheinbar stille Wiederholungen hat. Ich sehe das bei keiner meiner Verbindungen (HTTP oder HTTPS). Wie konfiguriert man Wiederholversuche mit okclient? 

Momentan fange ich Ausnahmen an und versuche, eine Zählervariable aufrechtzuerhalten.

51
Jaguar

Zum Nachrüsten 1.x;

Sie können Interceptors verwenden. Erstellen Sie einen benutzerdefinierten Interceptor 

    OkHttpClient client = new OkHttpClient();
    client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
    client.interceptors().add(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();

            // try the request
            Response response = chain.proceed(request);

            int tryCount = 0;
            while (!response.isSuccessful() && tryCount < 3) {

                Log.d("intercept", "Request is not successful - " + tryCount);

                tryCount++;

                // retry the request
                response = chain.proceed(request);
            }

            // otherwise just pass the original response on
            return response;
        }
    });

Und verwenden Sie es beim Erstellen von RestAdapter.

new RestAdapter.Builder()
        .setEndpoint(API_URL)
        .setRequestInterceptor(requestInterceptor)
        .setClient(new OkClient(client))
        .build()
        .create(Adapter.class);

Für Retrofit 2.x;

Sie können die Methode Call.clone () verwenden, um die Anforderung zu klonen und auszuführen.

65
Sinan Kozak

Ich weiß nicht, ob dies eine Option für Sie ist, aber Sie könnten RxJava zusammen mit Retrofit verwenden.

Retrofit kann Observables bei Ruhezuständen zurückgeben. Auf Oberservables können Sie einfach retry(count) aufrufen, um das Observable erneut zu abonnieren, wenn ein Fehler ausgegeben wird.

Sie müssten den Aufruf in Ihrer Oberfläche folgendermaßen definieren:

@GET("/data.json")
Observable<DataResponse> fetchSomeData();

Dann können Sie dieses Observable wie folgt abonnieren:

restApi.fetchSomeData()
.retry(5)  // Retry the call 5 times if it errors
.subscribeOn(Schedulers.io())  // execute the call asynchronously
.observeOn(AndroidSchedulers.mainThread())  // handle the results in the ui thread
.subscribe(onComplete, onError); 
// onComplete and onError are of type Action1<DataResponse>, Action1<Throwable>
// Here you can define what to do with the results

Ich hatte das gleiche Problem wie Sie und das war eigentlich meine Lösung. RxJava ist eine wirklich schöne Bibliothek, die in Kombination mit Retrofit verwendet werden kann. Sie können neben dem Wiederholungsversuch auch viele coole Dinge tun (z. B. Verfassen und Verketten von Anrufen ).

34
Jonas Lüthke

Das Problem mit response.isSuccessful () tritt auf, wenn Sie eine Ausnahme wie SocketTimeoutException haben.

Ich habe den Originalcode geändert, um ihn zu korrigieren.

OkHttpClient client = new OkHttpClient();
client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
client.interceptors().add(new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = null;
        boolean responseOK = false;
        int tryCount = 0;

        while (!responseOK && tryCount < 3) {
            try {
                 response = chain.proceed(request);
                 responseOK = response.isSuccessful();                  
            }catch (Exception e){
                 Log.d("intercept", "Request is not successful - " + tryCount);                     
            }finally{
                 tryCount++;      
            }
        }

        // otherwise just pass the original response on
        return response;
    }
});

Ich hoffe es hilft. Grüße.

10
Santiago SR

Höflichkeit bis zur obersten Antwort: Das hat bei mir funktioniert. Bei Verbindungsproblemen sollten Sie einige Sekunden warten, bevor Sie es erneut versuchen.

public class ErrorInterceptor implements Interceptor {
ICacheManager cacheManager;
Response response = null;
int tryCount = 0;
int maxLimit = 3;
int waitThreshold = 5000;
@Inject
public ErrorInterceptor() {

}

@Override
public Response intercept(Chain chain){

   // String language =  cacheManager.readPreference(PreferenceKeys.LANGUAGE_CODE);
  Request request = chain.request();
  response =  sendReqeust(chain,request);
    while (response ==null && tryCount < maxLimit) {
        Log.d("intercept", "Request failed - " + tryCount);
        tryCount++;
        try {
            Thread.sleep(waitThreshold); // force wait the network thread for 5 seconds
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       response = sendReqeust(chain,request);
    }
    return response;
}

private Response sendReqeust(Chain chain, Request request){
    try {
        response = chain.proceed(request);
        if(!response.isSuccessful())
            return null;
        else
        return response;
    } catch (IOException e) {
      return null;
    }
}

}

2
Irshu

Ich fand den Weg (OKHttpClient-Interceptor), der von Sinan Kozak bereitgestellt wird, nicht funktioniert, wenn die HTTP-Verbindung fehlgeschlagen ist. 

Also benutze ich eine andere Möglichkeit, um das Observable-Objekt einzuhaken, rufen Sie .retryWhen auf. . Außerdem habe ich das Limit für retryCount hinzugefügt.

import retrofit2.Call;
import retrofit2.CallAdapter;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.HttpException;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.jackson.JacksonConverterFactory;
import rx.Observable;
import Java.io.IOException;
import Java.lang.annotation.Annotation;
import Java.lang.reflect.Type;

Dann

    RxJavaCallAdapterFactory originCallAdaptorFactory = RxJavaCallAdapterFactory.create();

    CallAdapter.Factory newCallAdaptorFactory = new CallAdapter.Factory() {
        @Override
        public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {

            CallAdapter<?> ca = originCallAdaptorFactory.get(returnType, annotations, retrofit);

            return new CallAdapter<Observable<?>>() {

                @Override
                public Type responseType() {
                    return ca.responseType();
                }

                int restRetryCount = 3;

                @Override
                public <R> Observable<?> adapt(Call<R> call) {
                    Observable<?> rx = (Observable<?>) ca.adapt(call);

                    return rx.retryWhen(errors -> errors.flatMap(error -> {
                        boolean needRetry = false;
                        if (restRetryCount >= 1) {
                            if (error instanceof IOException) {
                                needRetry = true;
                            } else if (error instanceof HttpException) {
                                if (((HttpException) error).code() != 200) {
                                    needRetry = true;
                                }
                            }
                        }

                        if (needRetry) {
                            restRetryCount--;
                            return Observable.just(null);
                        } else {
                            return Observable.error(error);
                        }
                    }));
                }
            };
        }
    };                

Dann hinzufügen oder ersetzen

.addCallAdapterFactory(RxJavaCallAdapterFactory.create())

mit

.addCallAdapterFactory(newCallAdaptorFactory)

Zum Beispiel:

return new Retrofit
        .Builder()
        .baseUrl(baseUrl)
        .client(okClient)
        .addCallAdapterFactory(newCallAdaptorFactory)
        .addConverterFactory(JacksonConverterFactory.create(objectMapper));

Hinweis: Der Einfachheit halber behandle ich HTTP-Code> 404-Code nur als Wiederholung, ändern Sie ihn bitte selbst.

Wenn die http-Antwort 200 lautet, wird darüber hinaus rx.retryWhen nicht aufgerufen. Wenn Sie darauf bestehen, eine solche Antwort zu überprüfen, können Sie rx.subscribeOn(...throw error... vor .retryWhen hinzufügen.

1
osexp2003

Ich bin der Meinung, dass Sie die API-Behandlung (durch Retrofit/Okhttp) nicht mit Wiederholungen mischen sollten. Wiederholungsmechanismen sind eher orthogonal und können auch in vielen anderen Kontexten verwendet werden. Daher verwende ich Retrofit/OkHTTP für alle API-Aufrufe und die Verarbeitung von Anforderungen/Antworten und füge eine weitere Ebene ein, um den API-Aufruf erneut zu versuchen. 

In meiner bisher begrenzten Java-Erfahrung habe ich die Failsafe-Bibliothek von jhlaterman (github: jhalterman/failsafe ) als eine sehr vielseitige Bibliothek für den sauberen Umgang mit vielen Wiederholungssituationen empfunden. Als Beispiel würde ich es mit einem Retrofit verwenden, bei dem mySimpleService instanziiert wurde - zur Authentifizierung - 

AuthenticationResponse authResp = Failsafe.with(
new RetryPolicy().retryOn(Arrays.asList(IOException.class, AssertionError.class))
        .withBackoff(30, 500, TimeUnit.MILLISECONDS)
        .withMaxRetries(3))
.onRetry((error) -> logger.warn("Retrying after error: " + error.getMessage()))
.get(() -> {
    AuthenticationResponse r = mySimpleAPIService.authenticate(
            new AuthenticationRequest(username,password))
            .execute()
            .body();

    assert r != null;

    return r;
});

Mit dem obigen Code werden Socket-Ausnahmen, Verbindungsfehler, Assertionsfehler und dreimalige Wiederholungen mit exponentiellem Backoff abgefangen. Darüber hinaus können Sie das Wiederholungsverhalten anpassen und auch einen Fallback angeben. Es ist ziemlich konfigurierbar und kann sich an die meisten Wiederholungssituationen anpassen.

Fühlen Sie sich frei, die Dokumentation der Bibliothek durchzusehen, da sie neben anderen Wiederholungen viele weitere Extras bietet. 

1
Shreyas

Für diejenigen, die einen Interceptor bevorzugen, der sich mit dem Problem des Wiederholens beschäftigt. -___. Aufbauend auf der Antwort von Sinan ist hier mein vorgeschlagener Interceptor, der sowohl die Anzahl der Wiederholungsversuche als auch die Verzögerungszeit und nur Wiederholungsversuche umfasst, wenn das Netzwerk verfügbar ist und wann die Anforderung erfolgt wurde nicht abgebrochen . (befasst sich nur mit IOExceptions (SocketTimeout, UnknownHost usw.))

    builder.addInterceptor(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();

            // try the request
            Response response = null;
            int tryCount = 1;
            while (tryCount <= MAX_TRY_COUNT) {
                try {
                    response = chain.proceed(request);
                    break;
                } catch (Exception e) {
                    if (!NetworkUtils.isNetworkAvailable()) {
                        // if no internet, dont bother retrying request
                        throw e;
                    }
                    if ("Canceled".equalsIgnoreCase(e.getMessage())) {
                        // Request canceled, do not retry
                        throw e;
                    }
                    if (tryCount >= MAX_TRY_COUNT) {
                        // max retry count reached, giving up
                        throw e;
                    }

                    try {
                        // sleep delay * try count (e.g. 1st retry after 3000ms, 2nd after 6000ms, etc.)
                        Thread.sleep(RETRY_BACKOFF_DELAY * tryCount);
                    } catch (InterruptedException e1) {
                        throw new RuntimeException(e1);
                    }
                    tryCount++;
                }
            }

            // otherwise just pass the original response on
            return response;
        }
    });
1
sahar

Ich möchte nur meine Version teilen. Es verwendet die rxJava-Methode retryWhen. Bei meiner Version wird die Verbindung alle N = 15 Sekunden erneut versucht, und fast sofort wird erneut versucht, wenn die Internetverbindung wiederhergestellt wird.

public class RetryWithDelayOrInternet implements Function<Flowable<? extends Throwable>, Flowable<?>> {
public static boolean isInternetUp;
private int retryCount;

@Override
public Flowable<?> apply(final Flowable<? extends Throwable> attempts) {
    return Flowable.fromPublisher(s -> {
        while (true) {
            retryCount++;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                attempts.subscribe(s);
                break;
            }
            if (isInternetUp || retryCount == 15) {
                retryCount = 0;
                s.onNext(new Object());
            }
        }
    })
            .subscribeOn(Schedulers.single());
}}

Und Sie sollten es vor .subscribe so verwenden:

.retryWhen(new RetryWithDelayOrInternet())

Sie sollten das isInternetUp-Feld manuell ändern 

public class InternetConnectionReceiver extends BroadcastReceiver {


@Override
public void onReceive(Context context, Intent intent) {
    boolean networkAvailable = isNetworkAvailable(context);
    RetryWithDelayOrInternet.isInternetUp = networkAvailable;
}
public static boolean isNetworkAvailable(Context context) {
    ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
    return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}}
0
Nokuap

Eine Lösung, die für mich unter OkHttp 3.9.1 funktioniert hat (unter Berücksichtigung anderer Antworten auf diese Frage):

@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
    Request  request      = chain.request();
    int      retriesCount = 0;
    Response response     = null;

    do {
        try {
            response = chain.proceed(request);

        // Retry if no internet connection.
        } catch (ConnectException e) {
            Log.e(TAG, "intercept: ", e);
            retriesCount++;

            try {
                Thread.sleep(RETRY_TIME);

            } catch (InterruptedException e1) {
                Log.e(TAG, "intercept: ", e1);
            }
        }

    } while (response == null && retriesCount < MAX_RETRIES);

    // If there was no internet connection, then response will be null.
    // Need to initialize response anyway to avoid NullPointerException.
    if (response == null) {
        response = chain.proceed(newRequest);
    }

    return response;
}
0
Yamashiro Rion

Es scheint, dass es in Retrofit 2.0 von der API-Spezifikation vorhanden sein wird: https://github.com/square/retrofit/issues/297 . Derzeit scheint der beste Weg eine Ausnahme zu sein und versuchen Sie es erneut.

0
Shiva

Ich habe viel mit diesem Problem gespielt und versucht herauszufinden, wie Retrofit-Anforderungen am besten wiederholt werden können. Ich verwende Retrofit 2, daher ist meine Lösung für Retrofit 2. Für Retrofit 1 müssen Sie Interceptor wie die hier akzeptierte Antwort verwenden. Die Antwort von @joluet ist korrekt, aber er erwähnte nicht, dass die Wiederholungsmethode vor der .subscribe (onComplete, onError) -Methode aufgerufen werden muss. Dies ist sehr wichtig, da die Anforderung sonst nicht erneut wiederholt wird, wie in @jocolet @pocmo erwähnt. Hier ist mein Beispiel:

final Observable<List<NewsDatum>> newsDetailsObservable = apiService.getCandidateNewsItem(newsId).map((newsDetailsParseObject) -> {
                    return newsDetailsParseObject;
                });

newsDetailsObservable.subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .retry((integer, throwable) -> {
                //MAX_NUMBER_TRY is your maximum try number
                if(integer <= MAX_NUMBER_TRY){
                    return true;//this will retry the observable (request)
                }
                return false;//this will not retry and it will go inside onError method
            })
            .subscribe(new Subscriber<List<NewsDatum>>() {
                @Override
                public void onCompleted() {
                    // do nothing
                }

                @Override
                public void onError(Throwable e) {
                   //do something with the error
                }

                @Override
                public void onNext(List<NewsDatum> apiNewsDatum) {
                    //do something with the parsed data
                }
            });

apiService ist mein RetrofitServiceProvider-Objekt.

Übrigens: Ich verwende Java 8, also stecken viele Lambda-Ausdrücke im Code.

0
Sniper

Arbeits prod lösung.

public int callAPI() {
    return 1; //some method to be retried
}

public int retrylogic()  throws InterruptedException, IOException{
    int retry = 0;
    int status = -1;
    boolean delay = false;
    do {
        if (delay) {
            Thread.sleep(2000);
        }

        try {
            status = callAPI();
        }
        catch (Exception e) {
            System.out.println("Error occured");
            status = -1;
        }
        finally {
            switch (status) {
            case 200:
                System.out.println(" **OK**");
                return status; 
            default:
                System.out.println(" **unknown response code**.");
                break;
            }
            retry++;
            System.out.println("Failed retry " + retry + "/" + 3);
            delay = true;

        } 
    }while (retry < 3);

    System.out.println("Aborting download of dataset.");
    return status;
}
0
sarjit07

Wie in docs angegeben, könnte es besser sein, die eingebackenen Authentifizierungsprogramme zu verwenden, z. B .: Private final OkHttpClient client = new OkHttpClient ();

  public void run() throws Exception {
    client.setAuthenticator(new Authenticator() {
      @Override public Request authenticate(Proxy proxy, Response response) {
        System.out.println("Authenticating for response: " + response);
        System.out.println("Challenges: " + response.challenges());
        String credential = Credentials.basic("jesse", "password1");
        return response.request().newBuilder()
            .header("Authorization", credential)
            .build();
      }

      @Override public Request authenticateProxy(Proxy proxy, Response response) {
        return null; // Null indicates no attempt to authenticate.
      }
    });

    Request request = new Request.Builder()
        .url("http://publicobject.com/secrets/hellosecret.txt")
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
  }
0
AllDayAmazing