okhttp源码深入学习

okhttp是现在比较流行的基础网络访问框架,因为有比他更加好用的Retrofit,okhttp用起来确实也很方便,基本可以满足我们日常开发的项目需求,如果实在满足不了,也可以做自己的定制。okhttp的源码也是面试中比较经常问到的,特别是拦截器这部分,好几次都问到了,当时只有很尴尬的回答不知道。作为一名有追求的程序员,也必须了解它的源码。其实好久以前就在看了,只不过当时能力有限,拦截器部分一直看不懂具体干了什么事情,虽然整个大致执行过程还是很容易看懂的,但是一直没写,最近看了下拦截器部分源码,也看了网上很多大牛的博客,感觉总算有点收获,所以打算这段时间写篇关于它的源码总结。

1.基本实现流程


okhttp基本使用流程比较简单,发起异步请求过程

private void testOkHttp() {

OkHttpClient okHttpClient = getOkHttpClient();    //构造 OkHttpClient
Request request = new Request.Builder()
        .get()       //Method GET
        .url("www.baidu.com")
        .build();    //构造请求信息

okHttpClient.newCall(request)
        .enqueue(new Callback() {    //发起异步请求
            @Override
            public void onResponse(final Call call, final Response response) throws IOException {
                //成功拿到响应
                int code = response.code();
                ResponseBody body = response.body();
                //服务器返回的结果信息
                String string = body.string();
            }

            @Override
            public void onFailure(final Call call, final IOException e) {
                e.printStackTrace();
            }
        });
    }

发起一个请求基本分三步走:

1 .创建okhttpclient对象
2 .通过build模式创建request对象, 封装网络请求参数
3 .okhttpclient.newCall(request)会返回一个realCall对象,然后拿它去发网络请求。

接下来就从基本请求流程,看它内部的执行流程

首先看下okHttpClient.newCall(request)

 @Override public Call newCall(Request request) {
   return RealCall.newRealCall(this, request, false /* for web socket */);
 }

//其实返回了一个RealCall对象
 static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
   // Safely publish the Call instance to the EventListener.
   RealCall call = new RealCall(client, originalRequest, forWebSocket);
   call.eventListener = client.eventListenerFactory().create(call);
   return call;
 }

内部创建了一个RealCall对象,它是okhttp3.call接口的一个实现

public interface Call extends Cloneable {
  //返回这个请求关联的 Request 对象
  Request request();
  //立即执行请求,阻塞等待拿到响应
  Response execute() throws IOException;
  //请求入队,异步执行
  void enqueue(Callback responseCallback);
  //取消一个请求
  void cancel();
  boolean isExecuted();
  boolean isCanceled();
  Call clone();
  interface Factory {
    Call newCall(Request request);
  }
}

它的具体实现RealCall表示一个准备好被执行的请求,并且只能被执行一次

  if (executed) throw new IllegalStateException("Already Executed");
  executed = true;

接着就拿着创建好的RealCall对象去发网络请求,enqueue为异步请求,execute为同步请求

从异步请求开始说起

  //RealCall	
  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback));//1
  }
  
  //Dispatcher
  synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
    runningAsyncCalls.add(call);
    executorService().execute(call);
  } else {
    readyAsyncCalls.add(call);
  }
}

看注释1处responseCallback被封装到AsyncCall中了,然后将结果分发给了Dispatcher的enqueue()方法,在该方法中将call加入队列。

首先看AsyncCall方法实现,它是RealCall的一个内部类

//NameRunnable 是一个runable,它的父接口会执行run()方法,里面会调用execute()方法,也就是AsyncCall的execute()方法。
final class AsyncCall extends NamedRunnable {
...
    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        //通过一个拦截器链去获取一个response
        Response response = getResponseWithInterceptorChain();
        //是否取消了请求,如果取消了,就会回调onFailure,并返回一个IOExecption异常
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          //请求结果成功就会回调onResponse,并且将response返回回去。
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        ...
      } finally {
        //最后不管结果成功还是失败都会调用Dispatcher的finished去移除队列中的请求
        client.dispatcher().finished(this);
      }
    }
  }

注释写的比较清楚了,接着看另一个比较重要的类Dispatcher,它是OkHttp的调度器,负责任务的添加和移除相关操作。

