OKHttp开源框架学习四:拦截器

目录

系列文章:

参考文章:

Okhttp拦截器:

拦截器在哪儿?

源码分析:

每个拦截器的作用:

Application Interceptor:

NetwrokInterceptor:

RetryAndFollowInterceptor:

BridgeInterceptor:

CacheInterceptor:

ConnectInterceptor:

CallServerInterceptor:

拦截器创建总结:

拦截器创建总结2:


系列文章:

OKHttp开源框架学习一:同步请求总结

OKHttp开源框架学习二:异步请求总结

OKHttp开源框架学习三:任务调度核心类Dispatcher

OKHttp开源框架学习四:拦截器

OKHttp开源框架学习五:拦截器之RetryAndFollowUpInterceptor

OKHttp开源框架学习六:拦截器之BridgeInterceptor

OKHttp开源框架学习七:缓存策略源码分析

OKHttp开源框架学习八:拦截器之CacheInterceptor

OKHttp开源框架学习九:拦截器之ConnectInterceptor

OKHttp开源框架学习十:ConnectionPool连接池

OKHttp开源框架学习十一:拦截器之CallServerInterceptor

Okhttp总结

参考文章:

OKHttp源码解析 4 - 1:拦截器源码分析、拦截器链

Okhttp拦截器:

官网:拦截器是Okhttp中提供的一种强大机制,它可以实现网络监听、请求、以及响应重写、请求失败重试等功能。

拦截器在哪儿?

这是Okhttp框架提供给我们的拦截器,分为两种,一种是Application拦截器,另一种是Network拦截器。而我们需要关注的就是位于图中绿色的Okhttp core,拦截器就在这里面。

下图是Okhttp core的内部:

可以看到,是一个拦截器链。

源码分析:

拦截器是不区分同步和异步请求的,我们以同步请求为例:

public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();  //位置 1
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }

这个是不是很熟悉,我们主要来看位置1:

Response result = getResponseWithInterceptorChain();

点进去:

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //添加用户自定义拦截器
    interceptors.addAll(client.interceptors());
    //重试和失败重定向拦截器
    interceptors.add(retryAndFollowUpInterceptor);
    //桥接拦截器,处理一些必须的请求头信息的拦截器
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //缓存拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //连接拦截器:建立可用的连接,是CallServerInterceptor的基本
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    //将http请求写进io流当中,并且从io流中读取response
    interceptors.add(new CallServerInterceptor(forWebSocket));

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

    return chain.proceed(originalRequest);
  }

看到没,这么多的intercepter!!

源码中前面部分主要提到了五个拦截器,分别是: 
- RetryAndFollowUpInterceptor:重试和失败重定向拦截器 
- BridgeInterceptor:桥接拦截器,处理一些必须的请求头信息的拦截器 
- CacheInterceptor:缓存拦截器,用于处理缓存 
- ConnectInterceptor:连接拦截器,建立可用的连接,是CallServerInterceptor的基本 
- CallServerInterceptor:请求服务器拦截器将 http 请求写进 IO 流当中,并且从 IO 流中读取响应 Response

后半部分很重要了,先是初始化 RealInterceptorChain,然后调用 proceed() 方法,我们跟踪这个方法,进入到 RealInterceptorChain 的源码中,最终定位到 RealInterceptorChain 中的 proceed() 方法

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {

    //......省略部分代码,只显示核心代码

    // Call the next interceptor in the chain.
    //调用拦截器链中的下一个拦截器,注意 index + 1 参数
    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);

    //......省略部分代码,只显示核心代码

    return response;
  }

从 RealInterceptorChain 的 proceed() 方法的核心代码中,我们可以看到。在这个方法中,主要就是新建了一个新的 RealInterceptorChain,只是在第五个 index 参数中,传入了 index + 1,确保调用到的是下一个拦截器,这也是 OKhttp 中拦截器链的关键所在。然后获取当前的拦截器,调用 Interceptor 的 intercept() 方法。我们可以查看上面五个拦截器的 intercept() 方法,可以发现,这五个拦截器的 intercept() 方法中,无一例外都调用了 chain.proceed() 方法,然后 proceed() 方法又会去调用下一个拦截器的 intercept()。Response也是由这条链依次向上返回。由此就形成了一个完整的链条。

从这里我们可以总结得到:拦截器通过 intercept() 调用拦截器链的 proceed() 方法,由此创建下一个拦截器链,然后又调用下一个拦截器的 intercept() 方法。从而形成一个调用链,依次获取 Response 返回。看下面的调用流程图

 

每个拦截器的作用:

Okhttp拦截器Interceptor学习和使用

Application Interceptor:

我们可以自定义设置 Okhttp 的拦截器之一。

从流程图中我们可以看到一次网络请求它只会执行一次拦截,而且它是第一个触发拦截的,这里拦截到的url请求的信息都是最原始的信息。所以我们可以在该拦截器中添加一些我们请求中需要的通用信息,打印一些我们需要的日志。

当然我们可以定义多个这样的拦截器,一个处理 header 信息,一个处理 接口请求的 加解密 。

NetwrokInterceptor:

NetwrokInterceptor 也是我们可以自定义的拦截器之一。

