okhttp源码分析
okhttp是一款强大的网络访问框架,使用很久了,通过源码分析,将理解到okhttp的加载流程和设计理念,以及一些核心OkHttpClient、Request、RealCall、Dispatcher、Interceptor等核心类的作用。按照他的加载流程进行分析,先来看看最简单的get请求:
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url("https://www.baidu.com/")
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {}@Override
public void onResponse(Call call, Response response) throws IOException {}
});
从上面的可以看出okhttp的get请求过程主要进行了三大步。分别是创建客户端、创建请求信息、包装请求信息、发起请求。因此我们按照这三大步骤进行逐个分析,在分析过程中尽量使用通俗易懂的话语对程进行一个总结。
一、OkHttpClient初始化流程 OkHttpClient被称为okhttp的客户端,在创建对象时候,OkHttpClient调用自己的另一个携带参数Bulider的构造方法B。传递过去得参数为new Bulider,这里的Bulider是它的内部类,创建Builder的时候,Builder的构造方法初始化了一些参数给自己的成员变量赋值这时候。这时候在B构造方法中获得传递过来的Builder,调用Builder的成员变量给OkHttpClient成员变量赋值,OkHttpClient的成员变量的大部分值都可以通过builder.变量名称获得,获取不到的,在本类中获取,这时候OkHttpClient初始化数据结束。简化伪代码如下:
public class OkHttpClient {
final int readTime;
public OkHttpClient(){
this(new Builder());
}public OkHttpClient(Builder builder) {
this.readTime= builder.readTime;
}public static final class Builder {
private int readTime;
public Builder(){
readTime = 20;
}
}public int defaultReadTime(){
return readTime;
}
}
很明显OkHttpClient的创建时使用了建造者设计模式,这种设计模式又称为生成器设计模式,其实在后面的Request、Response等对象的创建中也采用了这种设计模式,后面就不再介绍其创建过程了。因此在平时的开发中如果配置的参数信息量非常多也应该采用这种模式。
二、Request的创建过程 Request的主要功能是创建请求数据,在Request的类中有一个Builder内部类,参数的配置是通过他进行,包括url,请求方法、请求体、请求tag等等,配置好之后通过build进行创建返回Request对象。这里也是采用建造者模式,如上,不在贴上源码了。
三、请求数据的生成过程 Request对象虽然配置好了请求信息,但是他不直接参与请求的业务过程,仅仅是包含请求的信息,体现了他的单一职责。真正的请求数据的生成是在RealCall中。将Request请求信息传进OkHttpClient客户端的newCall方法中,返回RealCall实例对象,RealCall包装了请求信息和一些回调信息。如下可以看出来,他间接调用RealCall的newRealCall方法。
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
RealCall实现了Call接口,在这个方法里面传递进去上面创建的Request和OkHttpClient。然后通过new RealCall自己创建自己的实例对象call,在RealCall的构造方法中初始化RealCall的一些数据,这样call就生成了。在这个过程中还进行了一个操作是通过OkHttpClient的获取到监听请求过程的生命周期的接口。最后返回这个call。
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
//注意这里对RetryAndFollowUpInterceptor拦截器进行了一个初始化,后面会介绍
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
四、请求的发起过程 请求的发起是通过Call的enqueue方法进行请求。这个Call来自于上一步的RealCall的实例,找到对应的enqueue方法,首先会检查这个请求是否被执行过,请求过的话会被标记,然后调用上一步初始化的生命周期方法的start方法,表明请求开始了;然后把这个Call进行包装为一个AsyncCall类里面,进行请求。
@Override
public void enqueue(Callback responseCallback) {
//判断是否已经执行过了本次请求,执行过的话标记为true
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//记录请求的信息
captureCallStackTrace();
//设置开始请求回调
eventListener.callStart(this);
//执行请求
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
从上面可以看出dispatcher方法来自于OkHttpClient对象,通过这个enqueue方法进行请求。
五、Dispatcher的请求过程 Dispatcher是一个调度器,用于对任务的调度分发过程,为了管理所有的请求,Dispatcher采用了队列+生产+消费的模式。里面包含了三个ArrayDeque双端队列,这种双端能够解决在频繁出队的情况下产生的假溢出现象,非常适合这种环境。同时规定了最大的请求数量为64个,最大的请求host个数为5个。如下:
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
/** 当资源不足时候进行缓存 */
private final Deque readyAsyncCalls = new ArrayDeque<>();
/** 管理所有的异步请求 */
private final Deque runningAsyncCalls = new ArrayDeque<>();
/** 管理所有的同步请求 */
private final Deque runningSyncCalls = new ArrayDeque<>();
初步了解了Dispatcher之后来看看enqueue方法,首先判断如果正在运行的队列里面的call小于最大数量,并且请求的host的数量小于最大数量则将当前的call加入到正在运行的队列里面,进行请求。否则添加到缓存队列里面。
synchronized void enqueue(AsyncCall call) {if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//将call加入到正在运行的队列里面
runningAsyncCalls.add(call);
//开始执行请求
executorService().execute(call);
} else {
//添加到缓存队列里面
readyAsyncCalls.add(call);
}
}
六、请求线程的创建 在上一步满足条件开始请求会从executorService获取线程池对象。
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;
}
可以看出他的核心线程数为0,最大线程数为Integer.MAX_VALUE,空闲线程的存活时间是60秒,任务队列为没有容量的SysnchronousQueue作为线程池的工作队列,线程名称为OkHttp Dispatcher。这个和Executors.newCachedThreadPool()可缓存的线程池非常像。
七、请求任务的封装 在请求线程创建好之后就需要创建请求任务,在上面的第五步中可以看出请求任务传递的是一个AsyncCall,它来自于第四步的new AsyncCall(responseCallback),,AsyncCall就是RealCall的一个final类型的内部类,他继承自NamedRunnable,NamedRunnable实现了Runnable接口,重写run方法,而AsyncCall的execute就是run方法的具体实现类。直接看execute方法:
@Override
protected void execute() {
boolean signalledCallback = false;
try {
//请求各个拦截链获得响应 ——1
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 {
//一轮请求完成 ——2
client.dispatcher().finished(this);
}
}
这一部分是Okhttp请求的核心部分,所有的请求过程都封装在这里面,包括三大部分:第一、请求拦截器生成请求数据;第二、请数据回调,第三、请求完成之后的处理,下面对这一、三部分进行详细说明。
八、拦截链的请求过程 Interceptor拦截器,是OkHttp的核心,Call、Callback和Dispatcher虽然很有用,但对于解决复杂的网络请求没有太多作用,使用了分层设计的拦截器Interceptor才是解决复杂网络请求的核心,这也是OkHttp的核心设计。网络编程有七层,每层都有每层的含义,层层依赖,但每层都各司其职,OkHttp也采用了分层设计思想,每层Interceptor的输入都是Request,输出都是Response,所以可以一层层地加工Request,再一层层地加工Response。每一层是通过一个链将其连接起来。所以要了解okhttp的请求内容、重试机制、缓存、回调等核心思想就需要分析这些系统拦截器的源码进行深入了解。
不管是同步请求还是异步请求,最终都会调用拦截器来处理网络请求。上一步中标注的——1中就是拦截器的请求部分,是OkHttp的精髓所在:
Response response = getResponseWithInterceptorChain();
通过过getResponseWithInterceptorChain这个方法,他会生成Interceptor集合,里面添加各种拦截器,例如重试、缓存、拦截、网络等拦截器,然后根据这一系列的拦截器构建出一个Interceptor.Chain实例对象,之后调用RealInterceptorChain的processed方法。如下:
Response getResponseWithInterceptorChain() throws IOException {
//创建拦截器集合
List 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);
}
【okhttp源码分析】每个拦截器的具体内容这里不作介绍,后期会加上,这里主要介绍整个请求的流程,详情看上面的备注。找到对应的proceed方法,proceed方法省略了部分代码,便于理解,如下:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {}
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
//从拦截器例表中获取一个拦截器
Interceptor interceptor = interceptors.get(index);
//根据获取到的拦截器和拦截器链获得Response
Response response = interceptor.intercept(next);
return response;
}
上面的interceptor.intercept(next); 对应的是每个具体的拦截器的intercept,以重试拦截器为例,为了便于理解删除了部分代码:
@Override
public Response intercept(Chain chain) throws IOException {
//根据拦截器链获取Request、call
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
//生成请求链路信息
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
while (true) {
try {
//重点:
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {}
}
上面的重点部分:
response = realChain.proceed(request, streamAllocation, null, null);
可以看出在重试拦截器的intercept方法里面又调用RealInterceptorChain的proceed,有重复了上面的方法,可以推测出,在RealInterceptorChain的proceed方法里面又是调用下一个拦截器有的proceed,事实上就是这样。一直到调用CallServerInterceptor的proceed结束。这样,就形成一个chain.process(intreceptor)-->interceptor.intercept(chain)-->chainprocess(intreceptor)-->interceptor.intercept(chain)的循环,这个过程中,chain不断消费,直至最后一个拦截器,链式调用结束,获得Response。
九、请求队列数据的读取过程 又回到第七步,获取response数据后接着进行设置请求成功、失败、异常的回调,最后执行finally代码:
finally {
client.dispatcher().finished(this);
}
finally对本轮请求数据完成的后期操作。在第五步中,有三个缓存队列,专门用于对call的缓存,当一次请求执行完毕的时候,就需要从队列读取下一次请求了,finished方法就是处理这方面的内容。这里设计的非常好,因为网络请求失败是很常见的场景,必须能在失败时避免阻塞队列。写着在finally里面,不管是请求异常还是正常都会执行这段代码,保证了队列执行不会被阻塞,不会因为请求异常没有执行到队列里面的其他的无法执行。在Dispatcher类中找到对应的方法:
private void finished(Deque calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
//重点promoteCalls
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
可以看到这个方法里面加了同步锁synchronized,加同步锁的目的是为了保证从缓存队列里面读取的call加入到正在执行的队列里面数据的有序性,因为readyAsyncCalls队列移动到runningAsyncCalls队列里面,这在多线程场景下并不安全。看重点:
private void promoteCalls() {
//正在运行的队列大小超过最大容量,则不进行转移
if (runningAsyncCalls.size() >= maxRequests) return;
//正在运行队列为空则返回
if (readyAsyncCalls.isEmpty()) return;
//遍历缓存队列
for (Iterator i = readyAsyncCalls.iterator();
i.hasNext();
) {
AsyncCall call = i.next();
//正在运行的host小于最大的
if (runningCallsForHost(call) < maxRequestsPerHost) {
//移除call加入到正在运行的队列
i.remove();
runningAsyncCalls.add(call);
//扔进线程池中,执行任务,重复第六步
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return;
// Reached max capacity.
}
}
总结:
以上就是okhttp的加载流程源码分析,现在总结为一张图如下:
文章图片
推荐阅读
- 如何寻找情感问答App的分析切入点
- D13|D13 张贇 Banner分析
- 自媒体形势分析
- 2020-12(完成事项)
- Android事件传递源码分析
- Python数据分析(一)(Matplotlib使用)
- Quartz|Quartz 源码解析(四) —— QuartzScheduler和Listener事件监听
- 泽宇读书会——如何阅读一本书笔记
- Java内存泄漏分析系列之二(jstack生成的Thread|Java内存泄漏分析系列之二:jstack生成的Thread Dump日志结构解析)
- [源码解析]|[源码解析] NVIDIA HugeCTR,GPU版本参数服务器---(3)