敞开生长之旅!这是我参与「日新方案 2 月更文挑战」的第 5 天,点击查看活动概况

OkHttp 4.X 及其以上的版本的源码为 kotlin 语言编写。本文解说的 OkHttp 源码版本为 3.14.9,是 Java 语言编写的终究一个版本。因为 OkHttp 运用了 Okio 这个 IO 库,所以阅览本文之前,我非常主张咱们先阅览我之前写的两篇关于 Okio 的文章,再去阅览 OkHttp 的源码,就会扫清一道障碍。

  1. Android | 完全了解 Okio 之源码篇
  2. Android | 完全了解 Okio 超时机制

Http 恳求/呼应报文格式

okhttp 是一个 Http 协议的网络恳求框架,专为 Java 和 Android 精心规划。所以咱们有必要了解一些关于 Http协议的基础知识。Http 协议作业在 OSI 模型的运用层,基于TCP/IP通信协议来传递数据,运用 Socket 完成通信。

恳求报文格式

Http 协议规则恳求报文格式由恳求行+恳求头字段+恳求体组成。

  • 恳求行:由恳求办法+URL+协议版本组成,如POST /api/listNotice HTTP/1.1
  • 恳求头字段:键值对的方式组成,完好的头字段能够在 www.iana.org/assignments… 找到。
  • 恳求体:恳求发送的数据。

Android | 彻底理解 OkHttp 源码篇

呼应报文格式

Http 协议规则呼应报文格式由呼应行+呼应头字段+呼应体组成。

  • 呼应行:由协议版本+状况码+状况描述组成,如HTTP/1.1 200 ok
  • 呼应头字段:键值对的方式组成,完好的头字段能够在 www.iana.org/assignments… 找到。
  • 呼应体:呼应回来的数据

Android | 彻底理解 OkHttp 源码篇

重要的头字段

头字段 阐明 取值
Content-Type 1. 在恳求中 ,客户端告诉服务器实际发送的数据类型。2. 在呼应中,Content-Type 标头告诉客户端实际回来的内容的内容类型 www.iana.org/assignments…
Content-Length 发送给接收方的音讯主体的巨细 十进制整数,字节的数目
Connection 当时的事务完成后,是否会封闭网络衔接 keep-alive 或 close
Cache-Control 单向缓存指令,被用于在 http 恳求和呼应中,通过指定指令来完成缓存机制。 developer.mozilla.org/zh-CN/docs/…
Authorization 用于供给服务器验证用户代理身份的凭据,答应拜访受维护的资源 developer.mozilla.org/zh-CN/docs/…

回想一下 OkHttp 恳求网络的过程

在正式开端剖析源码之前,咱们在脑海里回顾一下 OkHttp 发送一个网络恳求要写哪些代码?对于一个简略的 Post 恳求来说,要阅历如下几个过程。

  1. 运用 Builder 方式创立一个大局运用的OkHttpClient目标,能够用它来设置对一切恳求都生效的超时时刻、缓存、自定义拦截器等。
  2. 运用 Builder 方式创立一个Request,设置Request恳求的 url,头字段,恳求体等。
  3. 运用大局的OkHttpClient目标发送同步或异步恳求,之后能够获取来自服务器的呼应。

本文剖析的源码,会以上述一个完好的网络恳求流程为主线进行。

Headers 类的规划-头字段

OkHttp 中,Headers类表明 Http 恳求或呼应报文中的头字段,运用Builder方式来创立目标。

Headers 成员变量

private final String[] namesAndValues;

namesAndValues是一个字符串数组,用来存储头字段。因为头字段是键值对的方式,所以在namesAndValuesindex*2方位存储keyindex*2 + 1方位存储value。如下面的方式。

Android | 彻底理解 OkHttp 源码篇

Headers 成员办法

下面是一些比较重要的成员办法介绍

// 依据 key(name) 获取头字段的 value
public @Nullable String get(String name)
// 回来头字段的个数
public int size()
// 回来在数组中第 index 个头字段的 key
public String name(int index)
// 回来在数组中第 index 个头字段的 value
public String value(int index)
// 回来整个恳求头或呼应头的字节数
public long byteCount()

