Okhttp的源码解读

Posted 风雨田

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Okhttp的源码解读相关的知识,希望对你有一定的参考价值。

重要的类

类名描述
OkHttpClientOkHttp请求客户端,Builder模式实现
Dispatcher本质是异步请求的调度器,负责调度异步请求的执行,控制最大请求并发数和单个主机的最大并发数,并持有有一个线程池负责执行异步请求,对同步请求只是作统计操作。
Request封装网络请求,就是构建请求参数(如url,header,请求方式,请求参数),Builder模式实现
Response网络请求对应的响应,Builder模式实现,真正的Response是通过RealCall.getResponseWithInterceptorChain()方法获取的。
Call是根据Request生成的一个具体的请求实例,且一个Call只能被执行一次。
ConnectionPool连接池
InterceptorInterceptor可以说是OkHttp的核心功能,它就是通过Interceptor来完成监控管理,重写和重试请求的。
Cache可以自定义是否采用缓存,缓存形式是磁盘缓存,DiskLruCache。

源码思想

OkHttp3,网络请求库,同步请求RealCall.execute()和异步请求RealCall.enqueue(),请求任务都是交给Dispatcher调度请求任务的处理,请求通过一条拦截链,每一个拦截器处理一部分工作,最后一个拦截器,完成获取请求任务的响应,会将响应沿着拦截链向上传递。

源码流程

初始化一个OkHttpClient对象

OkHttpClient mOkHttpClient = new OkHttpClient();  
public OkHttpClient()   
    this(new Builder());  
  

Builder是OkHttpClient的一个内部类,目的是构造和封装数据

public Builder()   
     dispatcher = new Dispatcher();  
     protocols = DEFAULT_PROTOCOLS;  
     connectionSpecs = DEFAULT_CONNECTION_SPECS;  
     proxySelector = ProxySelector.getDefault();  
     cookieJar = CookieJar.NO_COOKIES;  
     socketFactory = SocketFactory.getDefault();  
     hostnameVerifier = OkHostnameVerifier.INSTANCE;  
     certificatePinner = CertificatePinner.DEFAULT;  
     proxyAuthenticator = Authenticator.NONE;  
     authenticator = Authenticator.NONE;  
     connectionPool = new ConnectionPool();  
     dns = Dns.SYSTEM;  
     followSslRedirects = true;  
     followRedirects = true;  
     retryOnConnectionFailure = true;  
     connectTimeout = 10_000;  
     readTimeout = 10_000;  
     writeTimeout = 10_000;  

创建一个请求

Request类是HTTP请求,它携带了请求地址、请求方法、请求头部、请求体以及其他信息。它也是通过Builder模式创建的。

  Request request = new Request.Builder()  
      .url(url)  
      .build(); 
public Request build()   
  if (url == null) throw new IllegalStateException("url == null");  
  return new Request(this);  
  
private Request(Builder builder)   
    this.url = builder.url;  
    this.method = builder.method;  
    this.headers = builder.headers.build();  
    this.body = builder.body;  
    this.tag = builder.tag != null ? builder.tag : this;  
  

接下来对应的就是请求,请求分为异步与同步,先看同步

Response  response = mOkHttpClient.newCall(request).execute();

Response是HTTP响应,它继承自Closeable(Closeable继承自AutoCloseable,AutoCloseable资源自动关闭的接口),它携带了请求、网络请求协议、返回状态码、请求头、响应体等等,其中,网络请求协议是Protocol,Protocol是一个枚举类型,包含4中类型

public enum Protocol   
    HTTP_1_0("http/1.0"),  
    HTTP_1_1("http/1.1"),  
    SPDY_3("spdy/3.1"),  
    HTTP_2("h2");  
  

Call类是一个抽象类

OkHttpClient.newCall(request);  

@Override   
public Call newCall(Request request)   
    return new RealCall(this, request);  //RealCall extends Call


protected RealCall(OkHttpClient client, Request originalRequest)   
  this.client = client;  
  this.originalRequest = originalRequest;  
  this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client);  
 
