我在Android项目中使用Retrofit / OkHttp(1.6)。
我没有找到任何内置于其中任何一个的请求重试机制。在搜索更多时,我看到OkHttp似乎有沉默重试。我在任何连接(HTTP或HTTPS)上都看不到这种情况。如何使用okclient配置重试?
目前,我正在捕获异常并重试维护计数器变量。
答案 0 :(得分:65)
For Retrofit 1.x;
您可以使用Interceptors。创建自定义拦截器
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;
}
});
在创建RestAdapter时使用它。
new RestAdapter.Builder()
.setEndpoint(API_URL)
.setRequestInterceptor(requestInterceptor)
.setClient(new OkClient(client))
.build()
.create(Adapter.class);
对于Retrofit 2.x;
您可以使用Call.clone()方法克隆请求并执行它。
答案 1 :(得分:36)
我不知道这是否适合您,但您可以将RxJava与Retrofit一起使用。
Retrofit能够在休息时调用Observables。在Oberservables上,您可以在发出错误时调用retry(count)
重新订阅Observable。
您必须在界面中定义调用,如下所示:
@GET("/data.json")
Observable<DataResponse> fetchSomeData();
然后你可以这样订阅这个Observable:
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
我遇到了和你一样的问题,这实际上是我的解决方案。 RxJava是一个非常好的库,可与Retrofit结合使用。除了重试之外,你甚至可以做很多很酷的事情(比如composing and chaining calls)。
答案 2 :(得分:10)
response.isSuccessful()的问题是当你遇到像SocketTimeoutException这样的异常时。
我修改了原始代码以修复它。
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;
}
});
希望它有所帮助。 问候。
答案 3 :(得分:4)
我认为您不应将API处理(通过Retrofit / okhttp完成)与重试混合使用。重试机制更加正交,也可以在许多其他上下文中使用。因此,我将Retrofit / OkHTTP用于所有API调用和请求/响应处理,并在上面引入另一层以重试API调用。
到目前为止,根据我有限的Java经验,我发现jhlaterman的Failsafe库(github:jhalterman/failsafe)是一种非常通用的库,可以干净地处理许多“重试”情况。举例来说,这是我如何将其与经过实例化的mySimpleService进行改造以进行身份验证-
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;
});
上面的代码捕获套接字异常,连接错误,断言失败,并对其进行重试(最多3次),并采用指数补偿。它还允许您自定义重试行为,还可以指定回退。它是可配置的,并且可以适应大多数重试情况。
可以随意查看该库的文档,因为它提供了许多其他功能(除了重试之外)。
答案 4 :(得分:3)
礼貌地回答,这对我有用。如果存在连接问题,最好在重试前等待几秒钟。
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;
}
}
}
答案 5 :(得分:1)
我发现当http连接失败时,Sinan Kozak提供的方式(OKHttpClient intercepter)不起作用,还没有关注HTTP响应。
所以我用另一种方法来挂钩Observable对象,在它上面调用.retryWhen。 另外,我添加了retryCount限制。
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;
然后
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);
}
}));
}
};
}
};
然后 添加或替换
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
与
.addCallAdapterFactory(newCallAdaptorFactory)
例如:
return new Retrofit
.Builder()
.baseUrl(baseUrl)
.client(okClient)
.addCallAdapterFactory(newCallAdaptorFactory)
.addConverterFactory(JacksonConverterFactory.create(objectMapper));
注意:为简单起见,我只是对待HTTP代码&gt; 404代码重试,请自行修改。
此外,如果http响应为200,则上述rx.retryWhen
将不会被调用,如果您坚持检查此类响应,则可以在.retryWhen之前添加rx.subscribeOn(...throw error...
。
答案 6 :(得分:1)
对于那些喜欢拦截器来处理重试问题的人 - 根据Sinan的回答,这是我提出的拦截器,它包括重试计数和退避延迟,并且只在网络可用时重试尝试,并且在请求未被取消时重试。 (仅处理IOExceptions(SocketTimeout,UnknownHost等))
pip install --upgrade pip
答案 7 :(得分:0)
它似乎将出现在API Spec的改进2.0中: https://github.com/square/retrofit/issues/297。 目前,最好的方法似乎是捕获异常并手动重试。
答案 8 :(得分:0)
正如docs中所述,更好的方法是在身份验证器中使用烘焙,例如: 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());
}
答案 9 :(得分:0)
我已经玩了很多这个问题试图找到重试改造请求的最佳方法。我正在使用Retrofit 2,所以我的解决方案是Retrofit 2.对于Retrofit 1,你必须使用Interceptor,就像这里接受的答案一样。 @joluet的答案是正确的,但他没有提到需要在.subscribe(onComplete,onError)方法之前调用重试方法。这非常重要,否则请求不会像@joluet中提到的@pocmo那样再次重试。这是我的例子:
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是我的RetrofitServiceProvider对象。
BTW:我使用的是Java 8,所以代码中有很多lambda表达式。
答案 10 :(得分:0)
只想分享我的版本。它使用rxJava retryWhen方法。我的版本每N = 15秒重试一次连接,并在互联网连接恢复时几乎立即发出重试。
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());
}}
你应该在之前使用它.subscribe如下:
.retryWhen(new RetryWithDelayOrInternet())
您应该手动更改isInternetUp字段
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();
}}
答案 11 :(得分:0)
在OkHttp 3.9.1上对我有用的解决方案(考虑此问题的其他答案):
@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;
}
答案 12 :(得分:0)
工作产品解决方案。
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;
}
答案 13 :(得分:0)
正如以前的用户所说,如果您使用的是 Retrofit2 call.clone 就足够了,但我还想添加一个简单的示例来说明它的外观:
public class CallbackImpl implements Callback<ResponseBody> {
private final Set<Integer> retryCode = new HashSet<>(Arrays.asList(503, 504));
int requestRetry = 1;
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if (response.code() == 201) {
// Object was created.
} else {
if (requestRetry != 0 && retryCode.contains(response.code())) {
call.clone().enqueue(this);
} else {
// Handle the error
}
}
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable throwable) {
if (throwable instanceof IOException) {
// Network failure
} else {
// Conversion Issue
}
}
}