首页 > 解决方案 > 未应用使用注释的改造拦截器的 OkHttp 调用超时

问题描述

我正在尝试使用 OkHttp 3.12.0 中最近添加的功能:完全操作超时。为此,我还依赖于Invocation改造 2.5.0 中的新类,它允许我检索方法注释。

注释是:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Timeout {

    int value();

    TimeUnit unit();

}

改造界面为:

public interface AgentApi {

    @Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
    @GET("something")
    Call<String> getSomething();

}

拦截器是:

class TimeoutInterceptor implements Interceptor {

    @NonNull
    @Override
    public Response intercept(@NonNull Chain chain) throws IOException {
        Request request = chain.request();
        final Invocation tag = request.tag(Invocation.class);
        final Method method = tag != null ? tag.method() : null;
        final Timeout timeout = method != null ? method.getAnnotation(Timeout.class) : null;
        if (timeout != null) {
            chain.call().timeout().timeout(timeout.value(), timeout.unit());
        }
        return chain.proceed(request);
    }

}

我已经.addInterceptor(...)在提供给 Retrofit Builder 的 OkHttpClient 中正确添加了 TimeoutInterceptor。

不幸的是,它并没有像我预期的那样工作。达到超时时呼叫不会失败?

虽然在使用拦截器中的链方法时它工作正常:

chain
  .withConnectTimeout(connect, unit)
  .withReadTimeout(read, unit)
  .withWriteTimeout(write, unit)

这是因为必须在呼叫入队之前设置呼叫超时吗?(并且拦截器在这个过程中被触发得太晚了?),或者这是别的什么?

标签: androidretrofitretrofit2okhttpokhttp3

解决方案


不幸的是,你是对的。这是因为OkHttpClient在执行拦截器链之前获得超时。如果您查看okhttp3.RealCallResponse execute()类中的方法,您会发现调度超时的行,并且在执行拦截器之前调用它。timeout.enter()OkHttpgetResponseWithInterceptorChain()

幸运的是,您可以为此编写解决方法 :) 放入您TimeoutInterceptorokhttp3包中(您可以在您的应用程序中创建该包)。这将允许您访问RealCall具有包可见性的对象。你的TimeoutInterceptor班级应该是这样的:

package okhttp3;

public class TimeoutInterceptor implements Interceptor {

   @Override
   public Response intercept(Chain chain) throws IOException {
       Request request = chain.request();
       Invocation tag = request.tag(Invocation.class);
       Method method = tag != null ? tag.method() : null;
       Timeout timeout = method != null ? method.getAnnotation(Timeout.class) : null;
       if (timeout != null) {
           chain.call().timeout().timeout(timeout.value(), timeout.unit());
           RealCall realCall = (RealCall) chain.call();
           realCall.timeout.enter();
       }
       return chain.proceed(request);
   }
}

timeout.enter()解决方法是在更改超时后再次执行。所有的魔法都发生在一行中:

RealCall realCall = (RealCall) chain.call();
realCall.timeout.enter();

祝你好运!


推荐阅读