public interface Call   
    Request request();  
    Response execute() throws IOException;  
    void enqueue(Callback responseCallback);  
    void cancel();  
    boolean isExecuted();  
    boolean isCanceled();  
    interface Factory   
        Call newCall(Request request);  
      
  

OKHttp提供了execute()方法(同步方法)和enqueue()方法(异步方法),下面我们先看看execute()方法(同步方法)

execute()方法,首先判断是否已执行过,如果已经执行过,则抛出异常信息,也就是说一次Call实例只能调用一次execute()方法。没有执行则通过分发器进行执行

@Override 
public Response execute() throws IOException   
synchronized (this)   
    if (executed) throw new IllegalStateException("Already Executed");  
    executed = true;  
  
try   
    client.dispatcher().executed(this);  
    Response result = getResponseWithInterceptorChain();  
    if (result == null) throw new IOException("Canceled");  
    return result;  
 finally   
    client.dispatcher().finished(this);  
 

分发器将请求加入到队列

 ...  
 private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();//双端队列  
 ...  
     /** Used by @code Call#execute to signal it is in-flight. */  
synchronized void executed(RealCall call)   
  runningSyncCalls.add(call);  
  

最后通过分发器的finished()方法结束请求

void finished(RealCall call)   
    finished(runningSyncCalls, call, false);  
  

private <T> void finished(Deque<T> calls, T call, boolean promoteCalls)   
    int runningCallsCount;  
    Runnable idleCallback;  
    synchronized (this)   
        if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");  
        if (promoteCalls) promoteCalls();  
        runningCallsCount = runningCallsCount();  
        idleCallback = this.idleCallback;  
      
    if (runningCallsCount == 0 && idleCallback != null)   
        idleCallback.run();  
      
 

所以通过上面信息可知真正发送请求的地方是getResponseWithInterceptorChain()方法

真正发送请求源码分析

组装各种拦截器,然后发送请求

private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException 
    Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
    return chain.proceed(originalRequest);

拦截器接口

public interface Interceptor 
    Response intercept(Chain chain) throws IOException;
    interface Chain 
        Request request();
        Response proceed(Request request) throws IOException;
        Connection connection();
    

在这个责任链模式中执行每一个连接器的proceed方法,然后我们逐个分析这些连接器

这里是责任链模式,由用户主动调用此方法,因此需要实现拦截的功能

这里如果有Intercepter实现已经添加,则为每一个拦截器创建一个ApplicationInterceptorChain然后递归直到将所有的链调用完成最后进入getResponse方法中。

@Override public Response proceed(Request request) throws IOException 
  //If there's another interceptor in the chain, call that.
  if (index < client.interceptors().size()) 
  //新建一个环节,并且将索引+1,这里的request可能不再是originalRequest了,因为在拦截器中可能被修改了
    Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
    //获取到对应的拦截器
    Interceptor interceptor = client.interceptors().get(index);
    //执行拦截器的intercept方法,参数是上面新建的环节,这个方法里面会调用chain.proceed(),递归了。
    //在最深的一层调用getResponse之后,响应会一层层的往外传
    Response interceptedResponse = interceptor.intercept(chain);

    if (interceptedResponse == null) 
      throw new NullPointerException("application interceptor " + interceptor
          + " returned null");
    
 // 返回这一层拦截器处理用的响应
    return interceptedResponse;
  

  //递归到了最深的一层,拦截器都进入过了,发送httpRequest,获取到response.
  return getResponse(request, forWebSocket);

当执行完getResponse完成之后就一步步返回。

