Android面试有迹可循(一)OkHttp3.9拦截器原理与区别

接上回 传送门

上回我们讲到,OkHttp的请求过程中有个非常重要的东西-“拦截器”,而且拦截器又分为interceptors和networkInterceptors两种,那它们具体有何区别呢?又要怎么来使用?现在来一探究竟

拦截器工作原理

在弄清楚区别之前,要先知道他们工作的原理,还是来到RealCall.execute方法里面的getResponseWithInterceptorChain:

RealCall.getResponseWithInterceptorChain

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());//自定义的拦截器
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }
}

其实可以看出来,这个方法主要就是添加各种interceptor,然后文章最开始提到的第一个interceptors其实是自定义的拦截器,添加到了最前面,然后依次添加了四种拦截器过后,第五个是networkInterceptors,最后一共加了七种拦截器。

拦截器责任链

在这之后他们是怎么执行的?这就要说说Interceptor.Chain这个类了,其实从这个getResponseWithInterceptorChain方法名也可知一二,划重点了!这里用到了责任链模式,在请求的各个阶段都有相应的拦截器来负责,这些拦截器组成了一个链。在这里首先实例化成了RealInterceptorChain,来到这个类的构造方法来看看:

  public RealInterceptorChain(List interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
      EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
    this.call = call;
    this.eventListener = eventListener;
    this.connectTimeout = connectTimeout;
    this.readTimeout = readTimeout;
    this.writeTimeout = writeTimeout;
  }

这里主要是初始化了各种属性,这里面的参数大多数和OkHttp的属性无异,但需要注意的是这里的interceptors不同于OkHttp构造里面的那个interceptors,这里是表示刚才组成的所有拦截器的数组,包含interceptors和networkInterceptors在内的七种拦截器,然后index这个属性初始化为“0”。

接下来在getResponseWithInterceptorChain最后一句执行了它的proceed方法,在这个方法里有很多抛出异常的语句,这里不去深究,主要看看中间主要的一两句:

RealInterceptorChain.proceed

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // 重点!!!主要就看看这几句
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
  }

划重点!在这里就到了最核心的几句了

首先:又实例化了一个RealInterceptorChain,取名为next(从名字真的可以看出很多蹊跷),构造参数没变化,只是把index+1

然后:从interceptors数组中用index来依次取出Interceptor(每次执行index都会+1,所以是依次取出来);

最后:调用interceptor.intercept(next),把next传入调用intercept方法

完;

这里如果直接点进去intercept方法会发现,这个方法在接口中:

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

所以要去它的具体实现类去看看这个方法,从一开始的getResponseWithInterceptorChain方法可知,我们首先加入的拦截器是自定义的interceptors,第二个是retryAndFollowUpInterceptor,这里我们对拦截器的具体作用按下不表,先看看责任链的执行,找到RetryAndFollowUpInterceptor的intercept方法,方法很长,只截取了关于chain操作的部分:

RetryAndFollowUpInterceptor.intercept(Chain chain)

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    
    response = realChain.proceed(request, streamAllocation, null, null);
}

这个方法里面会对一些参数做修改,又调用了proceed方法,在proceed方法里我们知道index又会+1,然后取出下一个拦截器并且传入修改后的参数。这就完成了这个链式结构的运转,一步一步的往下传递,然后到了最后又依次返回Response

各种拦截器

看到这里,不禁会问,上面看到的那么多种拦截器到底分别是用来干啥的呢,在这里来总结一下(来自网络,只是做个归纳):

RetryAndFollowUpInterceptor

用来实现连接失败的重试和重定向

BridgeInterceptor

用来修改请求和响应的 header 信息

CacheInterceptor

用来实现响应缓存。比如获取到的 Response 带有 Date,Expires,Last-Modified,Etag 等 header,表示该 Response 可以缓存一定的时间,下次请求就可以不需要发往服务端,直接拿缓存的

ConnectInterceptor

用来打开到服务端的连接。其实是调用了 StreamAllocation 的newStream 方法来打开连接的。建联的 TCP 握手,TLS 握手都发生该阶段。过了这个阶段,和服务端的 socket 连接打通

CallServerInterceptor

用来发起请求并且得到响应。上一个阶段已经握手成功,HttpStream 流已经打开,所以这个阶段把 Request 的请求信息传入流中,并且从流中读取数据封装成 Response 返回

上一张图,看着直观一点(话说这个其实看着跟Android的点击事件分发机制很像啊)

Android面试有迹可循(一)OkHttp3.9拦截器原理与区别_第1张图片
Interceptor

最后来说说这个面试题

看到这里,对OkHttp的拦截器机制也了解了个大概,但是开篇提到的那个问题的答案到底是什么呢?

interceptors和networkInterceptors分别位于拦截器链的第一个和第六个,从每个拦截器的作用大致猜到一些。interceptors是肯定每次都会执行的,但是,在networkInterceptors之前有个ConnectInterceptor,这个拦截器的作用是用于建立跟服务器的连接。那如果我们现在设备离线,直接读取缓存呢?对,那这样的话networkInterceptors就不会执行了。

当然,这只是他们的一个最容易理解的区别,这个问题在OkHttp的官方文档已经给了我们答案 OkHttp文档中对于这两个拦截器的解释,下面来结合资料做个翻译

Android面试有迹可循(一)OkHttp3.9拦截器原理与区别_第2张图片
OkHttp文档图片

Application interceptors

  • Don't need to worry about intermediate responses like redirects and retries.
  • Are always invoked once, even if the HTTP response is served from the cache.
  • Observe the application's original intent. Unconcerned with OkHttp-injected headers like If-None-Match.
  • Permitted to short-circuit and not call Chain.proceed().
  • Permitted to retry and make multiple calls to Chain.proceed().
  • 不需要担心中间过程的响应,如重定向和重试
  • 只会被调用一次,即使这个响应是从缓存中获取的
  • 只关注最原始的请求, 不关心OkHttp注入的头信息如: If-None-Match
  • 可以中断调用过程,有权决定是否要执行Chain.proceed()
  • 允许重试,可以多次调用Chain.proceed()

Network Interceptors

  • Able to operate on intermediate responses like redirects and retries.

  • Not invoked for cached responses that short-circuit the network.

  • Observe the data just as it will be transmitted over the network.

  • Access to the Connection that carries the request.

  • 能够操作中间过程的响应,如重定向和重试.

  • 当返回缓存时不被调用.(也就是我们刚才举的例子)

  • 观察在网络上传输的数据变化,比如重定向

  • 携带请求来访问连接(这里已经建立了连接)

结题

看懂了拦截器其实也就是看懂了OkHttp,它的整个工作原理其实就是以拦截器责任链模式为核心,这种模式之下,我们可以很方便的来定制我们自己拦截器,比如可以改变请求头,处理缓存等等等等,不知道还有没有什么关于OkHttp的知识点呢?

你可能感兴趣的:(Android面试有迹可循(一)OkHttp3.9拦截器原理与区别)