敞开生长之旅!这是我参与「日新方案 2 月更文挑战」的第 5 天,点击查看活动概况
OkHttp 4.X 及其以上的版本的源码为 kotlin 语言编写。本文解说的 OkHttp 源码版本为 3.14.9,是 Java 语言编写的终究一个版本。因为 OkHttp 运用了 Okio 这个 IO 库,所以阅览本文之前,我非常主张咱们先阅览我之前写的两篇关于 Okio 的文章,再去阅览 OkHttp 的源码,就会扫清一道障碍。
- Android | 完全了解 Okio 之源码篇
- 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… 找到。
- 恳求体:恳求发送的数据。
呼应报文格式
Http 协议规则呼应报文格式由呼应行+呼应头字段+呼应体组成。
- 呼应行:由协议版本+状况码+状况描述组成,如
HTTP/1.1 200 ok
- 呼应头字段:键值对的方式组成,完好的头字段能够在 www.iana.org/assignments… 找到。
- 呼应体:呼应回来的数据
重要的头字段
头字段 | 阐明 | 取值 |
---|---|---|
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 恳求来说,要阅历如下几个过程。
- 运用 Builder 方式创立一个大局运用的
OkHttpClient
目标,能够用它来设置对一切恳求都生效的超时时刻、缓存、自定义拦截器等。 - 运用 Builder 方式创立一个
Request
,设置Request
恳求的 url,头字段,恳求体等。 - 运用大局的
OkHttpClient
目标发送同步或异步恳求,之后能够获取来自服务器的呼应。
本文剖析的源码,会以上述一个完好的网络恳求流程为主线进行。
Headers 类的规划-头字段
OkHttp 中,Headers
类表明 Http 恳求或呼应报文中的头字段,运用Builder
方式来创立目标。
Headers 成员变量
private final String[] namesAndValues;
namesAndValues
是一个字符串数组,用来存储头字段。因为头字段是键值对的方式,所以在namesAndValues
中index*2
方位存储key
,index*2 + 1
方位存储value
。如下面的方式。
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
有两个子类分别是FormBody
和MultipartBody
,对应application/x-www-form-urlencoded
和multipart/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
有两个笼统办法contentType
和writeTo
,答应咱们创立匿名类重写这两个办法自定义媒体类型(Content-Type)和写入到输出流的数据,愈加灵活。
create 创立一个恳求体
create
有多个重载的办法,是RequestBody
类创立RequestBody
目标的默许完成。大多数时候,咱们能够运用create
办法来便利的创立一个恳求体。
Request 类的规划-恳求报文
咱们现已熟悉了Headers
和RequestBody
,它们是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
需要重写contentType
,contentLength
,source
3个笼统办法。create
有多个重载办法,是ResponseBody
类创立ResponseBody
实例的默许完成,咱们能够运用create
系列办法轻松创立出一个呼应体。
Response 类的规划-呼应报文
Response
类表明呼应报文,运用Builder
方式来创立目标。
Response 成员变量
protocol
,code
,message
3个字段组成了呼应行,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
接口中的办法外,还新增了newRealCall
,getResponseWithInterceptorChain
两个重要办法。下面会对重要的办法进行解说。
// 静态办法,回来一个 RealCall 目标
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket);
// 回来被拦截器链处理过的呼应报文
Response getResponseWithInterceptorChain() throws IOException;
newRealCall-实例化一个RealCall目标
newRealCall
会对实例化一个RealCall
目标,并对client
,transmitter
,originalRequest
,forWebSocket
4个字段赋值初始化。
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
接口的函数中回调,回调函数相同是在子线程中履行,所以不要在Callback
的onFailure
和onResponse
函数中对 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-将异步使命放到线程池中履行
RealCall
的enqueue
办法终究会调用到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
办法咱们看到终究的异步恳求使命会交给AsyncCall
。AsyncCall
是RealCall
的一个内部类,它承继自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 ,或重视大众号:程序员小北,进一步了解。
- 假如本文协助到了你,欢迎点赞和重视,这是我继续创作的动力 ❤️
- 因为作者水平有限,文中假如有过错,欢迎在评论区指正 ✔️
- 本文首发于,未经许可制止转载 ️