OkHttp源码解析(小白必看,建议收藏)

Posted 计蒙不吃鱼

tags:

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

系列文章目录

在两年前还是专科的时候为了找工作看过Okhttp3的部分源码,记了点笔记,最近又到本科找工作阶段了,翻出来自己以前的笔记。整理出一篇文章,希望能够对各位产生帮助。(对初学者极其友好)


计蒙创作不易,未入驻的其他平台搬运请按协议规则搬运。


在看文章前需要知道的知识点

1、Request 组成

客户端发送一个 HTTP 请求到服务器的请求消息包括以下格式:
请求行(request line)、请求头部(header)、空行和请求数据四个部分组成。

  • 第一部分:请求行,用来说明请求类型,要访问的资源以及所使用的 HTTP 版本.
  • 第二部分:请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息
  • 第三部分:空行,请求头部后面的空行是必须的
  • 第四部分:请求数据也叫主体,可以添加任意的其他数据。

2、Response 组成

一般情况下,服务器接收并处理客户端发过来的请求后会返回一个 HTTP 的响应消息。
HTTP 响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。

3、HTTPS 中的 SSL 握手建立过程

简化如下:
1、客户端和服务端建立 SSL 握手,客户端通过 CA 证书来确认服务端的身份;
2、互相传递三个随机数,之后通过这随机数来生成一个密钥;
3、互相确认密钥,然后握手结束;
4、数据通讯开始,都使用同一个对话密钥来加解密;

4、响应码

  • 1** 信息,服务器收到请求,需要请求者继续执行操作
  • 2** 成功,操作被成功接收并处理
  • 3** 重定向,需要进一步的操作以完成请求
  • 4** 客户端错误,请求包含语法错误或无法完成请求
  • 5** 服务器错误,服务器在处理请求的过程中发生了错误

5、socket 概念

套接字(socket)是通信的基石,是支持 TCP/IP 协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的 IP 地址,本地进程的协议端口,远地主机的 IP 地址,远地进程的协议端口。

6.责任链模式(设计模式)

意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。

使用场景: 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。

在Android开发中,学过自定义view的应该也知道:ViewGroup 事件传递的递归调用就类似一条责任链,一旦其寻找到责任者,那么将由责任者持有并消费掉该次事件,具体体现在 View 的 onTouchEvent 方法中返回值的设置,如果 onTouchEvent 返回 false,那么意味着当前 View 不会是该次事件的责任人,将不会对其持有;如果为 true 则相反,此时 View 会持有该事件并不再向下传递。

下面开始为正文内容


一、OkHttp介绍

1.这个库的作用:

网络底层库,它是基于 http 协议封装的一套请求客户端,虽然它也可以开线程,但根本上它更偏向真正的请求,跟 HttpClient, HttpUrlConnection 的职责是一样的。其中封装了网络请求 get、post 等底层操作的实现。

2.项目中使用这个库的原因(优点)

  • OkHttp 提供了对最新的 HTTP 协议版本 HTTP/2 和 SPDY的支持,这使得对同一个主机发出的所有请求都可以共享相同的套接字连接。
  • 如果 HTTP/2 和 SPDY 不可用,OkHttp 会使用连接池来复用连接以提高效率。
  • OkHttp 提供了对 GZIP 的默认支持来降低传输内容的大小。
  • OkHttp 也提供了对 HTTP 响应的缓存机制,可以避免不必要的网络请求
  • 当网络出现问题时,OkHttp 会自动重试一个主机的多个 IP 地址。

二、简单使用步骤

1.导入库

implementation("com.squareup.okhttp3:okhttp:3.10.0")

2.步骤

  • 1.先创建 OkHttpClient 实例;
  • 2.构造 Request 实例,传入 url 等相关参数;
  • 3.通过前两步中的实例对象构建 Call 对象;
  • 4.异步请求通过 Call#enqueue(Callback) 方法来提交异步请求,同步请求通过 Call#execute() 直接 获取Reponse

相应代码如下

//1.创建⼀个 OkHttp 的实例
OkHttpClient client = new OkHttpClient.Builder().build();
//2.创建 Request
Request request = new Request.Builder().url("http://www.jimengjia.com").build();
//3.构建 Call 对象
Call call=okHttpClient.newCall(request);
//4.通过 Call#enqueue(Callback) 方法来提交异步请求
call.enqueue(new Callback() {
       @Override
       public void onFailure( Call call,  IOException e) {
       }

       @Override
       public void onResponse( Call call,Response response) throws IOException {
       }
});

3.源码解析

看源码的流程(思想):

1.从使用步骤中第四步可以看出起关键作用的是enqueue(),所以从源码中找到此方法开始分析。

代码如下:

@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));
}

2.从上述代码可以看到,创建了一个 AsyncCall 并将Callback传入后,最后交给了任务分发器 Dispatcher 来进一步处理。

client.dispatcher().enqueue(new AsyncCall(responseCallback));
进入下一个调用dispatcher().enqueue()

synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
   //运行中的队列
     runningAsyncCalls.add(call);
    executorService().execute(call);
  } else {
      //等待中的队列,可看成支线
    readyAsyncCalls.add(call);
  }
}

相应的对象如下:

 private int maxRequests = 64;
 private int maxRequestsPerHost = 5;
 //可以很明显看出是添加到了runningAsyncCalls,readyAsyncCalls中
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

