Ich verwende Retrofit / OkHttp (1.6) in meinem Android-Projekt.
Ich finde in keinem von beiden einen Mechanismus für Wiederholungswiederholungen eingebaut. Bei der Suche nach mehr habe ich gelesen, dass OkHttp stille Wiederholungsversuche zu haben scheint. Ich sehe das bei keiner meiner Verbindungen (HTTP oder HTTPS). Wie konfiguriere ich Wiederholungsversuche mit okclient?
Im Moment fange ich Ausnahmen ab und versuche erneut, eine Zählervariable beizubehalten.
Antworten:
Zur Nachrüstung 2.x;
Sie können die Call.clone () -Methode verwenden, um die Anforderung zu klonen und auszuführen.
Zur Nachrüstung 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);
quelle
Response response = chain.proceed(request);
nicht erreichbar, da kein CodeResponse
empfangen wird.response.close()
bevor Sie erneut versuchenresponse = chain.call().clone().execute();
Ich weiß nicht, ob dies eine Option für Sie ist, aber Sie könnten RxJava zusammen mit Retrofit verwenden.
Retrofit kann Observables bei Ruhegesprächen zurückgeben. Auf Oberservables können Sie einfach anrufen
retry(count)
, um das Observable erneut zu abonnieren, wenn es einen Fehler ausgibt.Sie müssten den Aufruf in Ihrer Schnittstelle 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 dies 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 erneuten Versuch sogar viele coole Dinge tun (z. B. das Verfassen und Verketten von Anrufen ).
quelle
Ich bin der Meinung, dass Sie das API-Handling (durchgeführt durch retrofit / okhttp) nicht mit Wiederholungsversuchen mischen sollten. Wiederholungsmechanismen sind orthogonaler 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 oben eine weitere Ebene ein, um den API-Aufruf erneut zu versuchen.
In meiner bisher begrenzten Java-Erfahrung habe ich festgestellt, dass die Failsafe-Bibliothek von jhlaterman (github: jhalterman / failafe ) eine sehr vielseitige Bibliothek ist, um viele Wiederholungssituationen sauber zu handhaben. Als Beispiel würde ich Folgendes mit einem nachgerüsteten instanziierten mySimpleService zur Authentifizierung verwenden:
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; });
Der obige Code fängt Socket-Ausnahmen, Verbindungsfehler, Assertionsfehler ab und versucht sie maximal dreimal mit exponentiellem Backoff. Außerdem können Sie das Verhalten bei erneuten Versuchen anpassen und einen Fallback angeben. Es ist ziemlich konfigurierbar und kann sich an die meisten Wiederholungssituationen anpassen.
Sie können gerne die Dokumentation der Bibliothek überprüfen, da sie neben nur Wiederholungsversuchen noch viele andere Extras bietet.
quelle
2.3.1
scheint API 26 aufgrund der verwendeten Zeiteinheiten (ChronoUnit) zu erfordernDas Problem mit response.isSuccessful () besteht darin, dass Sie eine Ausnahme wie SocketTimeoutException haben.
Ich habe den ursprünglichen Code geändert, um ihn zu beheben.
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.
quelle
OkHttpClient
hat keinesetConnectTimeout
Methode undinterceptors()
ist unveränderlich. Könnte es sein, dass diese Antwort auf einer alten Version basiert? Ich verwende <dependency> <groupId> com.squareup.okhttp3 </ groupId> <artifactId> okhttp </ifactId> <version> 3.11.0 </ version> </ dependency>return response;
am Ende durchreturn response != null ? response : chain.proceed(request);
Mit freundlicher Genehmigung der Top-Antwort: Dies hat bei mir funktioniert. Wenn es Verbindungsprobleme gibt, 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; } }
}}
quelle
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; }
quelle
Für diejenigen, die einen Interceptor bevorzugen, um sich mit dem Problem des erneuten Versuchens zu befassen - Aufbauend auf Sinans Antwort ist hier mein vorgeschlagener Interceptor, der sowohl die Anzahl der Wiederholungen als auch die Verzögerung des Zurücksetzens umfasst und nur Versuche wiederholt, wenn das Netzwerk verfügbar ist und wenn keine Anforderung vorliegt abgesagt. (behandelt nur 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; } });
quelle
Ich habe festgestellt, dass der von Sinan Kozak bereitgestellte Weg (OKHttpClient Intercepter) nicht funktioniert, wenn die http-Verbindung fehlschlägt. Es gibt noch nichts, was die HTTP-Antwort betrifft.
Also benutze ich eine andere Möglichkeit, um das Observable-Objekt zu verknüpfen, und rufe .retryWhen auf. Außerdem habe ich retryCount Limit 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 Wiederholungsversuch. Bitte ändern Sie ihn selbst.
Wenn die http-Antwort 200 ist, wird die oben
rx.retryWhen
genannte Antwort nicht aufgerufen. Wenn Sie darauf bestehen, eine solche Antwort zu überprüfen, können Sie sierx.subscribeOn(...throw error...
vor .retryWhen hinzufügen .quelle
Es scheint, dass es in Retrofit 2.0 aus der API-Spezifikation vorhanden sein wird: https://github.com/square/retrofit/issues/297 . Derzeit scheint der beste Weg zu sein, eine Ausnahme abzufangen und manuell erneut zu versuchen.
quelle
Wie in den Dokumenten angegeben , ist es möglicherweise besser, die eingebrannten Authentifikatoren 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()); }
quelle
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 Retrofit 2. Für Retrofit 1 müssen Sie Interceptor wie die hier akzeptierte Antwort verwenden. Die Antwort von @joluet ist korrekt, aber er hat nicht erwähnt, dass die Wiederholungsmethode vor der .subscribe-Methode (onComplete, onError) aufgerufen werden muss. Dies ist sehr wichtig, da sonst die Anfrage nicht erneut versucht wird, wie in der Antwort von @joluet 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, daher sind viele Lambda-Ausdrücke im Code enthalten.
quelle
Ich möchte nur meine Version teilen. Es verwendet die Methode rxJava retryWhen. Meine Version wiederholt die Verbindung alle N = 15 Sekunden und gibt fast sofort einen erneuten Versuch aus, wenn die Internetverbindung wiederhergestellt ist.
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 verwenden, bevor Sie sich wie folgt anmelden:
.retryWhen(new RetryWithDelayOrInternet())
Sie sollten das Feld isInternetUp 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(); }}
quelle
Arbeitsproduktlö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; }
quelle