本文使用的OkHttp版本为3.14.1
为什么使用OkHttp
OkHttp是Square公司开源的网络请求框架,是目前Android使用最广泛的网络框架,从Android4.4开始,HttpURLConnection的底层实现采用OkHttp。
- 支持HTTP/2并且允许对同一足迹的所有请求共享一个Socket(即套接字,是对TCP传输层/IP网络层的一种具体实现)
- 如果非HTTP/2,通过连接池减少请求延迟
- 默认请求Gzip压缩数据
- 响应缓存,避免重复请求
OkHttp使用方法
标准的OkHttp使用方法分为4个步骤:
- 创建
Client - 创建
Request - 将
Client和Request包装成Call - 执行
Call
Demo.kt
val client = OkHttpClient()
fun get(url: String) {
val req = Request.Builder()
.url(url)
.build()
val call = client.newCall(req)
val resp = call.execute() // 同步请求
val body = resp.body()
System.out.println("$body")
// application/x-www-form-urlencoded
fun post(url: String) {
val reqBody = FromBody.Builder()
.add("city", "杭州")
.add("key", "123123123jfangbfjweinv")
.build()
val req = Request.Builder()
.url(url)
.post(reqBody)
.build()
val call = client.newCall(req)
...
}
它的接口封装得很简洁,生成Call后,会交给调度器Dispatcher和拦截器Inspector两个模块依次处理。
- 调度器:内部维护队列与线程池,完成请求调配
- 拦截器:完成整个请求过程
HTTP的请求过程是:域名经DNS服务器解析成IP地址 -> 三次握手,建立Socket连接,得到Socket对象 -> 将HTTP报文经Socket写出,开始数据传输
请求处理流程
分发器/调度器Dispatcher
调度器用来对请求进行调度,将请求直接执行,或者加入等待队列。
newCall()函数返回一个Call对象,Call是一个接口,其实现类为RealCall。Call对象通过execute()进行同步流程,通过enqueue()进入异步流程。
异步请求enqueue()
RealCall.java
public void enqueue(Callback callback) {
synchronized (this) { // 锁住RealCall对象
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(callback); // <--重点:封装为AsyncCall
}
Dispatcher.kt
维护了3个队列。
/** Ready async calls in the order they'll be run. */
private val readyAsyncCalls = ArrayDeque<AsyncCall>() // 等待执行的异步队列
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private val runningAsyncCalls = ArrayDeque<AsyncCall>() // 正在执行的异步队列
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private val runningSyncCalls = ArrayDeque<RealCall>() // 正在执行的同步队列
ArrayDeque内部由数组实现,其优点是:This class is likely to be faster than Stack when used as a stack, and faster than LinkedList when used as a queue.
异步请求的处理流程图如下:
Dispatcher拿到Call对象后,是如何判断将其放入ready队列还是running队列?带着问题继续分析。
Dispatcher.java
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call);
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
// the same host.
if (!call.get().forWebSocket) {
AsyncCall existingCall = findExistingCallWithHost(call.host());
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
}
}
promoteAndExecute();
}
默认所有的Call都会先加入到ready待执行队列,随后执行promoteAndExecute(),扫描队列时有两个限制:
- 正在执行的异步请求个数
<=64 - 同一域名的异步请求个数
<=5
同时满足这两个限制时,请求会被交给线程池执行。
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>(); // <--重点1:找出可以执行的Call
boolean isRunning;
synchronized (this) {
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) { // <--重点2:扫描ready待执行队列
AsyncCall asyncCall = i.next();
// 同时最多64请求并发,防止打满带宽和内存
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
// 统一域名最多5个请求并发——目的是为服务器限流,但收效有限
if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
i.remove(); // 满足了64+5的限制,该请求可以执行
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(executorService()); // 丢给线程池执行
}
return isRunning;
}
线程池初始化的配置如下:
corePoolSize=0,核心线程数为0,意味着空闲时可以回收全部线程maximumPoolSize=Integer.MAX_VALUE,最大线程数为Integer上限,允许在线程池内创建巨多线程keepAliveTime=60s,超过1分钟不再使用的链接会释放SynchronousQueue,容量为0的队列,无法缓存任何东西,一旦有Call丢过来则直接创建线程来执行
对于线程池而言,当一个任务通过execute(Runnable)添加到线程池时:
- 若线程数量小于
corePoolSize,则新建核心线程来处理这个任务 - 若线程数量大于等于
corePoolSize,该任务被添加到等待队列 - 若无法添加到等待队列(如等待队列已满),则执行以下逻辑
- 若线程数量小于
maximumPoolSize,则新建临时线程执行任务 - 若线程数量等于
maximumPoolSize,使用RejectedExecutionHandler拒绝策略,默认是抛异常
- 若线程数量小于
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
AsyncCall本身是一个Runnable对象,它的executeOn()函数最终会执行到自身的execute()方法,代码如下。
AsyncCall.java
@Override protected void execute() {
boolean signalledCallback = false;
transmitter.timeoutEnter();
try {
Response response = getResponseWithInterceptorChain();
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 {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
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) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
...
}
execute()中通过getResponseWithInterceptorChain()进入到拦截器逻辑,这里按下不表。在try-catch-finally代码块中,finally执行了client.dispatcher().finished(this),其目的是找出下一个将要执行的异步请求,将其放入线程池执行。
void finished(AsyncCall call) {
call.callsPerHost().decrementAndGet();
finished(runningAsyncCalls, call);
}
private <T> void finished(Deque<T> calls, T call) {
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
idleCallback = this.idleCallback;
}
boolean isRunning = promoteAndExecute();
if (!isRunning && idleCallback != null) {
idleCallback.run();
}
}
以上就是异步请求enqueue()的源码分析。
同步请求execute()
同步请求比较简单,RealCall.execute()会直接把请求丢给任务链,同时调用Dispatcher.executed(this),仅仅是把请求记录在runningSyncCalls中,在finished()时将其移出。
RealCall.java
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.timeoutEnter();
transmitter.callStart(); // <--重点1:可以实现EventListener接口来计算网络请求耗时
try {
client.dispatcher().executed(this); // <--重点2:
return getResponseWithInterceptorChain();
} finally {
client.dispatcher().finished(this);
}
}
Dispatcher.java
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
private <T> void finished(Deque<T> calls, T call) {
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
idleCallback = this.idleCallback;
}
boolean isRunning = promoteAndExecute();
if (!isRunning && idleCallback != null) {
idleCallback.run();
}
}
拦截器Interceptor
拦截器的入口是 RealCall.getResponseWithInterceptorChain()。
RealCall.java
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors()); // <--重点0:自定义拦截器插入在头部
interceptors.add(new RetryAndFollowUpInterceptor(client)); // <--重点1:按序排列5种自带拦截器
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors()); // <--重点2:自定义网络拦截器插入在倒数第二位
}
interceptors.add(new CallServerInterceptor(forWebSocket));
...
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
...
Response response = chain.proceed(originalRequest); // <--重点3:组装成RealInterceptorChain并调用其proceed函数,得到Response
...
return response;
责任链
拦截器采用责任链的设计模式,客户将请求发送到责任链,无需关心请求处理细节。责任链模式的优点是将1.请求的发送者和处理者解耦,2.可以自由组装处理者。
Android的事件分发机制就是采用的责任链模式。
责任链的处理顺序是:
- 输入
Request,顺序执行责任链 - 发送请求
- 接收请求结果
- 得到
Response,倒序执行责任链
责任链
默认拦截器
OkHttp自带有5种拦截器,可以在它们的基础上增加自定义拦截器。添加自定义拦截器的时机是创建Client的时候,自定义的拦截器有两个插入位置,会插入到责任链的头部or尾部。
addInterceptor:添加到责任链条头部addNetworkInterceptor:添加到责任链条倒数第二位,即CallServerInterceptor之前
| 拦截器 | 交出前执行 | 获得结果后执行 |
|---|---|---|
RetryAndFollowUpInterceptor 错误重试与重定向 | 判断用户是否取消了请求 | 根据响应码判断是否需要重定向,如果满足条件则重启所有拦截器 |
BridgeInterceptor 桥接 | 将HTTP协议必备的请求头加入(如Host)并添加默认行为(如Gzip) | 保存cookie并解析Gzip |
CacheInterceptor 缓存 | 判断是否读缓存(在初始化Client时需要声明缓存目录和大小) | 判断是否写缓存 |
ConnectionInterceptor 建立连接 | 找到或者新建连接,并获得socket流 | 无 |
CallServerInterceptor 服务器请求 | 在已经建立的连接中发送HTTP头以及报文 | 解析数据 |
RealInterceptorChain
这个类的含义是“真正的拦截器”,因为它是组装了所有拦截器后得到的拦截器链,核心逻辑是proceed()函数,它会取出拦截器链头部的Interceptor,执行其proceed()函数,在执行的过程中递归调用,则会依次执行拦截器链第二个、第三个、第N个拦截器。
RealInterceptorChain.java
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
throws IOException {
...
// 取出第一个interceptor并执行其intercept方法,递归
RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
...
return response;
}
分析拦截器的写法,主要关注其intercept()函数,可以分成三个部分:
- 执行
proceed前,对Request进行处理,此时已经有上一节点处理过(如果存在上个节点) - 执行
proceed时,将处理过的Request交给任务链下一节点 - 执行
proceed后,将任务链下一节点返回的Response进行处理 - 最后将处理过的
Response返回给任务链上个节点(如果存在)
总结
整个OkHttp的运转过程,可以用这张流程图概括。