一文了解OKHttp3全(大话原理篇)

4,071

1.简介

首先为什么要写这篇博客,主要是因为现在网络用的都是okhttp3,所以在面试的时候,都会问一下okhttp的原理,而网上的原理,也看了下,要么太简短,核心的一笔带过,要么长篇大伦,看着蒙圈。所以想看看能不能用最简短明白的方式来解释下okhttp3的原理。 当然,如果还不是很熟悉这个框架的小朋友,可以点这里一文了解OKHttp3全(使用篇)

先看图

来,这就是简单的一个get请求,咱们按照顺序从1,2,3,4这几点开始分析。首先先分析第1点。


2 .okhttp的创建

首先要想使用网络请求的话,得先初始化它,先看看有哪些属性。

文件位置:OkHttpClient.java

    final Dispatcher dispatcher;//调度器
    final Proxy proxy;//代理
    final List<Protocol> protocols;//协议
    final List<ConnectionSpec> connectionSpecs;//传输层版本和连接协议
    final List<Interceptor> interceptors;//拦截器
    final List<Interceptor> networkInterceptors;//网络拦截器
    final EventListener.Factory eventListenerFactory;
    final ProxySelector proxySelector;//代理选择器
    final CookieJar cookieJar;//cookie
    final Cache cache;//cache 缓存
    final InternalCache internalCache;//内部缓存
    final SocketFactory socketFactory;//socket 工厂
    final SSLSocketFactory sslSocketFactory;//安全套层socket工厂 用于https
    final CertificateChainCleaner certificateChainCleaner;//验证确认响应书,适用HTTPS 请求连接的主机名
    final HostnameVerifier hostnameVerifier;//主机名字确认
    final CertificatePinner certificatePinner;//证书链
    final Authenticator proxyAuthenticator;//代理身份验证
    final Authenticator authenticator;//本地省份验证
    final ConnectionPool connectionPool;//链接池 复用连接
    final Dns dns; //域名
    final boolean followSslRedirects;//安全套接层重定向
    final boolean followRedirects;//本地重定向
    final boolean retryOnConnectionFailure;//重试连接失败
    final int connectTimeout;//连接超时
    final int readTimeout;//读取超时
    final int writeTimeout;//写入超时

很好,属性看完了,就这些,基本也够了,但是尼,怎么赋值是个问题,总不能都写在构造函数里面吧?有什么好的设计模式尼?就是那种将使用和复杂的构建相分离的那种?恭喜你答对了,就是构建者模式。

果然,okhttp3用的也是它。看下源码吧

public static final class Builder {
   ...
    public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      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;
      pingInterval = 0;
    }

    public OkHttpClient build() {
      return new OkHttpClient(this);
    }
    ...
}

这就简单了,都设置了默认值,那么我们在外边调用的时候就很简单了,只需要这么写就可以了。

OkHttpClient mClient = new OkHttpClient.Builder() // 构建者模式,创建实例
                           .build();

OK,第1步完事了

总结一下

第 1 步就是用构建者模式创建okhttp3的实例,里面封装了一些使用中必要的属性,如超时时间,拦截器等


3 .Request的创建

这是第2步,说白了,第1步是创建okhttp3的实例,那么第2步,就是创建请求信息。 废话少说,先看下它有哪些属性。

文件位置:Request

  final HttpUrl url; // 接口地址
  final String method; // post还是get
  final Headers headers; // Http消息的头字段
  final RequestBody body; // 它是抽象类, 有些请求需要我们传入body实例,如果是GET请求,body对象传的是null,如果是POST请求,就需要我们去设定了。
  final Object tag;

好了,如果你想简单省事的去用,自动设置默认值,那就继续我们的构建者模式吧

  public static class Builder {
    HttpUrl url;
    String method;
    Headers.Builder headers;
    RequestBody body;
    Object tag;

    public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

    public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }
}

其实也没省啥事,无非就是你直接用的时候,给你默认了个Get而已。 不过既然封装了,我们就可以这么调用了

 Request mRequest = new Request.Builder() // 构建者模式,创建请求信息
                .url("https://www.baidu.com")
                .build();

