简介
在Android端,网络请求用的最多的是 OkHttp和Retrofit,而后者实际上是基于OkHttp上封装开发的。基于前几篇文章的基础,今天就来认真分析一下这个框架。
一个简单的OkHttp请求
从客户端的角度看,一个完整的Http请求,可以简化为以下三步,
1、装配Request(请求方法,url,请求头等)
2、客户端发出request请求
3、接收服务端响应,解析数据
按照上面这几个步骤,在OkHttp框架中发起网络请求的代码如下:
//构造Request
Request req=new Request.Builder()
.url(url)
.build();
//构造一个HttpClient
OkHttpClient client=new OkHttpClient();
//发送请求
client.newCall(req)
.enqueue(new Callback() {
//获得服务器返回的Response数据
@Override
public void onResponse(Call arg0, Response arg1) throws IOException {
}
@Override
public void onFailure(Call arg0, IOException arg1) {
// TODO Auto-generated method stub
}
});
你看,就这简单的几个步骤,和我们正常的逻辑思维是一致的。这样使用起来才足够简单。
2、OkHttp框架设计
首先给出OKHttp框架的整个处理流程图:
image2.1、构建Request-构建者模式
public final class Request {
...省略代码...
public static class Builder {
private HttpUrl url;
private String method;
private Headers.Builder headers;
private RequestBody body;
private Object tag;
}
}
相信构建者模式大家都比较熟悉了,所以这里只给出构造参数。
构建Request,按常理想,应该是一个具备请求能力的对象。但实际上OkHttp为框架使用者简单使用着想,将使用者需要关心的内容(比如url,method,header等)单独封装在了Request中,而具体的网络请求内容剥离到了框架内部。
实际使用中也确实符合这样的设想,框架使用者需要关心的请求内容都在Request中了
2.2、构造OkHttpClient对象
public class OkHttpClient implements Cloneable, Call.Factory, WebSocketCall.Factory {
...
public static final class Builder {
Dispatcher dispatcher;
Proxy proxy;
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
ProxySelector proxySelector;
CookieJar cookieJar;
Cache cache;
InternalCache internalCache;
SocketFactory socketFactory;
SSLSocketFactory sslSocketFactory;
CertificateChainCleaner certificateChainCleaner;
HostnameVerifier hostnameVerifier;
CertificatePinner certificatePinner;
Authenticator proxyAuthenticator;
Authenticator authenticator;
ConnectionPool connectionPool;
Dns dns;
boolean followSslRedirects;
boolean followRedirects;
boolean retryOnConnectionFailure;
int connectTimeout;
int readTimeout;
int writeTimeout;
}
...
@Override
public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
...
}
OkHttpClient的构建依然是使用构建者模式。
值得注意的第一个点是OkHttpClient的Builder中是有Dispatcher的,也就是说OkHttpClient中有Dispatcher对象可以做异步请求
更值得注意的是OkHttpClient实现了Call.Factory接口,创建了RealCall类的实例(Call的实现类),并将OkHttpClient对象自身的引用和request对象传递了过去。
从文章开头写的OkHttp的简单使用可以看到在发送请求之前,是调用了newCall方法,创建了一个Call对象,实际上RealCall对象持有了OkHttpClient的引用和request对象引用,使得后面能够使用这两个对象发出请求
2.3、Call接口及其作用
我们先看看RealCall实现的Call接口
public interface Call extends Cloneable {
Request request();
Response execute() throws IOException;
void enqueue(Callback responseCallback);
void cancel();
boolean isExecuted();
boolean isCanceled();
Call clone();
interface Factory {
Call newCall(Request request);
}
}
基本上我们对一个请求会调用的方法都定义在这个接口里面了。比如同步请求调用execute方法,异步请求调用enqueue方法等。可以说这个接口就是OkHttp框架的操作核心。在构建好OkHttpClient和Request,调用newCall方法创建出Call对象后,我们就是持有着Call对象在进行请求的。
我们继续按照上面的流程图查看源码,发送请求时,将请求丢入请求队列,即调用RealCall的enqueue方法
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
//方便阅读,这里省略了部分代码逻辑,只展示关键步骤
//拿到okhttpClient中的diapatcher
Dispatcher dispatcher=client.dispatcher()
//将异步请求Runnable丢入dispatcher分发器中
/*
*这个AsyncCall类实现了Runnable接口
*responseCallback就是我们期望的response的回调
*/
dispatcher.enqueue(new AsyncCall(responseCallback));
}
2.4、Dispatcher执行请求
我们先看下Dispatcher分发器中的enqueue方法,再看AsyncCall中具体操作
...
...
//等待异步执行的队列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//正在执行的异步队列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//正在自行的同步队列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
...
synchronized void enqueue(AsyncCall call) {
//如果正在执行的请求小于设定值,默认64
//并且请求同一个主机的request小于设定值,默认为5
if (runningAsyncCalls.size() < maxRequests &&
runningCallsForHost(call) < maxRequestsPerHost) {
//添加到执行队列,开始执行请求
runningAsyncCalls.add(call);
//获得当前线程池,没有则创建一个
ExecutorService mExecutorService=executorService();
//执行线程
mExecutorService.execute(call);
} else {
//添加到等待队列中
readyAsyncCalls.add(call);
}
}
现在我们再看下AsyncCall实现了Runnable接口的类
//它是RealCall的一个内部类
//NamedRunnable实现了Runnable接口,把run()方法封装成了execute()
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
@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 {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
...
...
//发出请求得到响应的地方
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));
//责任链模式对request进行了系列操作后发出
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
在getResponseWithInterceptorChain方法中,可以看到很多个Interceptor。从名称上看,有重试的,缓存的,发出请求的。前文我们也提到了Request并不是请求的全部,OkHttp为我们隐藏了部分操作。到这里就可以看到了,是在Interceptor中为我们处理的。OkHttp整体框架到这里也就讲清楚了。
关键类:OkHttpClient、Request、Call、RealCall、Dispatcher、Response、Interceptor等
这些关键类各自负责什么,你清楚了吗?
3、Interceptor拦截器详解
拦截器,顾名思义是对请求Request或者响应Response做一些处理,而OkHttp是通过责任链模式,将所有的Interceptor串联在一起,保证Interceptor按规则一个一个执行
3.1、拦截器接口设计
我们先来看下Interceptor接口
public interface Interceptor {
//只有一个接口方法
Response intercept(Chain chain) throws IOException;
//责任链模式的联调
interface Chain {
// 包含了请求Resquest
Request request();
//获得Response
Response proceed(Request request) throws IOException;
//获得当前网络连接
Connection connection();
}
}
这块的整体设计使用了责任链模式,而在OkHttp这个框架中,使用责任链模式得到的效果是对请求和响应进行了拦截,达到了将各类加工处理进行解耦的效果。为了帮助理解,下面绘制一张流程图
image在OkHttp具体实现中,在各个拦截器上流转的是责任链的链条。包装了Request的链条经过各个拦截器进行了系列处理,最后响应又从各个拦截器逆序流转回来。
3.2、责任链模式实现拦截器功能
下面我们一起看下OkHttp的源码是怎么实现这个过程的
Response getResponseWithInterceptorChain() throws IOException {
List<Interceptor> interceptors = new ArrayList<>();
//开发者自定义的Interceptor
interceptors.addAll(client.interceptors());
//这个Interceptor是处理请求失败的重试,重定向
interceptors.add(retryAndFollowUpInterceptor);
//这个Interceptor工作是添加一些请求的头部或其他信息
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//这个Interceptor的职责是判断缓存是否存在,读取缓存,更新缓存等等
interceptors.add(new CacheInterceptor(client.internalCache()));
//这个Interceptor的职责是建立客户端和服务器的连接
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//添加开发者自定义的网络层拦截器
interceptors.addAll(client.networkInterceptors());
}
//这个Interceptor的职责是具体发送请求
interceptors.add(new CallServerInterceptor(forWebSocket));
//一个包裹这request的chain
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
//把chain传递到第一个Interceptor手中
return chain.proceed(originalRequest);
}
从上述代码看,最后构建了链条,开始执行就结束了,那拦截器的流转就是在Chain中实现的了。我们接着看RealInterceptorChain的实现
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final Connection connection;
private final int index;
private final Request request;
private int calls;
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, Connection connection, int index, Request request) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
}
...
@Override
public Response proceed(Request request) throws IOException {
//直接调用了下面的proceed(.....)方法。
return proceed(request, streamAllocation, httpCodec, connection);
}
//这个方法用来获取list中下一个Interceptor,并调用它的intercept()方法
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
Connection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
...
// Call the next interceptor in the chain.
//注意看这里的index+1了
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
//从list中获取到第一个Interceptor
Interceptor interceptor = interceptors.get(index);
//然后调用这个Interceptor的intercept()方法,并等待返回Response
//注意看,这个把next传递过去了,意味着这里的interceptor执行时,可以拿到next调用proceed方法,从而走起整条链路
Response response = interceptor.intercept(next);
....
....
return response;
}
这块由于是代码逻辑层面的,大家查看的时候注意看下上面代码的注释哈。最关键点步骤就是上面的几个注意了。拦截器拿到链条Chain(此时的联调带了所有的拦截器和下一个拦截器的索引),只要在拦截器中执行链条中的下一个拦截器,依次进行,就能走通整个链条
下面我们看下RetryAndFollowUpInterceptor这个拦截器的实现,是否按照我们所想的,调用了链条的proceed方法
...
//直接调用自身的intercept()方法
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
....
....
Response response = null;
boolean releaseConnection = true;
try {
//在这里通过继续调用RealInterceptorChain.proceed()这个方法
//在RealInterceptorChain的list中拿到下一个Interceptor
//然后继续调用Interceptor.intercept(),并等待返回Response
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
...
} catch (IOException e) {
...
} finally {
...
}
}
}
...
到这里,讲通了OkHttp拦截器部分是怎么使用责任链模式,对请求和响应进行拦截的了哈。
你看懂了吗?
但是,OkHttp到底怎么进行网络请求的,是在最后一个拦截器CallServerInterceptor中进行的,请求最终是利用Socket进行的,如果你有兴趣了解Socket如何进行请求的,Socket进行Http/Https请求
另外,本篇没有进行讲解的Dispatcher分发器,它的内部实现是使用了缓存类型的线程池进行的。简单展示下代码如下
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;
}
如果你想更多的了解线程池的知识,深度解析Java线程池,里面介绍了线程池各个参数的意义,以及各类线程池的不同,看完相信你能对OkHttp的线程池参数为什么是这样设置的进行一定的思考哦!
网友评论