它位于倒数第二层,会经过 RetryAndFollowIntercptor 进行重定向并且也会通过 BridgeInterceptor 进行 request请求头和 响应 resposne 的处理,因此这里可以得到的是更多的信息。在打印结果可以看到它内部重定向操作和失败重试,这里会有比 Application Interceptor 更多的日志。

RetryAndFollowInterceptor:

RetryAndFollowUpInterceptor 的作用,看到该拦截器的名称就知道,它就是一个负责失败重连的拦截器。它是 Okhttp内置的第一个拦截器,通过 while (true) 的死循环来进行对异常结果或者响应结果判断是否要进行重新请求。

.

@Override public Response intercept(Chain chain) throws IOException {

    //......

    //用于服务端数据传输的输入输出流,依次传到 ConnectInterceptor 用于 http 请求,本 intercepter 没有用到,核心点!!
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    this.streamAllocation = streamAllocation;

    //......

    while (true) {
      Response response;
      boolean releaseConnection = true;
      try {
        //调用拦截器链,执行下一个拦截器
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        //......
        releaseConnection = false;
        continue;
      } catch (IOException e) {
        //......
        releaseConnection = false;
        continue;
      } finally {
        // We're throwing an unchecked exception. Release any resources.
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

      //......

      //对重试次数进行判断,当重试次数大于20次时,释放掉streamAllocation
      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      //......

    }
  }

这里只展示了部分核心代码,详细代码可查看源码。

前面我们提到,OKHttp 请求是通过 RealInterceptorChain 串联起所有的拦截器,然后依次往回返回 Response,只有所有拦截器正常返回,我们的请求才算成功。但是我们知道,网络在请求过程中是不稳定的,可能会存在不同程度的问题。这个时候 Response 返回码就不一定是200了,Response 也就不能用了。那我们这个拦截器就对这种情况进行了精密的部署,具体的逻辑在代码段的 while 循环中。我们只讲一下两处注释的地方。第一个注释位置:很明显,这里通过调用 chain.proceed() 方法去调用下一个拦截器,并获取 Response;第二次注释位置:这里对重试次数做了限制,避免无限制的重试,能跳出整个逻辑。

另外这段源码中,还创建了一个很重要的内容,那就是 
StreamAllocation,它用于收集服务端数据传输时的输入输出流,在本拦截器中创建,但是会依次传到 ConnectInterceptor 用于 http 请求。

RetryAndFollowUpIntercepter 总结: 
1. 创建StreamAllocation对象 
2. 调用RealInterceptorChain.proceed(…)进行网络请求 
3. 根据异常结果或者响应结果判断是否要进行重新请求 
4. 调用下一个拦截器,对 Response 进行处理,返回给上一个拦截器

BridgeInterceptor:

BridgeInterceptor 拦截器主要负责对头部信息进行添加(长度、编码方式、压缩等),看一下源码

    //省略部分源码
    //调用拦截器的 proceed() 方法,请求服务器,获取 Response。
    Response networkResponse = chain.proceed(requestBuilder.build());

    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

    //判断 Response 是否进行了 Gzip 压缩,是则对 body 体进行解压。
    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

前面我省略了一段源码,其实这一段主要就是对请求头进行了处理,大家可以去源码查看。我这里贴上的主要就是两段,一段就是前面一直提到的,调用拦截器的 proceed() 方法,执行下一个拦截器,获取 Response。另外一段就是判断 Response 是否进行了 Gzip 压缩,是则对 body 体进行解压。

BridgeInterceptor 总结: 
1. 负责将用户构建的一个 Request 请求转化为能够进行网络访问的请求 
2. 将这个符合网络请求的 Request 进行网络请求 
3. 将网络请求回来的响应 Response 转化为用户可用的 Response

CacheInterceptor:

CacheInterceptor 根据 OkHttpClient 对象的配置以及缓存策略对请求值进行缓存。

首先我们在解析缓存拦截器源码前,我们先了解一下 OKHttp 缓存的使用方法。

public void cacheRequest() {
        //设置缓存目录、大小
        OkHttpClient client = new OkHttpClient.Builder()
                .cache(new Cache(new File("cache"), 24 * 1024 * 1024)).build();
        Request request = new Request.Builder().url("http://www.baidu.com").build();

        Call call = client.newCall(request);
        try {
            call.execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

这里我们看到了一个新建的 Cache 类,传入缓存目录和缓存大小参数。跟踪进去查看源码发现,这个 Cache 类其实本质就是 DiskLruCache,也就是说,OKHttp 的缓存策略其实就是用 DiskLruCache 实现的。在分析 CacheInterceptor 前,我们先主要讲一下这个 Cache 类,主要就是 Cache 类的 put() 和 get() 两个方法。

@Nullable CacheRequest put(Response response) {
    String requestMethod = response.request().method();

    //判断是否是无效的缓存
    if (HttpMethod.invalidatesCache(response.request().method())) {
      try {
        //是无效缓存则移除缓存
        remove(response.request());
      } catch (IOException ignored) {
        // The cache cannot be written.
      }
      return null;
    }
    //不缓存非 GET 方法的响应
    if (!requestMethod.equals("GET")) {
      // Don't cache non-GET responses. We're technically allowed to cache
      // HEAD requests and some POST requests, but the complexity of doing
      // so is high and the benefit is low.
      return null;
    }

    if (HttpHeaders.hasVaryAll(response)) {
      return null;
    }
    // Entry 类:封装了各种属性,用于数据缓存
    Entry entry = new Entry(response);
    DiskLruCache.Editor editor = null;
    try {
      //先把 URL 经 MD5 加密,获取十六进制字符串,然后根据这个字符串创建 Editor
      editor = cache.edit(key(response.request().url()));
      if (editor == null) {
        return null;
      }
      //把 Request 与 Response 的头部写入缓存
      entry.writeTo(editor);
      //返回 CacheRequestImpl,CacheRequestImpl 主要用于缓存 body 信息
      return new CacheRequestImpl(editor);
    } catch (IOException e) {
      abortQuietly(editor);
      return null;
    }
  }

put 方法总结: 
- 判断是否是有效的缓存 
- 判断是否是 GET 请求。注意:源码注释中提到,对非 GET 请求技术上能做到缓存,但是复杂性比较高,而且取得的收益很小,所以缓存只针对 GET 请求 
- 使用 DiskLruCache.Editor 创建用于数据缓存的 Entry 类,缓存Request 和 Response 的头部信息,然后返回 CacheRequest 的实现类,用于缓存 body 信息。具体 body 缓存的实现位于 CacheIntercepor 中。
 

@Nullable Response get(Request request) {
    //对 URL 进行MD5加密,获取加密后的 16进制字符串
    String key = key(request.url());
    //缓存快照
    DiskLruCache.Snapshot snapshot;
    Entry entry;
    try {
      //根据 key 值从缓存获取缓存快照
      snapshot = cache.get(key);
      if (snapshot == null) {
        return null;
      }
    } catch (IOException e) {
      // Give up because the cache cannot be read.
      return null;
    }

    try {
      //赋值 Entry 对象
      entry = new Entry(snapshot.getSource(ENTRY_METADATA));
    } catch (IOException e) {
      Util.closeQuietly(snapshot);
      return null;
    }
    //从 Entry 中获取 Response
    Response response = entry.response(snapshot);
    //匹配 Request 和 Response,如果不匹配,返回 null
    if (!entry.matches(request, response)) {
      Util.closeQuietly(response.body());
      return null;
    }

    return response;
  }

get方法总结:

根据加密的 key 值,从缓存中获取缓存快照
创建 Entry 对象,从 Entry 中获取 Response
检测 Request 和 Response 是否匹配,不匹配返回 null,匹配返回 Response
在了解了 Cache 类的存、取方法后,我们再来分析 CacheInterceptor。先贴上源码:
 

@Override public Response intercept(Chain chain) throws IOException {
    //获取缓存,可能为空
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    //获取缓存策略类,CacheStrategy 内部维护着一个网络请求 Request 和一个缓存 Response,目的是:判断是使用网络还是缓存,还是两者都用
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;

    //......

    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
      //当 networkRequest 和 cacheResponse 都为空时,构建一个错误码为504的 Response
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // If we don't need the network, we're done.当有缓存但是没有网络的时候,直接使用缓存的结果
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
      //获取 Response,交给下一个拦截器
      networkResponse = chain.proceed(networkRequest);
    } finally {
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // If we have a cache response too, then we're doing a conditional get.
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        //当响应码为304,从缓存中获取数据
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();

        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
      //http 头部是否有响应体,并且缓存策略可以被缓存
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.写入 Response 到缓存中
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }
      //判断 Request 方法是否是有效的缓存,是的话,移除这个缓存。
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }

 详情查看上面源码的注释。从源码中我们可以发现,OKHttp 的缓存主要是根据 CacheStrategy 这个缓存策略类来处理的,它决定了是使用网络还是缓存,又或是两者都用。源码最后可以看到,当有响应体且可以缓存时,缓存当前数据。然后还判断了,当请求是无效缓存时,要清除这个缓存。CacheInterceptor 缓存拦截器的整个逻辑就是这样了,重点在 CacheStrategy 这个缓存策略类。
 

ConnectInterceptor:

ConnectInterceptor 在 OKHTTP 底层是通过 SOCKET 的方式于服务端进行连接的,并且在连接建立之后会通过 OKIO 获取通向 server 端的输入流 Source 和输出流 Sink。

CallServerInterceptor:

CallServerInterceptor 在 ConnectInterceptor 拦截器的功能就是负责与服务器建立 Socket 连接,并且创建了一个 HttpStream 它包括通向服务器的输入流和输出流。而接下来的 CallServerInterceptor 拦截器的功能使用 HttpStream 与服务器进行数据的读写操作的。

拦截器创建总结:

  • 创建一系列拦截器,并将其放入一个拦截器list中;
  • 创建一个拦截器链RealInterceptorChain,并执行拦截器链的proceed方法;

拦截器创建总结2:

  • 在发起请求前对request进行处理
  • 调用下一个拦截器,获取response
  • 对response进行处理,返回给上一个拦截器

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值