public final class Dispatcher {
  private int maxRequests = 64; //同时发送最多64个请求
  private int maxRequestsPerHost = 5; //同一host最多发送5个请求
  private @Nullable Runnable idleCallback;
  private @Nullable ExecutorService executorService;  //将要创建异步任务的线程池
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>(); //等待被执行的异步请求队列
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>(); //正在运行的异步任务请求队列(包括已经取消还没有完成的)
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); //正在运行的同步请求队列
  //线程池部分的初始化
  public synchronized ExecutorService executorService() {
  if (executorService == null) {
   executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
       new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
  }
  return executorService;
}
  ...
}

从Dispatcher调度器可以知道以下信息:

一个OkhttpClient只有一个Dispatcher,也就是说最大请求个数为64
同一host最大请求个数为5个
有三个请求队列保存同步请求,和异步请求
默认线程池核心线程数量为 0,最多数量不限制,消息队列为 SynchronousQueue,因此有请求时会将任务直接提交给线程处理

tip: 关于线程池相关部分,可以看我之前写的线程池文章

接着回到之前Dispatcher.enqueue()这个地方

 synchronized void enqueue(AsyncCall call) {
   if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
     runningAsyncCalls.add(call);
     executorService().execute(call);
   } else {
     readyAsyncCalls.add(call);
   }
 }

首先会判断正在运行的异步请求个数是否超过64个,并且同一Host的请求个数是否超过5个,如果都不满足就将任务添加进正在运行的队列中,然后添加进线程池,否则将任务添加进准备执行的线程池中。线程池就会执行AsyncCall的run方法,在里面会执行execute方法,这个方法刚刚已经分析了。

异步方法已经看了,然后看下同步请求方法execute()

@Override 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 和刚刚的一样这是个重点,后面在核心机制里面再去学习
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } catch (IOException e) {
    eventListener.callFailed(this, e);
    throw e;
  } finally {
    //执行完之后就通过finish移除请求对象
    client.dispatcher().finished(this);
  }
}

同步的看完之后,通过一张图来看下基本执行流程

在这里插入图片描述

okhttp一个请求设计几个类,每个类都有自己的职责,这恰恰是我们平时开发应该学习的,对于我们系统中某一个模块,就应该做到责任分明,细化粒度,把具体属于某一阶段的功能、信息、方法都封装到单独的类中,然后逐层添加信息。

2.核心机制学习


基本流程中有个部分没有涉及到,那就是拦截器和拦截器链,接下来就是展示技术的时候了

在这里插入图片描述

由于一个网络请求设计的功能和细节非常多,如果它的所有实现都包含在一个类中,那么代码将会异常臃肿,所以okhttp设计者就将单独的功能封装到各自的拦截器中,通过一个拦截器链连接各个拦截器

入口方法是这个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()));
  //和服务器建立通道连接拦截器
  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);
}

tip: 关于两种自定义拦截器的区别部分,可以看这里

拦截器和链的接口都定义在一个类中

public interface Interceptor {
  //拦截器需要实现的方法	
  Response intercept(Chain chain) throws IOException;
  interface Chain {
  	//当前的请求信息
    Request request();
    //处理请求,返回结果
    Response proceed(Request request) throws IOException;
    //执行当前请求的连接
    @Nullable Connection connection();
    Call call();
    int connectTimeoutMillis();
    Chain withConnectTimeout(int timeout, TimeUnit unit);
    int readTimeoutMillis();
    Chain withReadTimeout(int timeout, TimeUnit unit);
    int writeTimeoutMillis();
    Chain withWriteTimeout(int timeout, TimeUnit unit);
  }
}

okhttp比较好用同时比较牛的地方就是拦截器和拦截器链的设计,通过一个拦截器链将所有拦截器连接,而我们也可以自定义拦截器去给请求添加Header,也可以去打印响应结果,这是我们在项目中经常干的事情。接下来就看下拦截器的接口实现RealInterceptorChain是如何串联起所有的拦截器。

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;  //重点关注1
  private final int index; //重点关注2

...

  @Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }

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

    // Call the next interceptor in the chain.
    //创建下一个拦截器链
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
	//拿到当前拦截器List中的拦截器
    Interceptor interceptor = interceptors.get(index);
    //将下一个拦截器链传入当前拦截器的intercept方法
    Response response = interceptor.intercept(next);

 ...

    return response;
  }

}