RequestBody 类的规划-恳求体

RequestBody是一个笼统类,内部没有成员变量。它表明 Http 恳求报文中的恳求体,在 Post 恳求方式中,需要运用到RequestBody来构建恳求体。依据恳求体内容编码类型的不同,RequestBody有两个子类分别是FormBodyMultipartBody,对应application/x-www-form-urlencodedmultipart/form-data两种编码方式。

RequestBody 成员办法

// 回来媒体类型
public abstract @Nullable MediaType contentType()
// 写入数据到输出流
public abstract void writeTo(BufferedSink sink) throws IOException
// 回来恳求体的字节数
public long contentLength() throws IOException
// 创立一个恳求体
public static RequestBody create(params)

自定义媒体类型和输出流

RequestBody有两个笼统办法contentTypewriteTo,答应咱们创立匿名类重写这两个办法自定义媒体类型(Content-Type)和写入到输出流的数据,愈加灵活。

create 创立一个恳求体

create有多个重载的办法,是RequestBody类创立RequestBody目标的默许完成。大多数时候,咱们能够运用create办法来便利的创立一个恳求体。

Request 类的规划-恳求报文

咱们现已熟悉了HeadersRequestBody,它们是Request类的重要组成部分。Request类表明一个恳求报文,相同运用Builder方式来创立目标。

Request 成员变量

// 恳求 url
final HttpUrl url;
// 恳求办法(get, head, post, put, delete 等)
final String method;
// 恳求头字段
final Headers headers;
// 恳求体
final @Nullable RequestBody body;
// 浏览器缓存操控,存储了 Cache-Control 头字段相关的指令信息。
private volatile @Nullable CacheControl cacheControl; // Lazily initialized.

Request类的规划严格遵守了 Http 协议的恳求报文格式,由恳求行,恳求头,恳求体三大部分组成。一起Request类还对头字段Cache-Control的值做了封装。Request类的成员办法都是用来获取这些字段值的,这里不做介绍。

ResponseBody 类的规划-呼应体

ResponseBody表明 Http 呼应报文中的呼应体,它被规划成一个笼统类,没有成员变量。它表明 Http 呼应报文中的呼应体。

ResponseBody 成员办法

// 回来媒体类型
public abstract @Nullable MediaType contentType();
// 回来呼应体的字节数
public abstract long contentLength();
// 回来输入流,用于读取呼应体的数据
public abstract BufferedSource source();
// 将呼应体以字节数组的方式回来
public final byte[] bytes() throws IOException;
// 创立一个呼应体
public static ResponseBody create(@Nullable MediaType contentType, String content);

实例化一个ResponseBody需要重写contentTypecontentLengthsource3个笼统办法。create有多个重载办法,是ResponseBody类创立ResponseBody实例的默许完成,咱们能够运用create系列办法轻松创立出一个呼应体。

Response 类的规划-呼应报文

Response类表明呼应报文,运用Builder方式来创立目标。

Response 成员变量

protocolcodemessage3个字段组成了呼应行,headers字段表明呼应头字段,body字段表明呼应体。Response类成员办法都是用来获取字段值的,这里不做介绍。到此咱们现已剖析完了 OkHttp 中与恳求报文和呼应报文相关的类,了解这些基础类的规划有利于咱们后边源码的阅览。