OK,第2步完事了

总结一下

第 2 步就是用构建者模式创建请求信息的实例,里面封装了一些使用中必要的属性,如请求方式,请求头信息,接口地址等


4 .Call对象的创建

好了,继续看我们的第3步 首先尼,根据okHttpClient和Request对象,我们就能构建出实际进行请求的call对象。看源码 我们得知,call是个接口,实际进行请求的是RealCall,来我们创建它

文件位置:OkHttpClient.java

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

看看它做了什么

  RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    final EventListener.Factory eventListenerFactory = client.eventListenerFactory();

    this.client = client; // 第1步创建的okHttpClient实例
    this.originalRequest = originalRequest; // 第2步创建的request实例
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); // 重定向拦截器,后面会说

    // TODO(jwilson): this is unsafe publication and not threadsafe.
    this.eventListener = eventListenerFactory.create(this);
  }

好了,第3步就完事了

总结一下

第 3 步就是创建realcall对象,真正的请求是交给了 RealCall 类,它实现了Call方法,它是真正的核心代码。


5 .realcall的异步请求(上)

开始分析最后1步 这块是关键,我们一步一步的来。首先看我们的调用

call.enqueue(new Callback(){ ... });

然后看,realcall.enqueue()方法

@Override 
public void enqueue(Callback responseCallback) {
    1.synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    2.client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
  1. synchronized (this) 确保每个call只能被执行一次不能重复执行,如果想要完全相同的call,可以调用如下方法:进行克隆
  2. 利用dispatcher调度器,来进行实际的执行client.dispatcher().enqueue(new AsyncCall(responseCallback)), 这里分为2步走

先看下new AsyncCall(responseCallback)

 final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }
 }

对我们的回调进行封装,继承Runnable接口 好,再继续看client.dispatcher().enqueue()

文件位置:Dispatcher.java

synchronized void enqueue(AsyncCall call) {
    1.if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      2.runningAsyncCalls.add(call);
      3.executorService().execute(call);
    } else {
      4.readyAsyncCalls.add(call);
    }
  }
  1. runningAsyncCalls是正在运行的任务, runningAsyncCalls.size() < maxRequests 那么这一句的解释就是,总任务数不能超过maxRequests这个值(默认64,可设置调度器调整),runningCallsForHost(call) < maxRequestsPerHost) 这句解释就是:当前主机的请求数不能超过5个。
  2. 如果符合条件,就将正在请求的runnable添加到正在执行的异步请求队列之中。
  3. 然后通过线程池执行这个AsyncCall
  4. 如果正在执行的任务数已经超过了设置的最大值,或者当前网络请求的主机数超过了设置的最大值,那么就会将AsyncCall加入到readyAsyncCalls 这个等待队列中。
总结一下

在call.enqueue(new Callback(){ ... }) 执行之后,首先做的是

1. 调用RealCall.call.enqueue()方法,判断当前call是否已经被执行过了,被执行过了,就抛出异常,如果没有执行过,就先将callback封装成AsyncCall,然后调用dispatcher.enqueue()方法(dispatcher调度器,在okHttpClient里面创建的)

2. 在dispatcher.enqueue()方法中,判断当前正在执行的请求数及当前网络请求的主机数是否超过了最大值。要是超过了最大值,就将请求放到等待队列中,要是没超过,就放当正在执行的队列中,然后调用线程池执行它。


6 .realcall的异步请求(下)

6.1 executorService.execute

吃过了午饭,继续分析源码,承接上文,我们分析到了,如果符合条件,就用线程池去执行它,也就是这句

executorService().execute(call);

看一下我们的线程池