RealInterceptorChain中我们主要做了三件事情

1 .创建了下一个索引的拦截器链,第一个索引默认为0
2 .获取当前索引下面的拦截器
3 .执行当前获取到的拦截器里面的intercept方法,参数为下一个拦截器链。

这里设计得确实神奇,如果文字的不好理解,可以参考拭心大佬的这张图:

在这里插入图片描述

每个拦截器内部会执行chain.proceed,这样就可以将上一个和下一个拦截器链连接起来。

整体执行流程大致是这样的:首先创建一个RealInterceptorChain,这个拦截器会传一个索引0,在proceed中接着会获取当前索引下的拦截器,然后索引自加,创建下一个拦截器链,接着执行拦截器的intercept方法,参数为下一个拦截器链的引用,拦截器里面会执行chain.proceed方法,接着就会递归调用下一个拦截器链,最后一个拦截器不会执行chain.proceed,到最后一个执行完就会将response返回,然后依次返回,有点类似aop编程,用皇叔的图比较直观
在这里插入图片描述

这部分分析完了,接着主要看看每个拦截器的具体作用

在这里插入图片描述

RetryAndFollowUpInterceptor:取消、失败重试、重定向
BridgeInterceptor:把用户请求转换为 HTTP 请求;把 HTTP 响应转换为用户友好的响应
CacheInterceptor:读写缓存、根据策略决定是否使用
ConnectInterceptor:实现和服务器建立连接
CallServerInterceptor:实现读写数据

这几个拦截器掌握清楚了,就基本掌握了okhttp的核心了。

1.) RetryAndFollowUpInterceptor

这个拦截器的实例化是在RealCall中,在这里面

retryAndFollowUpInterceptor.isCanceled()

判断任务是否取消,接着看下intercept方法

@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    //获取传过来的拦截器链
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();

	//创建流引用管理类
    streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
        call, eventListener, callStackTrace);

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) {
      //是否取消了请求,如果取消了,则通过streamAllocation释放连接
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
      //连接下一个拦截器的方法
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        ...
        releaseConnection = false;
        continue;
      } catch (IOException e) {
       ...
      } finally {
        //出现了未处理的异常,就会通过streamAllocation释放连接
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

      // 关联前一个响应
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }
	  //重定向请求	
      Request followUp = followUpRequest(response);
	  //不需要重定向时候直接返回结果	
      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

      ...

		//发生重定向时候url地址会不一样重新创建streamAllocation
      if (!sameConnection(response, followUp.url())) {
        streamAllocation.release();
        streamAllocation = new StreamAllocation(client.connectionPool(),
            createAddress(followUp.url()), call, eventListener, callStackTrace);
      } else if (streamAllocation.codec() != null) {
      ...
      }

      request = followUp;
      priorResponse = response;
    }
  }

主要做了几件事:
1.创建流引用管理类StreamAllocation
2.在一个while循环中调用了realChain.proceed
3.拿到的响应过程如果出现远端和IO异常就continue请求
4.通过followUpRequest重定向请求,如果返回的结果为null,就返回response,否则就会拿着这个request去重新请求,如果重定向的url不一致,会重新创建流引用管理类

接着来看下是如何做重定向请求的

  private Request followUpRequest(Response userResponse) throws IOException {
    if (userResponse == null) throw new IllegalStateException();
    Connection connection = streamAllocation.connection();
    Route route = connection != null
        ? connection.route()
        : null;
    //获取响应码     
    int responseCode = userResponse.code();
	//获取method
    final String method = userResponse.request().method();
    //根据不同的code做响应处理
    switch (responseCode) {
      //代理服务器验证
      case HTTP_PROXY_AUTH:
        Proxy selectedProxy = route != null
            ? route.proxy()
            : client.proxy();
        if (selectedProxy.type() != Proxy.Type.HTTP) {
          ...
        }
        return client.proxyAuthenticator().authenticate(route, userResponse);

      case HTTP_UNAUTHORIZED:
        return client.authenticator().authenticate(route, userResponse);

      case HTTP_PERM_REDIRECT:
      case HTTP_TEMP_REDIRECT:
        
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
        // fall-through
      case HTTP_MULT_CHOICE:
      case HTTP_MOVED_PERM:
      case HTTP_MOVED_TEMP:
      case HTTP_SEE_OTHER:
        ...
        return requestBuilder.url(url).build();

      case HTTP_CLIENT_TIMEOUT:
        ...
        return userResponse.request();

      default:
        return null;
    }
  }