// 为呼应 Http 重定向或身份验证生成的 request
final Request request;
// Http 协议版本
final Protocol protocol;
// Http 呼应状况码
final int code;
// Http 状况描述
final String message;
// TLS 握手记录,Https 相关
final @Nullable Handshake handshake;
// 呼应头字段
final Headers headers;
// 呼应体
final @Nullable ResponseBody body;
// 从网络接收到的原始呼应报文,若运用了缓存将会回来 null
final @Nullable Response networkResponse;
// 从缓存获取的原始呼应报文,若没用运用缓存将会回来 null
final @Nullable Response cacheResponse;
// Http 重定向或身份验证的呼应报文
final @Nullable Response priorResponse;
// 发送恳求报文时的时刻戳
final long sentRequestAtMillis;
// 收到呼应时的时刻戳
final long receivedResponseAtMillis;
// 单个 Http 恳求和呼应的管理类
final @Nullable Exchange exchange;
// 缓存操控,存储了 Cache-Control 头字段相关的指令信息。
private volatile @Nullable CacheControl cacheControl; // Lazily initialized.

Call 接口的规划-现已准备好的恳求

Call是一个接口,它表明一个现已准备好被履行的恳求(调用)。一个Call能够在履行过程中被撤销,可是只能够履行一次,不答应被重复履行。

Call 接口办法

// 回来恳求报文
Request request();
// 同步履行当时恳求,会堵塞直到呼应回来。回来后需要封闭资源,以避免资源走漏
Response execute() throws IOException;
// 异步履行当时恳求
void enqueue(Callback responseCallback);
// 撤销当时正在履行的恳求
void cancel();
// 回来当时恳求是否正在履行
boolean isExecuted();
// 回来当时恳求是否被撤销
boolean isCanceled();
// 回来超时目标
Timeout timeout();
// 复制一个一样的恳求
Call clone();

RealCall 类的规划-Call 接口的完成类

Call是一个接口,RealCall类完成了这个接口。

RealCall 成员变量

// OkHttp 客户端
final OkHttpClient client;
// 运用层和网络层的数据传输前言类
private Transmitter transmitter;
// 原始恳求报文
final Request originalRequest;
// 是否为 WebSocket
final boolean forWebSocket;
// Guarded by this.
// 是否正在履行
private boolean executed;

RealCall 成员办法

RealCall除了完成Call接口中的办法外,还新增了newRealCallgetResponseWithInterceptorChain两个重要办法。下面会对重要的办法进行解说。

// 静态办法,回来一个 RealCall 目标
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket);
// 回来被拦截器链处理过的呼应报文
Response getResponseWithInterceptorChain() throws IOException;

newRealCall-实例化一个RealCall目标

newRealCall会对实例化一个RealCall目标,并对clienttransmitteroriginalRequestforWebSocket4个字段赋值初始化。

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.transmitter = new Transmitter(client, call);
    return call;
}

execute-同步履行当时恳求

execute会开端同步履行当时的恳求,并进行调用超时检测,即在 OkHttpClient 中设置的callTimeout调用超时包含的过程有:DNS解析,树立衔接,写入恳求报文,服务器处理,读取呼应报文的全过程。假如在规则的超时时刻内,这些过程没有完成,则认为是调用超时。

@Override public Response execute() throws IOException {
    synchronized (this) {	// 加同步锁,将 executed 置为 true
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    // 进入调用超时检测
    transmitter.timeoutEnter();
    // 恳求开端事情回调
    transmitter.callStart();
    try {
      client.dispatcher().executed(this);
      // 同步履行本次恳求,获取呼应成果
      return getResponseWithInterceptorChain();
    } finally {
      // 完毕本次恳求
      client.dispatcher().finished(this);
    }
}

同步履行并不会敞开一个线程去履行恳求使命。假如你在主线程调用execute办法履行一个恳求,那么主线程会一向堵塞等待直到成果回来。

enqueue-异步履行当时恳求

enqueue是异步履行,恳求会被线程池中的某个线程履行。该办法需要传入Callback类型的参数,呼应成果将会在Callback接口的函数中回调,回调函数相同是在子线程中履行,所以不要在CallbackonFailureonResponse函数中对 UI 进行操作。

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {	// 加同步锁,将 executed 置为 true
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    // 恳求开端事情回调
    transmitter.callStart();
    // 异步履行本次恳求,
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

getResponseWithInterceptorChain- 获取经拦截器链处理过的呼应报文

无论是同步恳求仍是异步恳求,终究都会调用getResponseWithInterceptorChain办法来履行网络恳求使命。这阐明在 OkHttp 中任何一个恳求都必须通过拦截器链的处理。

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    // 第一步增加用户自定义的运用拦截器
    interceptors.addAll(client.interceptors());
    // 第二步增加失利重试重定向拦截器
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    // 第三步增加桥接拦截器
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    // 第三部增加缓存拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    // 第四步增加衔接拦截器
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
    // 第五步,若不是 WebSocket,则增加用户自定义的网络拦截器
      interceptors.addAll(client.networkInterceptors());
    }
    // 第六步,增加调用拦截器。这个拦截器链中的终究一个拦截器
    interceptors.add(new CallServerInterceptor(forWebSocket));
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
            originalRequest, this, client.connectTimeoutMillis(),
            client.readTimeoutMillis(), client.writeTimeoutMillis());
    boolean calledNoMoreExchanges = false;
    try {
      // 让拦截器链开端作业,回来呼应成果
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
            closeQuietly(response);
            throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
            transmitter.noMoreExchanges(null);
      }
    }
}

