1、okHttp的工作流程
1.1、整体流程
①、OkHttpClient实现 Call.Factory,负责为 Request 创建 Call。
②、 RealCall 为具体的 Call 实现,其 enqueue() 异步接口通过 Dispatcher 利用 ExecutorService 实现,而最终进行网络请求时和同步 execute() 接口一致,都是通过 RealCall中的getResponseWithInterceptorChain()函数实现。
③、getResponseWithInterceptorChain() 中利用 Interceptor 链条,分层实现缓存、透明压缩、网络 IO 等功能。
1.2、各大拦截器介绍
1.2.1、getResponseWithInterceptorChain()方法
Response getResponseWithInterceptorChain()throws IOException {
// Build a full stack of interceptors.
List interceptors =new ArrayList<>();
interceptors.addAll(client.interceptors()); -------> (1)
interceptors.add(retryAndFollowUpInterceptor); -------> (2)
interceptors.add(new BridgeInterceptor(client.cookieJar())); -------> (3)
interceptors.add(new CacheInterceptor(client.internalCache())); -------> (4)
interceptors.add(new ConnectInterceptor(client)); -------> (5)
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors()); -------> (6)
}
interceptors.add(new CallServerInterceptor(forWebSocket)); -------> (7)
Interceptor.Chain chain =new RealInterceptorChain(interceptors,null,null,null,0,
originalRequest,this,eventListener,client.connectTimeoutMillis(),
client.readTimeoutMillis(),client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
(1)、在配置OkHttpClient时设置的interceptors。
(2)、负责失败重试以及重定向的RetryAndFollowUpInterceptor。
(3)、负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的BridgeInterceptor。
(4)、负责读取缓存直接返回、更新缓存的CacheInterceptor。
(5)、负责和服务器建立连接的ConnectInterceptor。
(6)、配置OkHttpClient时设置的networkInterceptors。
(7)、负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor。
1.2.2 RetryAndFollowUpInterceptor
负责请求的重试和重定向
1.2.3 BridgeInterceptor
负责将原始Requset转换给发送给服务端的Request以及将Response转化成对调用方友好的Response。具体就是对request添加Content-Type、Content-Length、cookie、Connection、Host、Accept-Encoding等请求头以及对返回结果进行解压、保持cookie等。
CacheInterceptor
负责读取缓存以及更新缓存。
在请求阶段:
1、读取候选缓存cacheCandidate
2、根据originOequest和cacheresponse创建缓存策略CacheStrategy
3、根据缓存策略,来决定是否使用网络或者使用缓存或者返回错误
结果返回阶段:
负责将网络结果进行缓存(使用于DiskLruCache)。
强制缓存:当客户端第一次请求数据是,服务端返回了缓存的过期时间(Expires与Cache-Control),没有过期就可以继续使用缓存,否则则不适用,无需再向服务端询问。
对比缓存:当客户端第一次请求数据时,服务端会将缓存标识(Etag/If-None-Match与Last-Modified/If-Modified-Since)与数据一起返回给客户端,客户端将两者都备份到缓存中 ,再次请求数据时,客户端将上次备份的缓存
标识发送给服务端,服务端根据缓存标识进行判断,如果返回304,则表示缓存可用,如果返回200,标识缓存不可用,使用最新返回的数据。
ETag是用资源标识码标识资源是否被修改,Last-Modified是用时间戳标识资源是否被修改。ETag优先级高于Last-Modified。
ConnectInterceptor:负责与服务器建立连接
使用StreamAllocation.newStream来和服务端建立连接,并返回输入输出流(HttpCodec),实际上是通过StreamAllocation中的findConnection寻找一个可用的Connection,然后调用Connection的connect方法,使用socket与服务端建立连接。
CallServerInterceptor:负责从服务器读取响应的数据
主要的工作就是把请求的Request写入到服务端,然后从服务端读取Response。
(1)、写入请求头
(2)、写入请求体
(3)、读取响应头
(4)、读取响应体
2、连接池原理
1、在Reuqst的header中将Connection设置为keepalive来复用连接。
2、Okhttp支持5个并发KeepAlive,默认链路生命为5分钟(链路空闲后,保持存活的时间),连接池有ConectionPool实现,对连接进行回收和管理。
2.1、连接池的清理
1、ConectionPool在内部使用一个异步线程来清理连接。
2、当连接池中有连接时:清理任务由cleanup()方法完成,首先执行清理,并返回下次需要清理的间隔时间,调用调用wait() 方法释放锁。等时间到了以后,再次进行清理,并返回下一次需要清理的时间间隔,再次进入wait,以此循环往复。
3、当连接池中没有连接时:cleanup()返回-1,跳出循环,下次有连接加进来时,再次开启线程进行循环清理。
4、之所以连接池线程可以跳出循环,是因为,他是子线程,而looper选择一直阻塞是因为他是主线程,如果跳出,程序执行结束。
3、OkHttp中Dispatcher和线程池
3.1、OkHttp中线程池
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;
}
1、OkHttp中的线程池是一个 newCachedThreadPool。
2、所以在 OkHttp 中线程池只是一个辅助作用,仅仅是用来做线程缓存,便于复用的。
3、真正控制请求并发数量和执行时机是通过调度器 Dispatcher 完成的。
3.2、OkHttp中Dispatcher
RealCall.execute
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
//将RealCall加入Dispatcher的runningSyncCalls队列
client.dispatcher().executed(this);
//调用getResponseWithInterceptorChain获取Response
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
//调用Dispatcher的finished方法,将自身从runningSyncCalls移除
client.dispatcher().finished(this);
}
}
1、先将RealCall加入Dispatcher的runningSyncCalls队列。
2、然后调用getResponseWithInterceptorChain获取Response。
3、最后调用Dispatcher的finished方法,将自身从runningSyncCalls移除。
RealCall.enqueue
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call);
}
promoteAndExecute();
}
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
i.remove();
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;
}
如果当前正在执行的RealCall的数量小于最大并发数maxRequest(64),并且该call对应的Host上的call小于同一host上的最大并发maxRequestsPerHos(5),则将该call加入runningAsyncCalls,并将这个call放到线程池中进行执行,否则加入readyAsyncCall排队等待。
注意:
同步请求和异步请求执行完成之后,都会调用dispatcher的finished方法,将自身从对应的队列中移除,然后进行轮询readyAsyncCalls队列,取出ready的异步任务在满足条件下放到线程池中执行。
3.3、Dispatcher.中的并发数量及三个队列的作用
1、maxRequests = 64 // 最大并发请求数为64
2、maxRequestsPerHost = 5 //每个主机最大请求数为5
3、ExecutorService executorService //消费者池(也就是线程池)
4、Deque
5、Deque
6、Deque
4、OkHttp中的设计模式
1、责任链模式:拦截器链
2、单例模式:线程池
3、观察者模式:各种回调监听
4、策略模式:缓存策略
5、Builder模式:OkHttpClient的构建过程
6、外观模式:OkHttpClient封装了很对类对象
7、工厂模式:Socket的生产
5、OkHttp的优势
5.1、功能方面
功能全面,满足了网络请求的大部分需求。
5.2、网络优化方面
1、内置连接池,支持连接复用
2、支持gzip压缩响应体
3、通过缓存避免重复的请求
4、支持http2,对一台机器的所有请求共享同一个socket
5.3、扩展性方面
拦截器模式使得我们很容易添加一个自定义拦截器对请求和返回结果进行处理。