今天想聊聊网络请求框架,谈到网络框架,不得不聊聊okhttp。okhttp开发者是square公司,作为一个Android开发者,对此框架肯定不会陌生,该网络请求框架的优点有很多:1、支持HTTP2.x,允许所有同一个主机地址的请求共享一个socket连接;2、使用连接池减少请求延时;3、使用GZIP压缩减少响应数据的大小;4、缓存机制,避免一些重复的请求;5、自动重连和重定向。不管我们是直接使用okhttp还是使用基于okhhtp开发的Retrofit,都很有必要搞清楚okhttp的原理。
那我们从一个简单的使用开始入手吧。
首先是添加库依赖,以及网络权限:(okhttp 4.x以上的版本就开始使用kotlin编写了,我这边先使用Java版本的)
implementation 'com.squareup.okhttp3:okhttp:3.14.0'
<uses-permission android:name="android.permission.INTERNET"/>
okhttp的请求分为同步请求和异步请求,先看下同步的是如何操作的:
OkHttpClient client = new OkHttpClient();
//client = new OkHttpClient.Builder().build();//采用创建者模式,下面会细聊
Request request = new Request.Builder()
.url(url)
//.get() //默认使用get,可不写
.post(requestBody)//如果是使用post方法,需要传一个requestBody
.build();
final Call call = client.newCall(request);
//耗时操作不能在主线程中执行,会引起ANR
new Thread(new Runnable() {
@Override
public void run() {
Response response = null;
try {
response = call.execute();//同步请求,获取网络请求结果
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
提交之后,框架里面替我们做了哪些操作呢,我们去一探究竟。首先我们看一下OkHttpClient的两种实例方法:先看第一种直接调用构造函数:
public OkHttpClient() {
this(new Builder());
}
public Builder() {
dispatcher = new Dispatcher(); //这个Dispatcher下面会谈到
protocols = DEFAULT_PROTOCOLS; //Util.immutableList(Protocol.HTTP_2, Protocol.HTTP_1_1);
connectionSpecs = DEFAULT_CONNECTION_SPECS;//设置支持的连接,默认是使用SNI和ALPN等扩展的现代TLS连接和未加密、未认证的http连接
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;//默认没有cookie
socketFactory = SocketFactory.getDefault(); //使用的是Socket连接
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool(); //实例化连接池 使用连接池技术减少请求的延迟(如果SPDY是可用的话)
dns = Dns.SYSTEM;//域名解析系统
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000; //下面都是初始化一些值
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;//为了保持长连接,间隔一段时间发送一个ping指令进行保活
}
然后看第二种通过创建者模式创建的:
public OkHttpClient build() {
return new OkHttpClient(this);
}
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
this.interceptors = Util.immutableList(builder.interceptors);
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
this.eventListenerFactory = builder.eventListenerFactory;
this.proxySelector = builder.proxySelector;
this.cookieJar = builder.cookieJar;
this.cache = builder.cache;
this.internalCache = builder.internalCache;
this.socketFactory = builder.socketFactory;
……
this.connectionPool = builder.connectionPool;
}
参考下网上对创建者模式的说明:创建者模式又叫建造者模式,是将一个复杂的对象的构建与它的表示分离,使
得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建具有复合属性的对象。简单形象地认识下就是:肯德基有很多炸鸡、汉堡、薯条、可乐,你要买的套餐就是用那些部件按照不同的组合拼一份给你。
上面的代码中我们可以看到一个很熟悉的身影Dispatcher,跟下这个类的源码:
private int maxRequests = 64;//最大的请求数是64
private int maxRequestsPerHost = 5;//
private @Nullable Runnable idleCallback;
/** Executes calls. Created lazily. */
/**执行call,就是Realcall,下面会提到,就是一个个网络请求*/
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<>();
Dispatcher:分发器(调度员),内部维护了一个线程池,用于执行网络请求的线程;同时还维护了三个请求队列:等待执行的异步请求队列;正在进行异步请求的队列(包含了已取消但未完成的请求);正在进行同步请求的队列(包含了已取消但未完成的请求),对OKHttp中的所有网络请求任务进行调度,我们发送的同步或异步请求都会由它进行管理。
还有一个是ConnectionPool,
/**
创建一个适用于单个应用程序的新连接池。
该连接池的参数将在未来的okhttp中发生改变
目前最多可容乃5个空闲的连接,存活期是5分钟
*/
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
//这个类中的一些变量
private static final Executor executor
= new ThreadPoolExecutor(
0 /* 核心线程数,为啥是0呢,下面会补充说明 */,
Integer.MAX_VALUE /* 线程池最大容量 */,
60L /*线程存活时常*/,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
Util.threadFactory("OkHttp ConnectionPool", true)
);
/** The maximum number of idle connections for each address. */
private final int maxIdleConnections;
private final long keepAliveDurationNs;
private final Runnable cleanupRunnable = new Runnable() {//维护着一个清理任务
@Override public void run() {
while (true) {
...
}
};
ConnectionPool:管理HTTP和HTTP / 2连接的重用,以减少网络延迟。 相同Address的HTTP请求可以共享Connection。 此类实现了哪些连接保持打开以供将来使用的策略,我们常说OKHttp有多路复用机制和减少网络延迟功能就是由这个类去实现;我们将客户端与服务端之间的连接抽象为一个connection(接口),其实现类是RealConnection,为了管理所有connection,就产生了ConnectionPool这个类;当一些connection共享相同的地址,这时候就可以复用connection;同时还实现了某些connection保持连接状态,以备后续复用。(每个OkHttpClient 里面只有一个dispatcher和一个connectionpool,都是在创建OkHttpClient就初始化好了)好像一下子扯的有点多了,那我们继续看前面发起的请求,刚刚创建好了OkHttpClient实例,然后要创建Request请求,Request同样也是采用创建者模式:
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
public Request build() {
if (url == null) throw new IllegalStateException("url == null");
return new Request(this);
}
Request(Builder builder) {
this.url = builder.url;//请求地址
this.method = builder.method; //采用的请求方式
this.headers = builder.headers.build();//请求头
this.body = builder.body;//请求体(一般封装一些请求参数)
this.tag = builder.tag != null ? builder.tag : this;
}
然后是Call对象的创建:
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
/*返回的是一个RealCall的对象
client:OkHttpClient 实例
originalRequest :最初的Request
forWebSocket :是否支持websocket通信
retryAndFollowUpInterceptor 重试和重定向拦截器
*/
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;
}
准备工作都做好了,我们开始看同步请求的执行:response = call.execute();
Call只是一个接口,这边execute()其实是在call的实现类(RealCall)中执行的:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace(); //该方法中的操作是重试监听器做一些栈StackTrace记录
eventListener.callStart(this);//事件监听做回调处理
try {
client.dispatcher().executed(this); //通知Dispatcher这个Call正在被执行,同时将此Call交给Dispatcher
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); //mark一下,下面要讲这个地方
}
}
稍微了解下dispatcher的executed方法:
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); // runningSyncCalls 是个双端队列
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);//将请求放入正在运行的异步请求队列
}
然后核心的方法就是getResponseWithInterceptorChain(),返回的是我们要的Response,这个方法中都做了些什么呢?
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>(); //实例化一个List,用来存放Interceptor对象
interceptors.addAll(client.interceptors()); //添加用户自定义的拦截器
//下面 依次添加OKHttp内部的五个拦截器
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));
//创建了一个RealInterceptorChain对象,它构建了一个拦截器链
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest); //通过proceed方法将这些拦截器一一执行
}
在RealCall中经过一系列的设置拦截器,然后一次执行拦截器最后获取到我们要请求的数据,具体怎么请求到的,下面会详细说面,那这边的call执行完之后是怎么处理呢,往回翻翻刚才RealCall的execute()方法最后一行:client.dispatcher().finished(this);还是交给dispatcher来处理,那它是怎么处理的呢?
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
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;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
public synchronized int runningCallsCount() {
return runningAsyncCalls.size() + runningSyncCalls.size();//返回的值是正在进行的同步请求数和正在进行的异步请求数
}
过程不是很复杂,就是从正在执行中的同步队列中移除该请求,然后判断runningCallsCount 是否为0,这说明整个dispatcher分发器内部没有维护正在进行的请求了,同时idleCallback不为null,那就执行它的run方法(每当dispatcher内部没有正在执行的请求时进行的回调),到这里,同步请求的整个过程就完成了。
那还有一种方法是异步执行,异步执行又是怎样的呢?
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//请求失败的回调
}
@Override
public void onResponse(Call call, Response response) throws IOException {
//请求成功的回调
}
});
那我们就从enqueue这个方法开始入手
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();//和同步里面的一样,该操作是重试监听器做一些栈StackTrace记录
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));//然后dispatcher将请求存入队列
}
Dispatcher类中:
synchronized void enqueue(AsyncCall call) {
//判断正在执行的异步请求是否没达到64。以及,相同主机的请求是否小于5个。
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);//直接加入到正在执行的异步任务队列中
executorService().execute(call);//线程池中执行,执行AsyncCall的run方法,也就是执行AsyncCall的execute方法
} else {
readyAsyncCalls.add(call);//否则加入到异步任务准备队列中
}
}
private int runningCallsForHost(AsyncCall call) {
int result = 0;
//遍历正在执行的异步任务队列
for (AsyncCall c : runningAsyncCalls) {
if (c.host().equals(call.host())) result++;
}
return result;
}
Dispatcher对请求重新打包成一个异步请求,然后存入到一个正在运行的异步请求队列中,那这个异步请求是怎么执行的呢,看一下这个异步请求的源码,实现的是一个NamedRunnable接口,
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();
}
既然接口已经定义好实现方法,那我们看下实现类AsyncCall里面是怎么实现的吧
@Override protected void execute() {
boolean signalledCallback = false;
try {
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 {
client.dispatcher().finished(this);
}
}
执行的过程大致和RealCall是一致的,那这里就不再解说了。
所以总结一下请求的大致流程:client发起请求,dispatcher根据请求方式是同步或者异步,然后结合当前的资源使用情况将请求添加到相应的队列中,然后请求被一个个放到线程池中去执行,执行的过程就是拦截器链挨个执行设置进去的拦截器,最后获取到结果返回给用户,执行完毕后dispatcher将该请求移出队列,至此请求结束。
那么关键的部分,网络请求,是如何实现的呢,那么这里就要继续聊聊前面搁置的话题:拦截器,拦截器的内容实在太多了,且听我慢慢说来:
回想一下我们前面哪里聊到了拦截器?对,就是在刚刚的执行请求的方法中execute(),有提到一个核心方法getResponseWithInterceptorChain(),这个方法里面添加了很多拦截器,而添加完拦截器后,拦截器链会挨个执行这些拦截器;
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));
那我们也从上往下一次了解下各个拦截器,首先拿RetryAndFollowUpInterceptor开dao:
RetryAndFollowUpInterceptor
从字面翻译来看,是重试和继续访问拦截器。功能就是重试:根据后台返回的状态码或者执行过程中抛出的异常然后结合一些设置好的策略来尝试重新连接,然后还有就是继续请求。进入RetryAndFollowUpInterceptor拦截器,最先映入眼帘的是:
/**
* How many redirects and auth challenges should we attempt? Chrome follows 21 redirects; Firefox, curl, and wget follow 20; Safari follows 16; and HTTP/1.0 recommends 5.
重定向的次数:Chrome遵循21个重定向;Firefox,curl和wget是20;Safari是16;HTTP/1.0建议是5
*/
private static final int MAX_FOLLOW_UPS = 20;
然后就是一个很关键的类:StreamAllocation。谈这个类之前,我们先复习一下之前聊过的http 2新增的特性:多路复用。多个stream(也就是多个request)可以共用一个socket连接,每个tcp连接都是通过一个socket来完成的,socket对应一个host和port,如果有多个stream都是连接在一个host和port上,那么它们就可以共同使用同一个socket,这样做的好处就是可以减少TCP的一个三次握手的时间。HTTP通信执行网络"请求"需要在"连接"上建立一个新的"流",我们将StreamAllocation称之流的桥梁,它负责为一次"请求"寻找"连接"并建立"流",从而完成远程通信。所以说StreamAllocation与"请求"、"连接"、"流"都有关。(源码太多就不贴出来了)看过源码,可以将StreamAllocation的工作内容大致总结如下:先找(connectionPool)是否有已经存在的连接,如果有直接返回;根据设置好的策略(如更换路由,更换线路等),再重新查找,有的话返回;如果都找不到的话,就new一个RealConnection出来,然后通过acquire(RealConnection result = new RealConnection(connectionPool, selectedRoute); acquire(result, false);)关联到connection.allocations上,然后判断看是否有重复的socket并将重复的socket关闭。
然后继续分析RetryAndFollowUpInterceptor拦截器,既然是拦截器,那就要看拦截方法了intercept(Chain chain),看方法也知道,就是通过拦截器链来执行的,
intercepte()中部分源码如下:
@Override public Response intercept(Chain chain) throws IOException {
try {
response = realChain.proceed(request, streamAllocation, null, null); //正常按照链的顺序去执行
releaseConnection = false;// 如果没有发送异常,修改标志 不需要重试
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
// 出现路由连接异常,通过recover方法判断能否恢复连接,如果不能将抛出异常不再重试
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getLastConnectException();
}
// 能恢复连接,修改标志 不释放连接
releaseConnection = false;
//回到下一次循环 继续重试 除了finally代码外,下面的代码都不会执行
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
// 判断该异常是否是连接关闭异常
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
// 如果releaseConnection为true,说明后续拦截器抛出了其它异常,那就释放所有资源,结束请求
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
……
//至此,说明网络请求已经完成了,后面就要继续判断后台返回的响应码,然后继续做出应对
// 对response进行响应码的判断,如果需要进行重定向,那就获取新的Request
Request followUp = followUpRequest(response);//这里面都是一些响应码的判断
……
//关闭,忽略任何已检查的异常
closeQuietly(response.body());
//检测重连次数是否超过20次,如果超过就抛出异常,避免消耗客户端太多资源
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
……
}
源码中也可以看到两个比较重要的异常:RouteException、IOException,当抛出RouteException或者发生IOException时,拦截器会根据用户的设置和异常分析,决定当前请求是否可以重连。
BridgeInterceptor
那么如果是正常的拦截器链执行,下一个拦截器就是 BridgeInterceptor,Bridge即桥梁,连接的意思,顾名思义就是连接应用程序和服务器的桥梁,它会将我们发出去的请求处理成为一个服务器能识别的网络请求;主要负责对 Request 中的 Head 设置默认值,比如 Content-Type、Keep-Alive、Cookie 等,然后在获取响应后为响应添加一些响应头信息。直接上intercepte方法()
@Override public Response intercept(Chain chain) throws IOException {
……
//http请求的TCP需要三次握手,虽然保证了安全,但是频繁的请求效率就会低下,Keep-Alive机制可以在数据传输完成之后仍然保持TCP连接,以备后用
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
……
//上面是对请求进行处理
Response networkResponse = chain.proceed(requestBuilder.build());
//下面是对服务器返回值进行处理
……
if (transparentGzip //客户端是否设置了gzip压缩
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) //服务器是否支持gzip压缩
&& HttpHeaders.hasBody(networkResponse)) { //是否有响应体
GzipSource responseBody = new GzipSource(networkResponse.body().source());// 将响应体的输入流转换成GzipSource类型
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
return responseBuilder.build(); //将最终的响应返回给上一个拦截器
}
CacheInterceptor
过了桥,继续走,就来到了CacheInterceptor,缓存拦截器主要就是处理缓存,重复利用资源,有缓存的时候使用缓存,没有缓存的时候使用网络并将响应缓存起来。直接闯进intercepte方法看下CacheIntercpter主要做了以下内容:根据 Request 获取当前已有缓存的 Response(有可能为 null),并根据获取到的缓存 Response,创建 CacheStrategy 对象;通过 CacheStrategy 判断当前缓存中的 Response 是否有效(比如是否过期),如果缓存 Response 可用则直接返回,否则调用 chain.proceed() 继续执行下一个拦截器,也就是发送网络请求从服务器获取远端 Response;如果从服务器端成功获取 Response,再判断是否将此 Response 进行缓存操作。我们大概看一下源码:
@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null; //通过request从缓存中获取响应
long now = System.currentTimeMillis();
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); //CacheStrategy 是一个缓存策略类 比如强制缓存、对比缓存等,用来判断是使用缓存还是网络请求
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
……
// 能从缓存中获取响应但是缓存策略是不使用缓存,那就关闭获取的缓存
if (cacheCandidate != null && cacheResponse == null) {
……
}
// 如果缓存策略是不使用网络也不使用缓存,那就构建一个504错误码的响应并返回
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
……
}
// 如果缓存策略是不使用网络但是可以使用缓存,那就通过缓存策略的缓存构建响应并返回
if (networkRequest == null) {
……
}
// 准备进行网络请求
Response networkResponse = null;
try {
// 到这里就说明可以使用网络,那就交给下一个拦截器去处理获取响应
networkResponse = chain.proceed(networkRequest);
} finally {
// 如果发生了IO异常或者其它异常,关闭缓存避免内存泄漏
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// 如果缓存策略是可以使用缓存
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) { //网络响应码是304
Response response = cacheResponse.newBuilder()
……
}
// 走到这里说明缓存策略是不可以使用缓存或本地缓存不可用
// 那就通过网络响应构建响应对象
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
// 客户端允许使用缓存
if (cache != null) {
// 如果响应有响应体且响应可以缓存 那就将响应写入到缓存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// 缓存响应的部分信息
CacheRequest cacheRequest = cache.put(response);
// 缓存响应体并返回响应
return cacheWritingResponse(cacheRequest, response);
}
// 通过请求方法判断需不需要进行缓存
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
// 不合法就删除缓存
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
}
其实,OkHttp只是规范了一套缓存策略,我们可以根据自己的需求来自定义使用何种方式缓存数据及使用缓存数据,自定义的方式如下:
OkHttpClient client = new OkHttpClient.Builder()
.cache(new Cache(getCacheDir(), 20 * 1024 * 1024))//可以自己设置缓存路径和空间大小
.build();
然后查看Cache源码会发现
public Cache(File directory, long maxSize) {
this(directory, maxSize, FileSystem.SYSTEM);
}
Cache(File directory, long maxSize, FileSystem fileSystem) {
this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);//Cache 内部使用了 DiskLruCach 来实现具体的缓存功能
}
准备工作都做的差不多了,是不是要发起网络请求了?嗯,快了,再稍微整理一下:
ConnectInterceptor
这个类的代码比较简洁,总体就是创建连接然后准备好参数,然后传给下一个拦截器(也就是CallServerInterceptor)进行真正的网络请求。
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
//获取StreamAllocation对象
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对象,用于编码request和解码response,并且对不同http协议(http1.1和http/2)的请求和响应做处理
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
//通过StreamAllocation获取连接对象RealConnection,RealConnection对象负责实际进行与服务器交互
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
大致就是以上一些操作,StreamAllocation我们在前面也有稍微了解过,我们平时说的三次握手就是在这里完成的。
最后,重头戏来了:
CallServerInterceptor
CallServerInterceptor 是 OkHttp 中最后一个拦截器,也是 OkHttp 中最核心的网路请求部分,
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
//是一个接口用于编码请求解码响应,实现类有两个,根据具体是协议,使用Http1Codec、Http2Codec
HttpCodec httpCodec = realChain.httpStream();
……
long sentRequestMillis = System.currentTimeMillis();//获取发起请求的时间戳
realChain.eventListener().requestHeadersStart(realChain.call());
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
……
}
if (responseBuilder == null) {
……
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut); //Okio,提高效率
……
} else if (!connection.isMultiplexed()) {
streamAllocation.noNewStreams();
}
}
httpCodec.finishRequest();
//===================上面是发送网络请求================================
//===================下面是获取网络数据================================
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
…… //获取响应头部信息,然后构建响应Response 对象
if (forWebSocket && code == 101) { //使用websocket且响应码是101,那么构建一个空响应体的response 对象
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();
}
/**如果响应码是204或者205,且响应体长度大于0,也就意味着该次请求成功,但是服务器只返回了响应行和响应头信息,没有响应体,可是这里拿到的响应体长度居然大于0,那就说明出问题了,所以需要抛出一个协议异常
*/
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
到此,一个完整的网络请求终于完成,用户也从网络上拿到了想要的数据。
那我们最后在总结一下整个流程:客户端发起网络请求(同步或者异步)Dispatcher根据请求方式和目前内部资源的使用情况(判断连接池中的资源和请求队列中的使用情况)将请求放入合适的队列,然后RealCall执行请求,用一个拦截器链将系统内置的拦截器加入并一一执行,前后经过重试和继续请求拦截器RetryAndFollowUpInterceptor设置一些重定向的策略,然后发送给桥拦截器BridgeInterceptor,搭建客户端和服务器中的桥梁,处理客户端的请求使其可被服务器识别,处理服务器响应使其可被客户端解析,处理完之后发送给缓存拦截器CacheInterceptor,增加复用功能,减少不必要的网络请求节省资源,然后经过连接拦截器ConnectInterceptor,搭建与服务器直接交互的通道,准备好参数准备正式的网络请求,最后经请求服务拦截器CallServerInterceptor的一顿操作,在千里之外取到了远程服务器的数据。
从分析源码的过程中,我们发现几个比较明显的模式:
创建者模式,比如我们前面说到的OkHttpClient, Request等多个类都是采用创建者模式;
门户模式:所有的下发工作都是通过一个门户 Dispatcher 来进行分发;
责任链模:网络请求过程中经由多个拦截器,一一执行各自所负责的功能。
OkHttp的源码实在太多了,我的了解只是其中的一部分,还有很多详细的内容等着我们去阅读,阅读源码使我快乐(ku),明天我们继续了解新的网络框架Retrofit,那今天的OkHttp解析就到这里咯~