public synchronized ExecutorService executorService() {
    if (executorService == null) {
      1.executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
}

核心线程 最大线程 非核心线程闲置60秒回收,当然会有人提出疑问,最大值设置为Integer.MAX_VALUE,会不会对性能造成影响呀?答案是不会的,因为尼,在前面的dispatcher.enqueue()方法中,已经对请求数做了限制,超过设置的最大请求数,会被放到等待队列里面。

好,继续再看下线程池executorService.execute(call)方法 它会执行里面call方法的run()方法,也就是AsyncCall的run方法,这个run方法实际上是在它的父类NamedRunnable里面。在NamedRunnable.run()方法里面,实际上是调用了execute(),该方法由子类实现,也就是调用了AsyncCall.execute()

简单来说,就是executorService.execute(call) -> NamedRunnable.run() -> AsyncCall.execute() 看到这个写法,内心中就想吐槽一句话,真鸡儿秀! 来继续看下,AsyncCall.execute()

6.2 AsyncCall.execute()

看源码 文件位置:realcall.java

@Override protected void execute() {
      boolean signalledCallback = false;
      try {
        1.Response response = getResponseWithInterceptorChain();
        2.if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          3.responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        4.client.dispatcher().finished(this);
      }
    }
  1. 执行拦截器链,返回Response
  2. 判断拦截器链中的重定向拦截器是否已经取消了,如果取消了,就执行responseCallback.onFailure() ,这个也就是我们在外边在第3步,传过来的回调方法Callback()中的onFailure()方法。
  3. 如果没取消,则走onResponse也就是Callback()中的onResponse()方法。返回结果。当然这里都是在子线程里面的。
  4. 这句其实就是调用了,dispatcher方法中的finished方法。下面我们看下

6.3 dispatcher.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) {
      1.if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      2.if (promoteCalls) promoteCalls();
      3.runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }
  1. 将该请求从正在执行的任务队列里面删除
  2. 调用promoteCalls() 调整请求队列
  3. 重新计算请求数量

6.4 dispatcher.finished()

private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

很简单,这里无非就是遍历等待队列中的请求,然后加入到执行请求队列中,直到并发数和当前网络请求的主机数达到上限。

至此,okhttp的异步已经分析完毕了

面试题 1.什么是dispatcher? dispatcher作用是为维护请求的状态,并维护一个线程池。用于执行请求。

小伙子,是不是很简单呀?是不是已经完了?你想多了,来分析最核心的一部分


7. getResponseWithInterceptorChain()

先看源码

Response getResponseWithInterceptorChain() throws IOException {
    // 添加拦截器,责任链模式
    List<Interceptor> interceptors = new ArrayList<>();

    // 在配置okhttpClient 时设置的intercept 由用户自己设置
    interceptors.addAll(client.interceptors());

    // 负责处理失败后的重试与重定向
    interceptors.add(retryAndFollowUpInterceptor);

    /** 负责把用户构造的请求转换为发送到服务器的请求 、把服务器返回的响应转换为用户友好的响应 处理 配置请求头等信息.
    从应用程序代码到网络代码的桥梁。首先,它根据用户请求构建网络请求。然后它继续呼叫网络。最后,它根据网络响应构建用户响应。
    */
    interceptors.add(new BridgeInterceptor(client.cookieJar()));

    // 处理 缓存配置 根据条件(存在响应缓存并被设置为不变的或者响应在有效期内)返回缓存响应
    // 设置请求头(If-None-Match、If-Modified-Since等) 服务器可能返回304(未修改)
    // 可配置用户自己设置的缓存拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));

    // 连接服务器 负责和服务器建立连接 这里才是真正的请求网络
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    
    // 执行流操作(写出请求体、获得响应数据) 负责向服务器发送请求数据、从服务器读取响应数据
    // 进行http请求报文的封装与请求报文的解析
    interceptors.add(new CallServerInterceptor(forWebSocket));

    // 责任链,将上述的拦截器添加到责任链里面
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }

这里用的是责任链模式,不了解的朋友可以先了解下 这段代码其实很简单,就是将一些拦截器,都装到一个集合里面,然后将拦截器的集合当做构造参数,创建了个对象(RealInterceptorChain),最后调用它的proceed方法。然后一直往下调用。