真正请求数据的方法

  1. request.newBuilder();由于是之前的请求,所以内部调用newBuilder的时候使用老请求的url,method,body,tag,headers。然后给这个requestBuilder中添加各种请求的键值对信息最后构造一个原信息+新信息的新请求。
  2. 创建一个请求引擎,每一个请求引擎代表依次请求/响应
  3. 在死循环中做的事情
    1. 如果请求被取消抛出异常
    2. 然后发送请求,读取响应,重新连接设置成false
    3. 如果请求失败则算了不请求了,但是如果是路由异常则需要恢复请求。
    4. 最后释放链接,得到响应。
    5. 如果存在下一步请求,则重定向。
  Response getResponse(Request request, boolean forWebSocket) throws IOException 
    // 复制请求头,并设置适合的属性。如果长度不为-1,则设置Content-Length,否则使用chunked方式传输。
    // 如果对chunked不熟悉,请参考其他资料
    RequestBody body = request.body();
    if (body != null) 
      // 根据传进来的request创建新的Builder.
      Request.Builder requestBuilder = request.newBuilder();

      // 设置Content-Type
      MediaType contentType = body.contentType();
      if (contentType != null) 
        requestBuilder.header("Content-Type", contentType.toString());
      

      // 判断使用何种方式传输
      long contentLength = body.contentLength();
      // body长度不为-1,设置Content-Length
      if (contentLength != -1) 
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
       else 
        //  body 长度为 -1 ,使用chunked传输
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      

      //创建新请求
      request = requestBuilder.build();
    

    // 创建一个新的引擎,每个引擎代表一次请求/响应对
    engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);

    int followUpCount = 0; //重试次数
    //死循环 出口:
    // 1. 请求被取消
    // 2. 请求有问题
    // 3. 捕获到异常,且尝试恢复失败
    // 4. 获取到响应,且无需重定向
    // 5. 重定向次数超过最大限制
    while (true) 
      // 被取消的情况
      if (canceled) 
        engine.releaseStreamAllocation();
        throw new IOException("Canceled");
      

      boolean releaseConnection = true;
      try 
        // 发送请求
        engine.sendRequest();
        // 读取响应
        engine.readResponse();
        releaseConnection = false;
       catch (RequestException e) 
        // 请求失败,请求本身有问题,或者是网络不通
        throw e.getCause();
       catch (RouteException e) 
        // 连接到服务器的路由发生异常,请求还没被发送
        // 通过上一次连接异常恢复引擎
        HttpEngine retryEngine = engine.recover(e.getLastConnectException(), null);
        // 如果恢复成功,将当前的引擎设置为这个恢复好的引擎
        if (retryEngine != null) 
          releaseConnection = false;
          engine = retryEngine;
          continue;
        
        // 没法恢复,抛出异常
        throw e.getLastConnectException();
       catch (IOException e) 
        // 与服务器交互失败,这时,请求可能已经被发送
        // 恢复引擎
        HttpEngine retryEngine = engine.recover(e, null);
        //如果恢复成功,将当前的引擎设置为这个恢复好的引擎
        if (retryEngine != null) 
          releaseConnection = false;
          engine = retryEngine;
          continue;
        

        // 没法恢复,抛出异常
        throw e;
       finally 
        // 如果需要释放连接,则将连接释放
        if (releaseConnection) 
          StreamAllocation streamAllocation = engine.close();
          streamAllocation.release();
        
      

      // 获取响应
      Response response = engine.getResponse();
      // 下一步的请求,如果存在,则需要重定向
      Request followUp = engine.followUpRequest();

      //如果不需要重定向
      if (followUp == null) 
        if (!forWebSocket) 
          // 释放连接
          engine.releaseStreamAllocation();
        
        //返回响应
        return response;
      

      // 如果需要重定向,关闭当前引擎
      StreamAllocation streamAllocation = engine.close();

      // 如果超过最大数,释放连接,并抛出异常
      if (++followUpCount > MAX_FOLLOW_UPS) 
        // 释放连接
        streamAllocation.release();
        // 抛出异常
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      

      // 如果重定向的地址和当前的地址一样,则不需要释放连接
      if (!engine.sameConnection(followUp.url())) 
        streamAllocation.release();
        streamAllocation = null;
      

      // 使用重定向后的请求,重新实例一个引擎,开始下一次循环
      request = followUp;
      engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,
          response);
    
  

最后HttpEngine进行的操作与http协议很大相关,作者对此不感兴趣,本文只是对流程做大概分析

哥哥若是喜欢可移驾公众号:码老板

以上是关于Okhttp的源码解读的主要内容,如果未能解决你的问题,请参考以下文章

Okhttp的源码解读

Okhttp的源码解读

知识点OkHttp 原理 8 连问

知识点OkHttp 原理 8 连问

Retrofit2源码解读

OkHttp 基本使用&源码分析