看了有一段时间的OkHttp3的源码了。今天动笔开始写一写,本篇文章只是简单的写一下OkHttp3的一个过程。(以后的文章会对OkHttp3的内部进行分析)。
OkHttp3 优点:
1.支持http1/http2
2.对一台机器的所有请求共享同一个socket
3.内部有连接池,减少创建和链接时过多的时间消耗
设计模式
整个OkHttp用到很多设计模式:
1.外观模式:
OKHttpClient 里面封装了很多的类对象。其实就是将OKHttp的很多功能模块,全部封装到这个类中,让这个类单独提供对外的API。
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。
2.建造者模式
正因为内部功能块比较多,大量使用了建造者模式,比如Reuqest的创建,等等吧。
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。
主体架构和大概流程
OkHttp的主要使用就是:
new OkHttpClient().newCall(request).execute();(同步);
new OkHttpClient().newCall(request).enqueue();(异步)
通过这行代码,我们可以捋出OkHttp的大致流程:
execute同步请求的方法:
newCall(request)的方法是返回一个RealCall
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//是用来跟踪调用栈的信息的,不用深究
captureCallStackTrace();
try {
A://此方法只是把请求加入队列并没有真正执行;
client.dispatcher().executed(this);
B://真正执行请求进行网络请求返回结果
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
A:此处调用了Dispatcher的executed的方法 把Call加入到队列中runningSyncCalls.add(call);(稍后分析Dispatcher)
B:调用拦截器返回结果;
enqueue异步请求的方法:
调用RealCall的enqueue方法
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
A:
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
A:调用了Dispatcher的enqueue方法。可以看到此方法参数中创建了一个AsyncCall(构建call对象)。
其中Dispatcher的enqueue的方法
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
可以看出此段代码也是把请求加入队列,然后执行 executorService().execute(call),其中AsyncCall 继承NamedRunnable 是一个线程,我们应该看他的execute()方法,此方法中也同样调用了 Response response = getResponseWithInterceptorChain();方法。
executorService属于线程池,所以此方法executorService().execute(call)执行的是AsyncCall方法的execute方法;(稍后会对Dispatcher进行分析)
Dispatcher(任务分发器)
我们知道OkHttp内部是有一个线程池的,这个线程池就在Dispatcher中,其实这个类就是一个任务队列。
那么我们来看一下Dispatcher的成员变量:
//最大并发请求数为64
private int maxRequests = 64;
//每个主机最大请求数是5
private int maxRequestsPerHost = 5;
//线程
private @Nullable Runnable idleCallback;
//线程池
/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService;
//准备执行的异步请求队列,对象是异步请求
/** 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<>();
//正在执行的同步请求队列,同样包括了已经取消了但是还未执行完的请求
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
看完成员变量我们发现其中有两个异步队列,这是为什么?
采用Deque作为缓存,按照入队的顺序先进先出,Deque双端队列,继承自Queue,我们通过这连个队列我们不难看出是采用了生产消费者模式,结合线程池实现了低阻塞的运行。在大多数时候,每个缓存它们都只是访问自己的双端队列,这样的话极大地减少了竞争。当工作者线程需要访问另一个队列时,它会从队列的尾部而不是头部获取工作,因此进一步降低了队列上的竞争程度。
Dispatcher的线程池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;
}
使用单利的方式创建线程池,那么我们来解释一下线程池的几个参数
1.int corePoolSize: 0 线程池的基本大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。
2.int maxmumPoolSize: Integer.MAX_VALUE 最大线程数,就是当前任务进行时,此线程池能扩充的最大值。这里是无限大。
3.long keepAliveTime:60 当前线程数大于核心线程数,成为空闲线程,当空闲线程存活时间大于这个时间就会被取消
4.TimeUnit unit: TimeUnit.SECONDS 存活时间的单位是秒
5.BlockingQueue<Runnable> workQueue: new SynchronousQueue<Runnable>() 一个阻塞队列,用来存储等待执行的任务
6.ThreadFactory threadFactory: 创建线程的工厂 Util.threadFactory("OkHttp Dispatcher", false)
前面我们分析了同步和一部的方法,就不做过多解释了。
我们来看一下如何从ready到runing的添加的。
每次Call结束的时候都会调用finshed
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!");
//每次remove完后,执行promoteCalls来轮转。
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
//线程池为空时,执行回调
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
这个方法可以看出来是遍历了readyAsyncCalls,把Call一一添加到了RunningAysncCalls。
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;
}
}
getResponseWithInterceptorChain()
这一步是OkHttp中最重要的一部,也是核心。
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);
return chain.proceed(originalRequest);
}
代码很简单就是将
自定义的拦截器和OkHttp内置的拦截器放到一个List集合中,然后把拦截器集合和Request一起创建了一个RealInterceptorChain对象,然后调用proceed方法把整个拦截器组合成链状。最终返回一个Response
责任链模式:一个请求沿着一条“链”传递,直到该“链”上的某个处理者处理它为止。
- client.interceptors() :
自定义的拦截器 - retryAndFollowUpInterceptor:
失败后重连或者服务器返回请求重新发起请求的拦截器。 - BridgeInterceptor:
链接客户端代码和网络代码的桥梁,也就是说配置请求内容(设置内容长度,内容编码,设置gzip压缩,添加cookie,设置其他报头) - CacheInterceptor
缓存机制拦截器,也就是说有没有满足请求的缓存有的话返回Cache。当服务器返回数据有变化时更新Cache,如果当前Cache失效就删除。 - ConnectInterceptor:
建立服务器连接,正式开启了网络请求,调用连接池,开启Socket链接。 - networkInterceptors:
配置 OkHttpClient 时设置 - CallServerInterceptor
向服务器发送请求,并最终返回Response对象供客户端使用。
如何让整个链状拦截器运转起来的?咱们现在看一下proceed方法
//正式开始调用拦截器工作
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection){
//省略部分与本文无关的代码
// 调用链中的下一个拦截器
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
//确保每个拦截器都调用了proceed方法()
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
//省略部分与本文无关的代码
return response;
}
其中有个index变量,每次调用都加1,然后获得下一个拦截器,procced方法并没有用for循环来遍历interceptors集合,而是重新创建了一个RealInterceptorChain对象,且新对象的index在原来RealInterceptorChain对象index之上进行index+1,并把新的拦截器链对象RealInterceptorChain交给当前拦截器Interceptor 的intercept方法;查看BridgeInterceptor、CacheInterceptor等Okhttp内置拦截器就可以印证这一点:在它们intercept的内部都调用了chain.proceed()方法,且每次调用都在会创建一个RealInterceptorChain对象。所以整个拦截器的工作流程是这样的:
那么到此为止一个完整的Okttp请求的流程就已经完成。