此方法的大致内容:对请求的入队做了一些限制,若正在执行的请求数量小于最大值(默认64),并且此请求所属主机的正在执行任务小于最大值(默认5),就加入正在运行的队列并通过线程池来执行该任务,否则加入准备执行队列中。

3.从上述代码可以看到,当满足条件后将进入executorService()

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;
}

这里的具体就是放到线程池里的运行了。(以下内容为拓展)
ExecutorService的创建方式如下:所有线程池最终都是通过这个方法来创建的。

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
                             
  • corePoolSize :核心线程数,一旦创建将不会再释放。如果创建的线程数还没有达到指定的核心线程数量,将会继续创建新的核心线程,直到达到最大核心线程数后,核心线程数将不在增加;如果没有空闲的核心线程,同时又未达到最大线程数,则将继续创建非核心线程;如果核心线程数等于最大线程数,则当核心线程都处于激活状态时,任务将被挂起,等待空闲线程来执行。
  • maximumPoolSize :最大线程数,允许创建的最大线程数量。如果最大线程数等于核心线程数,则无法创建非核心线程;如果非核心线程处于空闲时,超过设置的空闲时间,则将被回收,释放占用的资源。
  • keepAliveTime : 也就是当线程空闲时,所允许保存的最大时间,超过这个时间,线程将被释放销毁,但只针对于非核心线程。
  • unit : 时间单位,TimeUnit.SECONDS等。
  • workQueue : 任务队列,存储暂时无法执行的任务,等待空闲线程来执行任务。
  • threadFactory : 线程工程,用于创建线程。
  • handler : 当线程边界和队列容量已经达到最大时,用于处理阻塞时的程序

可以明显的看到创建时使用的队列为SynchronousQueue:它内部没有容器(可以去拓展一下,本文就不写了)

4.进入到下一个代码区execute():

@Override protected void execute() {
  boolean signalledCallback = false;
  try {
      //处理请求的地方
    Response response = getResponseWithInterceptorChain();
    if (retryAndFollowUpInterceptor.isCanceled()) {
      signalledCallback = true;
      responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
    } else {
      signalledCallback = true;
       //回调将结果返回调用者
      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 {
      eventListener.callFailed(RealCall.this, e);
      //如果发生异常产生错误回调
      responseCallback.onFailure(RealCall.this, e);
    }
  } finally {
      //维护,执行完之后,调用dispatcher中的finish函数
    client.dispatcher().finished(this);
  }
}

5.可以明显的看到处理请求的为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);
}

这个方法里面通过拦截器组成的责任链,依次经过用户自定义普通拦截器、重试拦截器、桥接拦截器、缓存拦截器、 连接拦截器和用户自定义网络拦截器以及访问服务器拦截器等拦截处理过程来获取到一个响应并交给用户。

各个拦截器的作用:

  • interceptors:用户自定义拦截器(根据具体业务决定)
  • retryAndFollowUpInterceptor:负责失败重试以及重定向
  • BridgeInterceptor:请求时,对必要的 Header 进行一些添加,接收响应时,移除必要的 Header
  • CacheInterceptor:负责读取缓存直接返回(根据请求的信息和缓存的响应的信息来判断是否存在缓存可用)、更新缓存
  • ConnectInterceptor:负责和服务器建立连接。

6.回到第四步维护,调用dispatcher中的finish函数

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;
  }
  //如果队列没有在执行的线程且idle线程不为空则执行。 可以看出每个线程结束完都会检查一遍是不是
  //要执行空闲线程
  if (runningCallsCount == 0 && idleCallback != null) {
    idleCallback.run();
  }
}

7.最后看看promoteCalls()方法;

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.
  }
}

此方法内容如下:
1.首先会判断当前正在运行的异步请求的数量是否超过他的最大数量,如果超过了就返回了就不要再做其他操作了。
2.如果还能进行异步请求,就表示我们这个空闲还有余额,会直接返回。
3.接着会循环遍历这个等待的执行队列。
4.然通过next迭代器去获取到AsyncCall实例。
5.接着判断所有的运行的主机是否小于最大的限制,这是一个最大的前提条件。将call从等待队列中移到正在执行的请求队列当中,先移除,然后再把它添加到正在执行的异步请求队列当中。最后会开启一个线程池去执行请求(回到文中第三步)。

总结

OkHttp 内部的请求流程:使用 OkHttp 会在请求的时候初始化一个 Call 的实例,然后执行它的 execute()方法或 enqueue()方法,内部最后都会执行到getResponseWithInterceptorChain()方法,这个方法里面通过拦截器组成的责任链,依次经过用户自定义普通拦截器、重试拦截器、桥接拦截器、缓存拦截器、 连接拦截器和用户自定义网络拦截器以及访问服务器拦截器等拦截处理过程来获取到一个响应并交给用户。

以上是关于OkHttp源码解析(小白必看,建议收藏)的主要内容,如果未能解决你的问题,请参考以下文章

使用Jest进行React单元测试(建议收藏)小白必看!

2023Python学习路线图新版超详细,小白必看!建议收藏

程序员收藏必看系列:深度解析MySQL优化

新手必看 | Vimble 2 超详细使用教程(建议收藏)

okHttp源码解析------待续

OkHttp完全解析之整体调用流程