可以看到会根据不同的code返回不同的request,至于具体是怎样的操作,这里直接借鉴拭心大佬的总结:

如果 code 是 401 or 407 会调用我们构造 OkHttpClient 时传入的 Authenticator,做鉴权处理操作,返回处理后的结果
如果 code 是 307 or 308 重定向,且方法不是 GET 或 HEAD,就返回 null
如果 code 是 300、301、302、303,且构造 OkHttpClient 时设置允许重定向,就从当前响应头中取出 Location 即新地址,然后构造一个新的 Request 再请求一次
如果 code 是 408 超时,且上一次没有超时,就再请求一次
如果 code 是 503 服务不可用,且上一次不是 503,就再请求一次

在这里我们频繁看到 StreamAllocation,它主要用于管理客户端与服务器之间的连接,同时管理连接池,以及请求成功后的连接释放等操作

2 .) BridgeInterceptor

这个拦截器是将用户请求信息转化为HTTP请求信息,把服务器信息转化为用户友好的响应。

来看下它的intercept方法

  @Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    if (body != null) { //根据请求体添加header
      MediaType contentType = body.contentType();
      if (contentType != null) { 
        requestBuilder.header("Content-Type", contentType.toString()); //Content-Type header
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {  //添加不同的header
        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)); //host header
    }

    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive"); //connection header
    }

    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }

    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));//加载本地cooker信息
    }

    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());//User-Agent header
    }

//拦截器链连接方法
    Response networkResponse = chain.proceed(requestBuilder.build());

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

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

	//解压缩处理
    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)));
    }

    return responseBuilder.build();
  }

具体做的事情,请求前:

  • 如果这个请求有请求体,就添加 Content-Type, Content-Length 等
  • 如果这个请求没有 Host,就通过 url 来获取 Host 值添加到 Header 中
  • 如果这个请求没有接收的数据类型 Accept-Encoding,且没指定接收的数据范围,就添加默认接受格式为 gzip
  • 去 CookieJar 中根据 url 查询 Cookie 添加到 Header
  • 如果当前没有,就添加 User-Agent 信息

发起请求后:

  • 如果想要数据的格式是 gzip,就创建 GzipSource 进行解压,同时移除 Content-Encoding 和 Content-Length

这个拦截器的作用就是在用户请求时和响应时添加一些信息

3 .)CacheInterceptor

缓存的作用在http请求中很重要,缓存用得好响应就很快,缓存用得不好就会在对的时间遇见错误的数据,关于缓存的基础理论知识看这篇文章

接下来就看下缓存拦截器是如何实现缓存的,看intercept()方法,代码有点长分段看

Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;

首先通过cache去获取一个缓存响应,其实它的内部实现是用DiskLruCache来做的,这里不是我们关注的重点,然后通过它去构建一个CacheStrategy对象,这个对象内部会根据用户对当前请求设置的 CacheControl 和缓存响应的时间、ETag 、 LastModified 或者 ServedDate 等 Header 进行判断,最后输出两个值

//加工后拿到两个值,根据这两个值的情况决定是请求网络还是直接返回缓存
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;

接下来就是根据输出的这两个对象进行不同的请求

1 . networkRequest为空并且cacheResponse为空,表示调用端只使用缓存,但缓存不可用,就会返回504响应

// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
  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();
}

504 Gateway Timeout

作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)或者辅助服务器(例如DNS)收到响应。

2 .networkRequest为空,但cacheResponse不为空,就直接返回缓存响应

// If we don't need the network, we're done.
if (networkRequest == null) {
  return cacheResponse.newBuilder()
      .cacheResponse(stripBody(cacheResponse))
      .build();
}

3 .networkRequest不为空,表示不用缓存或者缓存的有效性需要验证,这时候就需要网络了

  • 如果 cacheResponse 不为空,且请求的响应码是 304,表示缓存还可以用,就直接返回 cacheResponse
  • 否则就返回网络请求的响应

对应的这段逻辑是

Response networkResponse = null;
try {
  networkResponse = chain.proceed(networkRequest);
} finally {
 ...
}