能够明晰的看见,一个恳求会先后通过自定义运用拦截器->失利重试重定向拦截器->桥接拦截器->缓存拦截器->衔接拦截器->自定义网络拦截器->调用拦截器,终究发送到服务器。而呼应成果通过的拦截器,刚好与上面的次序相反,是自定义网络拦截器->衔接拦截器->缓存拦截器->桥接拦截器->失利重试重定向拦截器->自定义运用拦截器。

Dispatcher 类的规划-异步恳求的调度员

Dispatcher类规划了一个线程池,担任管理一切异步恳求的履行,留意它并不担任同步恳求的履行,在前面的execute同步恳求办法中咱们现已看见它直接履行了getResponseWithInterceptorChain办法来获取呼应成果,并没有放在一个线程中去运转。

Dispatcher 成员变量

Dispatcher是个“调度员”,默许情况下最多能够一起履行 64 个异步恳求使命,而且最多答应 5 个恳求一起拜访同一个主机。在一个异步或同步恳求成果回来后,Dispatcher会查看还有没有恳求在履行,若没有恳求使命了,Dispatcher会去履行一个名为idleCallback的闲暇使命,咱们能够运用setIdleCallback办法来设置详细的闲暇使命。同步恳求和异步恳求都运用双端行列保存。

// 默许最多能够一起履行 64 个恳求
private int maxRequests = 64;
// 默许最多答应 5 个恳求一起拜访同一个主机
private int maxRequestsPerHost = 5;
// 没有恳求使命时(一切恳求履行完毕),Dispatcher 履行的闲暇使命
private @Nullable Runnable idleCallback;
// 线程池
private @Nullable ExecutorService executorService;
// 准备被履行的异步恳求
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
// 正在被履行的异步恳求
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
// 正在履行的同步恳求
// 再次阐明, Dispatcher 只担任保存和撤销同步恳求使命,并不会履行它
// 因为在调用 execute 办法后,同步使命就现已开端履行了
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

Dispatcher 成员办法

Dispatcher类能够用来履行保存在行列中的异步使命,并能够随时撤销履行。

// 创立线程池
public synchronized ExecutorService executorService();
// 设置能一起履行的最大恳求数
public void setMaxRequests(int maxRequests);
// 设置对每个主机的最大恳求数
public void setMaxRequestsPerHost(int maxRequestsPerHost);
// 设置闲暇使命
public synchronized void setIdleCallback(@Nullable Runnable idleCallback);
// 增加异步恳求使命到行列中并准备履行
void enqueue(AsyncCall call);
// 在异步恳求行列中查找是否已存在对目标主机 host 的恳求
@Nullable private AsyncCall findExistingCallWithHost(String host);
// 撤销一切恳求的履行,包括同步和异步恳求
public synchronized void cancelAll();
// 履行恳求
private boolean promoteAndExecute();
// 增加同步恳求使命到行列中
synchronized void executed(RealCall call);
// 恳求使命完成
private <T> void finished(Deque<T> calls, T call);
// 回来当时等待被履行的恳求
public synchronized List<Call> queuedCalls();
// 回来当时正在履行的恳求
public synchronized List<Call> runningCalls();

