Okhttp学习笔记(三)核心之二:okHttp拦截器链

学习资料:

BAT大牛带你深度剖析Android 十大开源框架

上一篇文章:
Okhttp学习笔记(一) 初步认识&& 源码浅析
Okhttp学习笔记(二)核心之一:Dispatcher

拦截器链

前面可以看到,获取的response都是通过 getResponseWithInterceptorChain这个方法获取的,okhttp通过拦截器链,一步步构建返回请求结果。


 // AsyncCall 方法
 // 异步时执行的execute方法
   @Override protected void execute() {
      ...
      try {
        Response response = getResponseWithInterceptorChain(); // 调用方法获取结果
        signalledCallback = true;
        responseCallback.onResponse(RealCall.this, response);
      } catch (IOException e) {
        ...
      } finally {
        client.dispatcher().finished(this);
      }
  }

   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())); // 缓存拦截器
    interceptors.add(new ConnectInterceptor(client)); // 连接拦截器 建立可用连接,是下一个拦截器的基础
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    // 将HTTP请求写入网络IO流,并从IO流中读取服务端返回的数据
    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);
  }

系统默认的五个拦截器:
retryAndFollowUpInterceptor -> BridgeInterceptor -> CacheInterceptor -> ConnectInterceptor -> CallServerInterceptor;
最后把拦截器的列表传入构建了RealInterceptorChain,并调用了它的proceed方法;

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    ...
    // 构建下一个拦截器链 注意这里是 index + 1 
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout); 
    Interceptor interceptor = interceptors.get(index);
    // 将下一个拦截器链传入
    // 一层层回调获取response(也是执行了 proceed方法)
    Response response = interceptor.intercept(next); 
    ...
   }

小结:

1、构建一个拦截器的list,包括用户自定义的和默认的五个拦截器
2、构建RealInterceptorChain,执行它的proceed方法
3、在proceed里构建下一个RealInterceptorChain(index为下一个的坐标),并调用当前interceptor的intercept方法获取结果并返回

在这里插入图片描述

小结二:

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

五个内部的拦截器

RetryAndFollowUpInterceptor

  • 创建StreamAllocation 对象
  • 调用RealInterceptorChain.proceed(…) 进行网络请求
  • 根据异常结果或者相应结果判断是否要进行网络请求
  • 调用下一个拦截器,对response进行处理,返回给上一个拦截器

StreamAllocation

  • 分配Stream(建立执行http请求所需要的网络组件)
  • 获取连接服务端的connection,获取和服务端进行数据传输的输入输出流
  • 通过RealInterceptorChain.proceed(…) 传递
     while(true){
         if (++followUpCount > MAX_FOLLOW_UPS) { // 20次
           streamAllocation.release();
           throw new ProtocolException("Too many follow-up requests: " + followUpCount);
        }
     }
     

BridgeInterceptor

  • 添加头部信息等操作,将request请求转化为能够进行网络访问的请求
  • 将这个符合条件的Request进行网络请求
  • 将网络请求返回的响应Response进行处理(压缩、解压等),转化为用户可用的Response

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

      ...
      
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }

    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    // 保持长连接
    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive"); 
    }
    
    // 发起网络请求
    Response networkResponse = chain.proceed(requestBuilder.build()); // 获取Response 
 
    // 得到最终的response
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
    
    // 判断是否支持Gzip压缩
    // 判断是否相应头是否支持Gzip压缩,(响应体内容是否经过了Gzip压缩)
    // 判断Http头部是否有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)));
    }

缓存策略

使用缓存

client = new OkHttpClient.Builder()
                    .connectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
                    .readTimeout(READ_TIMEOUT_MILLIS,TimeUnit.MILLISECONDS)
                    .writeTimeout(WRITE_TIMEOUT_MILLIS,TimeUnit.MILLISECONDS)
                    .cache(new Cache(new File("cache"),24*1024*1024))
                    .build();

okhttp缓存核心:DiskLruCache

  • okhttp维护了一个清理的线程池,由它进行着对缓存文件的清理和管理工作

Cache类

Cache.put方法

  @Nullable CacheRequest put(Response response) {
  
    String requestMethod = response.request().method();
    
    ...
   
    // 只缓存GET方法
    if (!requestMethod.equals("GET")) {
      return null;
    }

    // Entry:需要缓存信息的包装类 包括url、请求头部、响应行、响应头部
    
    Entry entry = new Entry(response);
    DiskLruCache.Editor editor = null;
    try {
      // 创建editor,cache为DiskLruCache
      // 传入url的MD5加密
      editor = cache.edit(key(response.request().url())); 
      if (editor == null) {
        return null;
      }
      // 开始缓存
      entry.writeTo(editor);
      
      // CacheRequestImpl:缓存响应主体
      return new CacheRequestImpl(editor);
    } catch (IOException e) {
      abortQuietly(editor);
      return null;
    }
  }

Cache.get方法

  @Nullable Response get(Request request) {
    
    // 解密得到key值
    String key = key(request.url()); 
    // 缓存“快照”
    DiskLruCache.Snapshot snapshot;
    Entry entry;
    try {
      // 如果没有这个“快照”就返回为空
      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 response = entry.response(snapshot);

    // 判断是否匹配
    if (!entry.matches(request, response)) {
      Util.closeQuietly(response.body());
      return null;
    }

    return response;
  }
    // Entry.response 方法
    public Response response(DiskLruCache.Snapshot snapshot) {
      String contentType = responseHeaders.get("Content-Type");
      String contentLength = responseHeaders.get("Content-Length");
      // 恢复缓存的request
      Request cacheRequest = new Request.Builder()
          .url(url)
          .method(requestMethod, null)
          .headers(varyHeaders)
          .build();
      return new Response.Builder()
          .request(cacheRequest)
          .protocol(protocol)
          .code(code)
          .message(message)
          .headers(responseHeaders)
          // CacheResponseBody 用来对响应体的读取	
          .body(new CacheResponseBody(snapshot, contentType, contentLength))
          .handshake(handshake)
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(receivedResponseMillis)
          .build();
    }
发布了27 篇原创文章 · 获赞 6 · 访问量 1649

猜你喜欢

转载自blog.csdn.net/weixin_41802023/article/details/102929085
今日推荐