本来想再写写拦截器这边的逻辑,但是尼,老夫掐指一算,就知道你们这块容易看懵逼,那么咱们自己写一个吧


1.先定义接口

public interface Interceptor {

    interface Chain{

        String request();

        String proceed(String request);
    }
}

2.来定义一个RetryAndFollowInterceptor(重定向拦截器)

public class RetryAndFollowInterceptor implements Interceptor{

    @Override
    public String interceptor(Chain chain) {

        System.out.println("RetryAndFollowInterceptor_start");
        String response = chain.proceed(chain.request());
        System.out.println("RetryAndFollowInterceptor_start");

        return response;
    }
}

2.再定义一个BridgeInterceptor(桥接拦截器)

public class BridgeInterceptor implements Interceptor{
    
    @Override
    public String interceptor(Chain chain) {

        System.out.println("BridgeInterceptor_start");
        String response = chain.proceed(chain.request());
        System.out.println("BridgeInterceptor_end");
        
        return response;
    }
}

3.最后一个拦截器CallServerInterceptor

public class CallServerInterceptor implements Interceptor {

    @Override
    public String interceptor(Chain chain) {

        System.out.println("CallServerInterceptor_start");
        System.out.println("----------将数据传到服务器端:数据为"+ chain.request());
        System.out.println("CallServerInterceptor_end");

        return "登陆成功";
    }
}

4.来,定义个责任链RealInterceptorChain对象

public class RealInterceptorChain implements Interceptor.Chain{

    private List<Interceptor> interceptors;
    private int index;
    private String request;

    public RealInterceptorChain(List<Interceptor> interceptors, int index, String request) {

        this.interceptors = interceptors;
        this.index = index;
        this.request = request;
    }

    @Override
    public String request() {

        return request;
    }

    @Override
    public String proceed(String request) {

        if(index >= interceptors.size()) {

            return null;
        }

        // 这里就是责任链模式,它会把它的index+1 然后再创建一个RealInterceptorChain对象
        RealInterceptorChain next = new RealInterceptorChain(interceptors, index + 1, request);

        Interceptor Interceptor = interceptors.get(index);
        return Interceptor.interceptor(next);
    }
}

5.调用一下

public static void main(String[] args) {

       List<Interceptor> interceptors = new ArrayList<>();
       interceptors.add(new RetryAndFollowInterceptor());
       interceptors.add(new BridgeInterceptor());
       interceptors.add(new CallServerInterceptor());

       RealInterceptorChain chain = new RealInterceptorChain(interceptors, 0, "");
       String result = chain.proceed("xiaoming, 123");
       System.out.println("----------服务器返回的结果是:"+result);
}

6.结果

看明白了吗?如果不明白,这个图应该明白了吧。

是不是感觉okhttp很简单?拦截器还没介绍尼,来最后介绍下拦截器。

8.拦截器介绍

1、用户自定义的拦截器 用在与服务器建立链接之前进行拦截

interceptors.addAll(client.interceptors());

2、RetryAndFollowUpInterceptor重试或失败重定向拦截器

 interceptors.add(retryAndFollowUpInterceptor);

3、BridgeInterceptor 校接和适配拦截器,主要补充用户创建请求当中的一些请求头Content-Type

interceptors.add(new BridgeInterceptor(client.cookieJar()));

4、CacheInterceptor主要处理缓存

interceptors.add(new CacheInterceptor(client.internalCache()));

5、ConnectInterceptor 与服务器创建链接,创建可以用的RealConnection(对java.io和java.nio进行了封装)

interceptors.add(new ConnectInterceptor(client));

6、用户自定义的拦截器 用在与服务器建立链接之后进行拦截。只有非socket进行设置

if (!forWebSocket) {
   interceptors.addAll(client.networkInterceptors());
}

7、CallServerInterceptor 向服务器发送请求和接收数据。将请求写入IO流,再从IO流中读取响应数据

interceptors.add(new CallServerInterceptor(forWebSocket));

这里只是面试的时候简写,如需详细,请底下留言。