// If we have a cache response too, then we're doing a conditional get.
// 缓存不为空,返回的响应码是304
if (cacheResponse != null) {
  if (networkResponse.code() == HTTP_NOT_MODIFIED) {
    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();

    // Update the cache after combining headers but before stripping the
    // Content-Encoding header (as performed by initContentStream()).
    cache.trackConditionalCacheHit();
    cache.update(cacheResponse, response);
    return response;
  } else {
    closeQuietly(cacheResponse.body());
  }
}
//缓存不可用,就返回新的响应
Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

这个就是缓存的几种方式,就是以url的MD5为值从DiskLruCache里找之前的response,然后去CacheStragegy里根据缓存header和过期时间进行判断,决定直接返回结果还是进行请求,在拿到缓存之后,就会将结果保存在cahce里面,具体就不进行得那么深了

4 .) ConnectInterceptor

okhttp底层是通过和服务器建立TCP连接才能通信,但是每次请求都建立连接会极大影响通信效率。HTTP1.1开始允许设置Connection keep-alive,这可以减少下一次TCP/IP三次握手四次挥手的建立,多个请求复用一个连接,提高了性格。看下连接拦截器的实现:

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();

    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //尽量底成本的创建一个流
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

它的实现很简单,但是里面涉及到几个很厉害的类,简单介绍下:

  • RealConnection:负责与远程服务器建立连接
  • ConnectionPoll:负责连接的管理和清理
  • StreamAllocation:负责在一个连接上建立新的流,取消一个请求对应的流,释放完成任务的流等操作,
  • HttpCodec:负责请求的写入与响应的读出,即底层 IO

OkHttp 处理一个请求时,会先从连接池中找可以复用的连接,没找到的话就创建新连接, 然后在该连接上创建流对象,通过该流对象完成数据通信,每个类得具体实现也不可能都分析到,毕竟里面得实现还是挺复杂的,想学习的可以看拭心大佬的文章

5 .) CallServerInterceptor

这是最后一个拦截器,负责实现网络IO,所有拦截器都要依赖它才能拿到响应数据。下面先介绍HttpCodec,因为数据读写都是靠它的,它是一个接口

public interface HttpCodec {
  int DISCARD_STREAM_TIMEOUT_MILLIS = 100;
  Sink createRequestBody(Request request, long contentLength);
  //写入请求头
  void writeRequestHeaders(Request request) throws IOException;
  // 将请求数据完全flush到底层
  void flushRequest() throws IOException;
  // 完成请求数据的写入
  void finishRequest() throws IOException;
  //读取并且解析响应头
  Response.Builder readResponseHeaders(boolean expectContinue) throws IOException;
  //打开一个读取响应体的流
  ResponseBody openResponseBody(Response response) throws IOException;
  //关闭流,清理资源
  void cancel();
}

它的实现是通过Okio来实现的,它是专门用来读写数据的IO库,主要围绕Source(输入流)和Sink(输出流)来展开扩展的,它不是本篇文章要介绍的,这里就绕过了。

然后来看下拦截器代码的实现

@Override public Response intercept(Chain chain) throws IOException {
...
    //将请求的 header 写给服务器
    httpCodec.writeRequestHeaders(request);
   
    Response.Builder responseBuilder = null;
    //有请求体的话,将body发送给服务器
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    ...
        long contentLength = request.body().contentLength();
        CountingSink requestBodyOut =
            new CountingSink(httpCodec.createRequestBody(request, contentLength));
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
		//写请求体数据
        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
...
    }
...
    //结束请求的发送
    httpCodec.finishRequest();

    if (responseBuilder == null) {
      //读取服务器的响应
      responseBuilder = httpCodec.readResponseHeaders(false);
    }
 
//处理响应的结果
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }
// 如果Connection值为Close,就关闭连接
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

...

    return response;
  }

3.总结


写了那么多用一张大神的图给个流程总结,看了okhttp的源码可以看出它的内部设计职责分明,设计得确实神奇,特别是拦截器部分,每个拦截器各自有自己的任务,然后通过拦截器链连接。这种分层此设计思想特别适合以后自己的项目开发中,还是很值的学习的。

在这里插入图片描述

4.参考


1.OKHTTP结合官网示例分析两种自定义拦截器的区别
2.深入浅出安卓热门网络框架 OkHttp3 和 Retrofit 原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值