promoteAndExecute-将异步使命放到线程池中履行

RealCallenqueue办法终究会调用到promoteAndExecute,这个办法会将readyAsyncCalls行列中的异步使命移动到runningAsyncCalls中,然后将这些使命放到executorService线程池中履行。

private boolean promoteAndExecute() {
    assert (!Thread.holdsLock(this));
    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    // 将使命从 readyAsyncCalls 移动到 runningAsyncCalls
    synchronized (this) {
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
            AsyncCall asyncCall = i.next();
            // 若正在履行的异步使命数 >= maxRequests,退出循环
            if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
            // 若对当时主机的恳求数 >= maxRequestsPerHost,刚恳求本次不履行
            if (asyncCall.callsPerHost().get() >= maxRequestsPerHost,) continue; // Host max capacity.
            i.remove();
            asyncCall.callsPerHost().incrementAndGet();
            executableCalls.add(asyncCall);
            runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }
    // 将可履行的异步使命放到线程池中履行
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      // 调用 AsyncCall 的 executeOn 办法
      asyncCall.executeOn(executorService());
    }
    return isRunning;
}

AsyncCall 类的规划-履行异步恳求的真正人

promoteAndExecute办法咱们看到终究的异步恳求使命会交给AsyncCallAsyncCallRealCall的一个内部类,它承继自NamedRunnable,而NamedRunnable完成了Runnable接口,所以AsyncCall能够被线程履行。AsyncCall中的execute办法正是被线程履行的办法。与RealCall同步恳求的execute办法对比来说,整体流程基本共同。首要进入超时检测,接着启动拦截器链来处理恳求,终究获取呼应成果。

@Override protected void execute() {
  boolean signalledCallback = false;
  // 进入调用超时检测
  transmitter.timeoutEnter();
  try {
    // 经拦截器链处理后,获取呼应成果
    Response response = getResponseWithInterceptorChain();
    signalledCallback = true;
    // 恳求成功回调 onResponse 办法
    responseCallback.onResponse(RealCall.this, response);
  } catch (IOException e) {
    // 呈现 IO 异常
    if (signalledCallback) {
      // Do not signal the callback twice!
      Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
    } else {
      // 恳求失利,回调 onFailure
      responseCallback.onFailure(RealCall.this, e);
    }
  } catch (Throwable t) {
    // 呈现其他异常
    cancel();
    if (!signalledCallback) {
      IOException canceledException = new IOException("canceled due to " + t);
      canceledException.addSuppressed(t);
      // 恳求失利,回调 onFailure
      responseCallback.onFailure(RealCall.this, canceledException);
    }
    throw t;
  } finally {
    // 告诉 Dispatcher 本次恳求使命完毕,你能够去履行其他待履行的使命或闲暇使命
    client.dispatcher().finished(this);
  }
}

总结

本文介绍了 OkHttp 中与 Http 协议相关的类,异步恳求使命的调度员Dispatcher,履行同步恳求使命的RealCall,履行异步恳求使命的AsyncCall。信任通过本文的剖析,咱们对 OkHttp 这个框架的作业流程有了基本的了解。本文所解说的 OkHttp 源码侧重于一个网络恳求的全过程,还有很多未涉及的地方,如拦截器、衔接池、缓存等并没有深入剖析他们的源码和运用,对于 OkHttp 更深一点的问题,后边我会写新的文章解说,欢迎咱们继续重视。

写在终究

假如你对我感兴趣,请移步到 blogss.cn ,或重视大众号:程序员小北,进一步了解。

  • 假如本文协助到了你,欢迎点赞和重视,这是我继续创作的动力 ❤️
  • 因为作者水平有限,文中假如有过错,欢迎在评论区指正 ✔️
  • 本文首发于,未经许可制止转载 ️