Android 常用开源框架源码解析 系列 (一)网络框架之一 OkHttp
OkHttp
作用
- 处理网络请求
- 平时处理Http请求更加快速
- 流量更加节省
一、流程图
(1)创建OkHttpClient 对象;只会创建一次并作为全局进行保存的单例对象一般。
(2)创建Request 对象;封装一些请求报文信息,比如请求的头信息,url地址,请求的类型方法等
ps:通过build 构建者模式 链式生成Request对象
(3)得到RealCall对象; 通过Client.newCall()方法获取call对象,readcall代表一个实际的http请求
ps: 连接Request 和 Response 的一个桥梁;并将请求添加到dispatcher中
(4)Dispatcher 分发器类 ***;-确认同步 or 异步
ps:在这个类内部维护了一个线程池,这个线程池用于执行网络请求(同步/异步);当中通过三个队列维护池中的同步/异步请求
不断的从Request请求队列当中获取需要的RealCall,根据是否调用缓存(Cache)来选择是直接Connect拦截器+CallServer还是 Cache 拦截器
(5)interceptors 拦截器 ***;进行服务器数据的获取,构建一个拦截器列,通过依次执行这个拦截器列中的每个拦截器,将服务器获得的数据返回
ps:不论同步操作还是异步操作最终都会通过拦截器进行分发
a、RetryAndFollow interceptors拦截器 ;网络请求失败后的重试,服务器返回当前request请求需要重定向的时候
b、Bridge interceptors拦截器 ;设置request请求前的操作主要比如内容长度,编码,设置gzip压缩等内容拦截器同时可以添加cookie
c、Cache interceptors拦截器 ; 负责缓存的管理,可以直接返回cache给客户端
d、Connect interceptors拦截器 ;为当前request请求找到一个合适的连接(可以复用已有的链接)
e、CallServer interceptors拦截器 ;向服务器发起真正的网络请求,接受服务器返回数据并读取
——————————————————————————————————————
二、同步请求方法
简单步骤:
//1、创建OkHttpClient 请求客户端对象--单例对象创建
OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build();
public void synRequest() { //同步基本方法
//2、创建携带请求信息的Request对象
Request request = new Request.Builder().url("http://www.baidu.com")
.get().build();
//3、创建RealCall对象通过Client对象和Request对象构建实际的call对象;连接Request、Response桥梁
Call call = client.newCall(request);//同步异步分水岭
//4、通过Call对象的execute获取Response对象
try { //RealCall实际上是Call的具体实现类
Response response = call.execute();//响应报文信息 -同步
System.out.println("body:" + response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
同步 发送请求后,就会进入阻塞状态,直到收到数据响应(与异步最大的区别)
核心类:
在getResponseWithInterceptorChian()方法中,构建一个拦截器的链,通过依次执行拦截器的链当中的每一个不同作用的拦截器来获取服务器的数据返回。
源码分析 核心类:
(1)new OkHttpClient.Builder():
Builder() :需要很多参数的设置的时候考虑的方法
public Builder() {
dispatcher= new Dispatcher(); (1)
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool= new ConnectionPool();(2)
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
(1)Http请求重要的分发器,尤其确定异步请求是缓存等待还是直接处理;针对同步请求就是直接把request放入队列当中;
(2)connectionPool,连接池;
尤其进行统一管理客户端和服务器端的连接,当请求的URL是相同的时候可以选择复用;
实现哪些网络连接是打开状态,哪些是复用的策略的设置;
(2)new Request.Builder(). …. .build():
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
- 默认的连接方式:get请求
- 保存一些头部信息
//创建一个Request对象 把当前的build对象传递回去(把之前配置好的信息赋值给这个Request对象
比如url, method,headers,body,tag)
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
(3)client.newCall(request): //不论同步还是异步都会调用该方法
Call是个接口 其实现都在newRealcall() 这个实际的实现类当中
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
(4)newRealcall():
//创建一个RealCall对象 并配置监听事件 后返回该对象
static RealCall newRealCall(OkHttpClient client,
Request originalRequest,
boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
(5)RealCall():
1、持有了Client 和Request对象 2、赋值了一个重定向拦截器
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor= new RetryAndFollowUpInterceptor(client, forWebSocket);
}
(6)Realcall的execute():
@Override
public Response execute() throws IOException {
//1、在同步代码块 当中首先判断executed 标志位 是否为 true
同一个http请求只能执行一次,没有执行过就会把executed设置为true,若执行过抛异常
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//2、捕捉异常信息
captureCallStackTrace();
//3、每当调用call或是enqueue 就会开启这个监听器
eventListener.callStart(this);
try {
//4、返回一个dispatcher对象操作
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
dispatcher的作用:
1、维持call请求发给它的状态
2、维护一个线程池用于执行网络请求
call请求在执行任务的时候通过上面的dispatcher分发器类,将任务推到执行队列当中进行操作,操作完成后,再执行下面等待队列的任务
ps:
(7)client.dispatcher().executed(this):————>将call请求添加到request请求队列当中
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
在这里要先提一下 ,在dispatcher类中定义了3个队列
/** 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<>();
(8)getResponseWithInterceptorChain():拦截器链 方法 在内部依次调用拦截器进行操作
后面会注重解析该方法
finally {
(9) client.dispatcher().finished(this);
}
主动回收一些请求
(10)finished():从当前正在执行队列中移除这个同步请求,不能移除则抛异常
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!");
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount(); //计算正在执行的请求数量
idleCallback = this.idleCallback;
}
//正在执行的请求数量为0,同时满足idleCallback不为空
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
——————————————————————————————————————
三、异步请求方法
简单步骤:
public void asynRequest() {
Request request_ays = new Request.Builder().url("http://www.baidu.com")
.get().build();
//将Request封装成Call对象
Call call_ays = client.newCall(request_ays);
call_ays.enqueue(new Callback() {//开启一个工作线程执行下面的方法
@Override
public void onFailure(Call call, IOException e) {
切记更新UI操作要在主线程而不是在现在的子线程中执行
}
@Override
public void onResponse(Call call, Response response) throws IOException {
切记更新UI操作要在主线程而不是在现在的子线程中执行
}
});
}
Dispatcher 分发器类,有分发器来选择是异步请求执行还是就绪准备
源码分析 核心类:
前三步没有真正的发起网络请求,步骤同理 - 同步请求
call_ays.enqueue(new Callback() :
//4、传递一个Callback对象,请求结束以后的接口回调
@Override public void enqueue(Callback responseCallback) {
//1、锁住当前的RealCall对象, 并对executed进行判断,有没有执行过,执行过就抛出异常
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
//2、将responseCallback对象封装成AsyncCall对象
实际上AsyncCall 因为继承自Runnable,所以可以把其想象成就是Runnable
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
//3、调用拦截器的enqueue()方法进行异步网络请求
client.dispatcher().enqueue():
dispatcher已经在构建client的时候进行了初始化赋值操作!!!
重要方法!!!!!!!!!! -———————————>
synchronized void enqueue(AsyncCallcall) {
//a、runningAsyncCalls的数量是否小于最大请求数量(64)
同时 正在运行的主机的请求是否小于设定好的主机请求的最大值(5)
if (runningAsyncCalls.size() < maxRequests
&& runningCallsForHost(call) < maxRequestsPerHost) {
//b、满足条件把当前的异步请求添加到正在执行的异步请求队列当中
runningAsyncCalls.add(call);
//c、通过线程池执行异步请求
executorService().execute(call);
} else {
//d、添加到等待就绪队列当中
readyAsyncCalls.add(call);
}
}
executorService():线程池
//使用synchronized关键字锁对象,保证ExecutorService线程池是单例的
public synchronized ExecutorService executorService() {
if (executorService == null) {
//ThreadPoolExecutor (corePoolSize,最大线程池数量,)
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
AsyncCall():
前面提到AsyncCall继承自NamedRunnable,那这个方法做了什么呢?
asyncCall自身没有run()方法,是在Runnable()方法中的execute()方法中执行的,所以具体的实现应该在AsyncCall的execute()方法中实现的
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute(); //核心方法
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
execute():
@Override protected void execute() {
boolean signalledCallback = false;
try {
//1、构建一个拦截器的链返回一个respone对象,每一个拦截器中的链作用不同。
Response response = getResponseWithInterceptorChain();
//2、判断 重定向重试拦截器是否被取消了-在子线程执行!
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
//2.1、取消了 ,则调用call.callback的onFailure方法 回调失败的提示
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
//2.2 、没有取消则调用onResponse()的回调,真正发送网络请求的就是onResponse()
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 {
//3、清除当前异步的client分发器
client.dispatcher().finished(this);
}
}
finished(runningAsyncCalls, call, true); ******* 重要方法
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
//1、通过call.remove把这个请求从正在执行的异步请求队列中删除
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!”);
//2、调整整个异步请求的任务队列,出于线程安全考虑用sy
if (promoteCalls) promoteCalls();
//3、 重新计算线程数量并赋值
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
——————————————————————————————————————
四、同步/异步请求方法差异 及任务调度dispatcher
- 发起请求的方法调用
- 同步是execute()
- 异步是enqueue(),传入callback对象
- 阻塞线程是否
任务调度dispatcher
思考1:okHttp如何实现同步异步请求?
答: 发送的同步/异步 请求 最后都会在dispatcher中 管理其请求队列状态!
思考2:什么是dispatcher
答:dispatcher 的作用为了
- 维护请求的状态(同 or 异)
- 维护一个线程池,用于执行请求;把请求推送到请求队列
在dispatcher类中定义了3个队列
维护了一个线程池
private @Nullable ExecutorService executorService;
维护一个等待就绪的异步执行队列1 ,不满足条件的时候请求进入该队列进行缓存
/** Ready async calls in the order they'll be run. */ 异步的就绪队列
private final Deque<AsyncCall> readyAsyncCalls= new ArrayDeque<>();
维护一个正在执行的异步请求队列2 ,包含了已经取消但没有执行完的请求
/** 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<>();
思考3:异步请求为什么需要两个队列-想象成生产和消费者模型
dispatcher :生产者 默认运行在主线程
ExecutorService :消费者池
-
Deque<AsyncCall> runningAsyncCalls
-
Deque<AsyncCall> readyAsyncCalls
每当有新的异步请求通过call.enqueue的时候,通过2个条件判断是否进入正在执行队列还是等待队列;
条件:runningAsyncCalls的数量是否小于最大请求数量(64)同时 正在运行的主机的请求是否小于设定好的主机请求的最大值(5)
promoteCalls() 手动清除缓存区
===============================dispatch核心
A、同步——请求的executed():
每当有新的请求来了就直接加入 同步请求队列
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
B、异步——请求的executed(): 判断条件
核心:
这个call实际上就是一个runnable对象,不满足条件就放在就绪异步等待队列当中。正在请求的异步队列有空闲位置的时候会从就绪缓存等待中取出优先级高的请求加入队列进行执行
条件满足 将封装好的call添加到正在执行的异步队列中然后交给线程池管理执行
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests
&& runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
executorService():线程池
//使用synchronized关键字锁对象,保证ExecutorService线程池是单例的
public synchronized ExecutorService executorService() {
if (executorService == null) {
//ThreadPoolExecutor (corePoolSize,最大线程池数量,)
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
ThreadPoolExecutor(参数)
corePoolSize:0 的意义
如果空闲一段时间后,就会自动销毁所有线程池中的线程!
Integer.MAX_VALUE:最大线程数
当请求到来时候可以无限扩充请求创建线程,这个无限不大于64
keepAliveTime
当线程数量大于核心线程数的时候,多于的线程最大的存活时间!!
这个方法的意义:
在程序中开启了多个并发的请求,线程池会创建N个线程。当工作完成后,线程池会在60秒后相继关闭所有无用的线程
思考4: readyAsyncCalls队列中的线程在什么时候才会被执行呢?
finally {
client.dispatcher().finished(this);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
...
synchronized (this) {
//1、把异步请求的从正在执行的异步请求队列删除
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!”);
//2、调整任务队列,因为线程是不安全的
if (promoteCalls)promoteCalls();
//3、重新计算正在执行的请求的数量
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
promoteCalls():对等待的异步请求缓存队列进行调度
private void promoteCalls() {
// Already running max capacity.
if (runningAsyncCalls.size() >= maxRequests) return;
// No ready calls to promote.
if (readyAsyncCalls.isEmpty()) return;
//1、对原来的等待缓存的异步请求队列进行遍历
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
//2、把它最后一个元素取出并移除后,加入到runningAsyncCalls 中
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
——————————————————————————————————————
五、拦截器 Interceptor
拦截器是OkHttp中提供一种强大的机制,它可以实现 网络监听、请求 以及响应重写、请求失败重试等功能。(不区分同步/异步)
内部拦截器:
- 定向拦截器
- 桥接适配拦截器
- 缓存拦截器
- 连接管理拦截器
- 网络IO流数据读写拦截器
外部拦截器:
- App拦截器client.interceptors
- 网络拦截器networkInterceptors
getResponseWithInterceptorChain() :返回网络响应
ps:在方法内构成一个拦截器的链,通过依次执行每个不同功能的拦截器来获取服务器的响应返回:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//1、创建一系列拦截器,然后将创建的拦截器放入到interceptors 集合当中
interceptors.addAll(client.interceptors()); //添加app 拦截器
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));
//2、将集合封装到RealIntercptorChain 链当中
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis() );
//依次执行拦截器链的proceed()方法
return chain.proceed(originalRequest);
}
//3、核心功能:创建下一个拦截器链
@Override public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
…
// Call the next interceptor in the chain.
访问的话需要从下一个拦截器进行访问而不是从当前的拦截器
RealInterceptorChainnext = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
将获取的拦截器链传入,构成一个完整的链条
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
ps:简单的说把一个OkHttp的网络请求看作是一个一个拦截器执行Interceptor的方法的过程
小结:
1、在发起请求前对request 进行处理
2、调用下一个拦截器,获取response (持有链条的概念)
3、对response进行处理,返回给上一个拦截器
——————————————————————
A、 RetryFollowUpInterceptor 重定向拦截器
流程:
(1)StreamAllocation
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
StreamAllocation对象,建立Http请求所需要的所有网络组件;分配stream
作用:
获取链接服务器端的connection链接和连接数据传输的输入输出流
ps:真正使用该对象的是ConnectInterceptor
(2) 调用RealInterceptorChain.proceed()进行实际的网络请求
如何进行拦截操作的呢?
(3)根据异常结果或是响应结果判断是否要进行重新请求
核心操作:
1、对重试的次数进行判断 超过20次就不会再请求而是释放
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throws ...
} ——20次
(4)调用下一个拦截器,对response进行处理,返回给上一个拦截器
++++++++++++++++++++++++++++++++++++++++
B、BridgeInterceptor 桥接拦截器
作用:
- 设置内容长度、编码方式、压缩方式等的头部信息添加;
- keep-Alive操作保持连接复用的基础
keep-Alive:当开启一个TCP连接之后,他不会关闭它的连接,而是在一定时间内保持它的连接状态
//调用拦截器链的proceed()方法,获得服务器发回的响应之后把该响应返回给客户端
Response networkResponse = chain.proceed(requestBuilder.build());
//将从服务器返回的网络请求响应Response转化为用户可以读取并使用的response
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
//核心转化功能:转化Response
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
...
}
a、transparentGzip 为true 表示请求的客户端支持Gzip压缩
b、返回的Gzip是否符合头部信息的编码
c、判断头部信息是否含有body体
//Response的body体的输入流转化为GzipSource类型,为了使用者可以直接以解压的方式读取流数据
GzipSourceresponseBody = new GzipSource(networkResponse.body().source());
核心功能:
1、负责将用户构建的一个Request请求转化为能够进行网络访问的请求
2、将这个符合网络请求的Request进行网络请求
3、将网络请求回来的响应Response 转化为用户可以读取的Response
++++++++++++++++++++++++++++++++++++++++
C、CacheInterceptor 缓存拦截器
OkHttpClient client = new OkHttpClient.Builder()
.cache(new Cache(new File("cache"),24*1024*1024))
.readTimeout(5, TimeUnit.SECONDS).build();
param1 : 缓存目录
param2: 缓存目录大小
internalCache所有的实现都是通过上面的cache类实现的
final InternalCache internalCache= new InternalCache() {
//从缓存中读取响应体Response
@Override public Response get(Request request) throws IOException {
return Cache.this.get(request);
}
@Override public CacheRequest put(Response response) throws IOException {
return Cache.this.put(response);
}
…
}
CacheRequest put(Response response) {
//1、通过Response响应信息获取对应Response对应的Request请求,再调用method方法获取requestMethod
String requestMethod = response.request().method();
// 2、如果不是get方法的话,就不进行缓存
if (!requestMethod.equals("GET")) {…}
//3、写入缓存的部分 Entry:OkHttp所需要的所有属性被封装成Entry类
Entry entry = new Entry(response);
//4、最后缓存都会交给DisLruCahce算法来实现实际写入
DiskLruCache.Editor editor = null;
ps:OkHttp内部由于维护着一个清理的线程池,由这个清理的线程池来实现对缓存文件的自动的清理和管理工作
//5、创建真正的editor ,通过传入key值获取完成的editor
editor = cache.edit(key(response.request().url()));
key:将网络请求的url做MD5 加密处理 得到16进制表示形式 并转化为key
//6、真正的开始进行网络缓存操作,将缓存写入磁盘
entry.writeTo(editor);
public void writeTo(DiskLruCache.Editor editor) throws IOException {
BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));
//缓存一些请求的头部信息
sink.writeUtf8(url)
.writeByte('\n');
sink.writeUtf8(requestMethod)
.writeByte('\n');
sink.writeDecimalLong(varyHeaders.size())
.writeByte('\n');
for (int i = 0, size = varyHeaders.size(); i < size; i++) {
sink.writeUtf8(varyHeaders.name(i))
.writeUtf8(": ")
.writeUtf8(varyHeaders.value(i))
.writeByte('\n');
}
…
//缓存http的响应行 等信息
sink.writeUtf8(new StatusLine(protocol, code, message).toString())
.writeByte('\n');
sink.writeDecimalLong(responseHeaders.size() + 2)
.writeByte('\n');
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
sink.writeUtf8(responseHeaders.name(i))
.writeUtf8(": ")
.writeUtf8(responseHeaders.value(i))
.writeByte('\n');
}
…
//判断是否是Https请求
if (isHttps()) {
sink.writeByte('\n');
...
}
sink.close();
}
//7、实际的响应主体 CahceInterceptor的实现类,通过该实现类实现数据的缓存和更新
return new CacheRequestImpl(editor);
通过源码可以看到它是通过DisLruCache书写body的
CacheRequestImpl(final DiskLruCache.Editor editor) {}
——————
//从缓存中读取响应体Response
@Override public Response get(Request request) throws IOException {
return Cache.this.get(request);
}
DiskLruCache.Snapshot snapshot;
//1、目标缓存快照:记录缓存在某一个特定时间包含的内容
//MD5加密计算得到的Key值
snapshot = cache.get(key);
if (snapshot == null) {
return null;
}
//2、通过key值将缓存的值保存到snapshot缓存快照中
//3、根据缓存快照创建entry缓存对象用于缓存数据
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
//4、根据entry 获取响应的response对象
Response response = entry.response(snapshot);
//5、进行响应的匹配工作,如果不是一对一的话就返回null并关闭当前流
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
public Response response(DiskLruCache.Snapshot snapshot) {
…
//1、根据头部信息创建cacheRequest请求
Request cacheRequest = new Request
.Builder()
.url(url)
.method(requestMethod, null)
.headers(varyHeaders)
.build();
//2、将响应的1步信息保存到entry当中
CacheResponseBody:响应体读取
return new Response.Builder()
.request(cacheRequest)
.protocol(protocol)
.code(code)
.message(message)
.headers(responseHeaders)
.body(new CacheResponseBody(snapshot, contentType, contentLength))
.handshake(handshake)
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(receivedResponseMillis)
.build();
============================================
CacheInterceptor 缓存拦截器
@Override public Response intercept(Chain chain) throws IOException {
//1、通过get方法尝试获取缓存
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
…
//2、通过缓存策略的工厂类获取具体的缓存策略
CacheStrategystrategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
//3、根据缓存策略获取其内部维护的response和request
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
//4、当有缓存的时候,更新一下缓存的命中率
if (cache != null) {
cache.trackResponse(strategy);
}
…
//5、如当前不能使用网络又没有找到响应的缓存对象,通过构建者模式构建一个response 这会抛出一个504错误
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
//6、若有缓存且不能使用网络时候,直接返回缓存的结果
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//7、通过调用拦截器链的preceed方法来进行网络响应的获取,里面的工作交给下一个拦截器(Connectinterpreter)
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
//8、判断缓存响应是否为null 从缓存中读取数据
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
…
}
//9、网络缓存保存判断
if (cache != null) {
//判断http头部有没有响应体,并且缓存策略选择为可以被缓存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// 将网络响应写入put到缓存当中
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
//10、这个请求request是否是无效的缓存方法,如果是删除该request
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
CacheStrategy 缓存策略
//维护了一个网络请求networkRequest 和一个缓存响应 cacheResponse,内部通过制定以上二者来描述是通过何种方式获取Response
public final class CacheStrategy{
/** The request to send on the network, or null if this call doesn't use the network. */
public final @Nullable Request networkRequest;
/** The cached response to return or validate; or null if this call doesn't use a cache. */
public final @Nullable Response cacheResponse;
}
实际创建CacheStrategy对象的核心类:
private CacheStrategy getCandidate() {
// 判断是否有缓存响应对象,若无创建新的对象传入请求request
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// 判断请求是否是https,是否经历过握手操作
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
//判断缓存是否该存储
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//请求当中制定不使用缓存响应或是进行了可选择的httpget请求,就重新建立请求
acheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
…
//比较缓存的response,添加缓存头操作
Response.Builder builder = cacheResponse.newBuilder();
…
//返回一个CacheStrategy对象
return new CacheStrategy (null, builder.build());
…
}
++++++++++++++++++++++++++++++++++++++++
C、ConnectInterceptor 连接拦截器
作用:打开与服务器的连接,正式开网络启请求
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
(1) StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnectionconnection = streamAllocation.connection();
(2) return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
StreamAllocation:用来建立执行Http请求所需要的所有的网络的组件(初始化在重定向拦截器中)
HttpCodec:通过创建好的StreamAllocation创建HttpCodec的对象
ps:HttpCodec 是用来编码request 和解码 response
RealConnection:实际用来进行网络IO传输的对象
(1) ConnectInterceptor 从拦截器链中获取到前面的Interceptor传过来给StreamAllocaiton这个对象,然后执行streamAllocation.newStream方法(),初始化HttpCodec对象。该对象用于处理Request和Response对象
(2) 将刚才创建的用于网络IO的RealConnection对象,以及对于与服务器交互最为关键的HttpCodec等对象传递给后面的拦截器
streamAllocation.newStream():
public HttpCodec newStream(OkHttpClient client,
Interceptor.Chain chain, boolean doExtensiveHealthChecks){
...
try {
//1、生成一个realConnection对象进行实际的网络连接
RealConnectionresultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
//2、通过获取到的realConnection对象生成一个HttpCodec对象
HttpCodecresultCodec = resultConnection.newCodec(client, chain, this);
//同步代码块 返回一个HttpCodec对象
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
}
…
}
findHealthyConnection():
开启一个while循环 调用findConnection进行二次封装继续获取RealConnection
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
//如果realConnection的对象成功数量==0就返回,意味着整个网络连接结束
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
//如果这个candidate是不健康的就销毁资源操作
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue; //循环findConnection()获取对象
}
findConnection():
//1、尝试获取这个链接,如果可以复用connection 直接赋值给releasedConnection,然后判断这个可复用的connection是否为空,
不为空就把这个链接connection赋值给RealConnection对象
releasedConnection = this.connection;
toClose = releaseIfNoNewStreams();
if (this.connection != null) {
// We had an already-allocated connection and it's good.
result = this.connection;
releasedConnection = null;
}
2、如果RealConnection对象为空,就从连接池中获取一个实际的RealConnection
if (result == null) {
// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
…
//3、调用connect方法进行实际的网络连接
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
ps:赋值操作!判断这个连接是否已经建立了,如果已经建立就抛出异常,这个连接与否的标志是protocol标志位协议,表示在整个连接建立和可能的协议过程中所要用到的协议
if (protocol != null) throw new IllegalStateException("already connected”);
if (route.requiresTunnel()) {
判断是否建立Tunnerl连接
routeDatabase().connected(result.route());
...
// Pool the connection. 4、将这个实际的RealConnection连接放到连接池中
Internal.instance.put(connectionPool, result);
小结 ConnectInterceptor
1、建立RealConnection对象
2、选择不同的连接方式
3、CallServerInterceptor
连接池
定义
OhHttp将客户端和服务器端的连接抽象成Connection类,RealConnection是它的实现类,为了管理这些连接的复用,所以有了ConnectionPool类。
- 当他们共享相同的地址就可以复用连接!
- 在一定时间内,维护哪些连接是打开状态,已被以后复用的策略
ps:http1.0 keep-alive机制 http2.0 多路复用机制
本章重点:
- 对连接池的Get、Put连接操作
- 对连接池的自动回收操作
连接池的 Get()
@Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
assert (Thread.holdsLock(this));
//1、for循环遍历连接池当中的Connections,获取可用的connection
for (RealConnection connection : connections) {
if (connection.isEligible(address, route)) {
streamAllocation.acquire(connection, true);
return connection;
}
}
return null;
}
streamAllocation.acquire():
public void acquire(RealConnection connection, boolean reportedAcquired) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();
//2、把从连接池当中获取到的RealConnection对象赋值给StreamAllocation的成员变量
this.connection = connection;
this.reportedAcquired = reportedAcquired;
//3、将StreamAllocationReference的弱引用添加到RealConnection的allocation集合当中
connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
}
ps:根据allocations这个ArrayList集合来判断当前连接对象所持有的StreamAllocation的数目,
通过这个集合大小判断一个网络连接的负载量是否已经超过了他的最大值
连接池的 put()
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
//在添加以前 进行一个异步的清理任务
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);//如何回收connection的类
}
connections.add(connection);
}
小结
1、不论哪个拦截器中,请求都会产生一个SteramAllocation对象
2、StreamAllocation对象的弱引用添加到RealConnection对象的allocations集合当中
3、从连接池中获取链接
对连接池的自动回收操作
cleanupRunnable
private final Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
while (true) {
//1、首次清理的时候一定返回下一次清理的间隔时间;cleanup具体GC回收算法
long waitNanos= cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (ConnectionPool.this) {
try {
//2、等待释放锁和时间片,等待时间结束后再次执行runnbale
ConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
...
};
cleanup():类似Java里的标记清除算法
首先标记出最不活跃的connection链接,或是叫泄漏链接,空闲链接
long cleanup(long now) {
…
//在同步代码块中标记泄漏的链接
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
…
//如果被标记的链接,比如空闲的Socket链接超过5个,就会从池子中删除然后关闭链接
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
// We've found a connection to evict. Remove it from the list, then close it below (outside
// of the synchronized block).
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
// A connection will be ready to evict soon.
//当全部都是活跃链接的时候会返回
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
//如果目前还能塞进去链接 有可能有泄漏的链接也会返回keepAliveDurationNs下次使用已被
return keepAliveDurationNs;
} else { //如果没有任何可用链接跳出死循环
// No connections, idle or in use.
cleanupRunning = false;
return -1;
}
思考:如果找到最不活跃的链接呢?
// If the connection is in use, keep searching.
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
pruneAndGetAllocationCount():根据弱引用是否为null来判断
private int pruneAndGetAllocationCount(RealConnection connection, long now) {
List<Reference<StreamAllocation>> references = connection.allocations;
//1、遍历维护弱引用的list
for (int i = 0; i < references.size(); ) {
Reference<StreamAllocation> reference = references.get(i);
//2、查看streamAllocation对象是否为空,为空表示没有代码引用该对象则进行删除
if (reference.get() != null) {
i++;
continue;
}
…
references.remove(i);
connection.noNewStreams = true;
//3、当references对列为空时候,表示整个维护的队列已经被删空了所以返回0,表示这个链接已经没有引用了
if (references.isEmpty()) {
connection.idleAtNanos = now - keepAliveDurationNs;
return 0;
}
}
//4、否则返回非0的具体值
return references.size();
}
小结
1、OkHttp使用了GC回收算法
2、StreamAllocation的数量会渐渐减少为0
3、被线程池检测到并回收,这样就可以保持多个健康的Keep-alive连接
++++++++++++++++++++++++++++++++++++++++
D、CallServerInterceptor 连接拦截器
作用:
- 向服务器发起真正请求,
- 并接收服务器返回给的读取响应并返回
//1、创建OkHttp拦截器的链,所有的请求正是通过链(proceed() )依次完成其任务
RealInterceptorChain realChain = (RealInterceptorChain) chain;
//创建HttpCodec 对象,所以的流对象封装成HttpCodec对象,用来编码解码的请求和响应
HttpCodec httpCodec = realChain.httpStream();
//创建Http请求所需要的其他网络设施组件,用来分配Stream流
StreamAllocation streamAllocation = realChain.streamAllocation();
//抽象客户端与服务器端的连接的具体实现
RealConnection connection = (RealConnection) realChain.connection();
//网络请求
Request request = realChain.request();
...
//2、利用httpCodec对象向Sokcet当中写入请求的Header头信息
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
…
//3、特殊情况:询问服务器是否可以发送带有请求体的信息,如果能接收这个规则的信息就返回给100的标示的响应吗,
会跳过写入body操作直接获取响应信息
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}
…
//4、调用body的writeTo()方法向Socket写入body信息
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
…
//5、运行到这儿表明已经完成的整个网络请求的写入操作
httpCodec.finishRequest();
...
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
//6、读取网络响应信息中的头部信息
responseBuilder = httpCodec.readResponseHeaders(false);
}
…
//7、读取网络响应的body信息
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
}
else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams(); //8、当已经建立好连接的时候,禁止新的流创建
}
//9、当响应吗是204、 205的时候抛出异常
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
//10、返回Response响应信息
return response;
}
小结
1、Call对象对请求的封装
2、dispatcher分发器对Request请求的分发
3、getResponseWithInterceptors()方法进行拦截器链的构造
- RetryAndFollowUpInterceptor
- CacheInterceptor
- BridgeInterceptor
- ConnectionInterceptor
- CallServerInterceptor