看了有一段时间的 OkHttp3 的源码了. 今天动笔开始写一写, 本篇文章只是简单的写一下 OkHttp3 的一个过程.(以后的文章会对 OkHttp3 的内部进行分析).
OkHttp3 优点:
1. 支持 http1/http2
2. 对一台机器的所有请求共享同一个 socket
3. 内部有连接池, 减少创建和链接时过多的时间消耗
设计模式
整个 OkHttp 用到很多设计模式:
1. 外观模式:
OKHttpClient 里面封装了很多的类对象. 其实就是将 OKHttp 的很多功能模块, 全部封装到这个类中, 让这个类单独提供对外的 API.
外观模式 (Facade Pattern) 隐藏系统的复杂性, 并向客户端提供了一个客户端可以访问系统的接口. 这种类型的设计模式属于结构型模式, 它向现有的系统添加一个接口, 来隐藏系统的复杂性.
这种模式涉及到一个单一的类, 该类提供了客户端请求的简化方法和对现有系统类方法的委托调用.
2. 建造者模式
正因为内部功能块比较多, 大量使用了假造这模式, 比如 Reuqest 的创建, 等等吧.
建造者模式 (Builder Pattern) 使用多个简单的对象一步一步构建成一个复杂的对象. 这种类型的设计模式属于创建型模式, 它提供了一种创建对象的最佳方式.
一个 Builder 类会一步一步构造最终的对象. 该 Builder 类是独立于其他对象的.
主体架构和大概流程
IMG_20180129_093053.png
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 workQueue: new SynchronousQueue() 一个阻塞队列, 用来存储等待执行的任务
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 对象. 所以整个拦截器的工作流程是这样的:
微信图片_20180129200804.jpg
那么到此为止一个完整的 Okttp 请求的流程就已经完成.
来源: http://www.jianshu.com/p/092336d80423