Android网络平台的三板斧基本被square公司承包了,Okhttp,Retrofit,Okio真可是三巨头。平时用的okhttp比较多,所以我们很有必要来看看它的实现原理。
相信大家对okhttp的使用一点都不陌生了,我这里不会详细讲解它的使用,仅仅把官网上的搬下来看看,作为研究的入口:
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
复制代码
这个是比较简单的Get请求
public static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
复制代码
这个是Post请求
从上面的使用来看,不管是get还是post请求,都会先创建OkHttpClient实例,这个很容易理解,没有实例哪来的入口呢,观摩一下:
OkHttpClient client = new OkHttpClient(); 复制代码
接着看看实例化了哪些东西
public OkHttpClient() {
this(new Builder());
}
复制代码
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;
...
}
复制代码
这是默认的实例化方式,builder里面也都是默认值,主要实例了代理,缓存,超时等等一些参数,这些系统都给我们设置了一些默认的值,比如超时是10s,读写也是10s等等。但是在实际情况中,我们往往去要自定义一些方式,比如,读写时间设置30s,增加一些拦截器等,这就需要另外一种实例化的方式了。
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.connectTimeout(20 * 1000, TimeUnit.MILLISECONDS)
.readTimeout(30 * 1000, TimeUnit.MILLISECONDS)
/* 关闭OkHttp失败重试连接的机制,该问题导致发帖重复的问题 */
.retryOnConnectionFailure(false)
.addInterceptor(new EnhancedCacheInterceptor())
.addInterceptor(new SecurityInterceptor(mApplicationContext)) // 加密解密
.addNetworkInterceptor(new StethoInterceptor())
.dns(new dsn().build();
复制代码
看上去很清晰,采用的是建造者模式,我们设置自定义参数,然后通过build去实例化。这两种方式具体怎么用,选择哪一种那就看我们具体业务了。
接下来看看Request的封装
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
复制代码
同样的,不管什么请求方式,都要构建Request
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable RequestBody body;
final Object tag;
private volatile CacheControl cacheControl; // Lazily initialized.
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;
}
复制代码
可以看到request包含url,请求方法,header,以及body。这也就对应了http里面的请求头部,包含一些具体信息。它也是采用的建造者模式去构造的。平时我们用得比较多的是post请求,这里我们做个简要的分析:
RequestBody body = RequestBody.create(JSON, json);
...
public Builder post(RequestBody body) {
return method("POST", body);
}
复制代码
首先我们构造RequestBody,body里面封装表单或者json,然后调用post方法:
if (method == null) throw new NullPointerException("method == null");
if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
if (body != null && !HttpMethod.permitsRequestBody(method)) {
throw new IllegalArgumentException("method " + method + " must not have a request body.");
}
if (body == null && HttpMethod.requiresRequestBody(method)) {
throw new IllegalArgumentException("method " + method + " must have a request body.");
}
this.method = method;
this.body = body;
return this;
}
复制代码
这里面很简单,分别对method,body做个校验,这两者都不能为空,也就是说,当采用post去发请求的时候body是不能为空的,这个错误有时候我们经常会遇到。然后赋值给request的变量存着备用。
ok,如果上面的都是准备工作,那么接下来就是正式发送请求了。还是直接看代码:
Response response = client.newCall(request).execute() 复制代码
这个是发送同步请求的,哇塞,一句话?是的,就是一句话解决了,看上去简直不可思议,我们看看源码newCall方法,很显然call是发送请求的核心:
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
复制代码
接着去RealCall类中去调用newRealCall方法:
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
复制代码
构造函数把参数赋值,然后new了一个实例RetryAndFollowUpInterceptor,这个我们后面再来看。接着就是调用execut函数了:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
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);
}
}
复制代码
这里做了几件事:
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, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
复制代码
这看上去有点儿陌生,里面增加了各种拦截器,然后调用了chain.proceed(originalRequest),感觉这才是大boss啊,官网里面有句话
the whole thing is just a stack of built-in interceptors. 复制代码
这句话很有含金量,也就是说okhttp所做的是就是拦截各种请求去解析,对拦截器一一做处理,纳闷了,为啥要拦截这么多?因为一个请求的发送到接收到返回数据,中间要做很多是事,比如失败重传,缓存,还有自定义的拦截器,比如对数据加密等等。
首先给出一张整体图:
上一节中,实例化RealInterceptorChain 后调用了chain.proceed();最终会执行下面的代码
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = 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;
}
复制代码
这是核心中的核心,它采用了责任链模式,这种模式,是java设计模式中比较重要的一种,大家不懂的可以单独去查查,它的总体思想就是如果自己可以消费事件就消费,不消费就传递给下一个拦截器。
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
复制代码
可以看到这里的index进行了+1处理,然后开始启动下一个拦截器。很显然,这这些拦截器的执行是有顺序的。我们先看看这种拦截器的左右:
/** * Bridges from application code to network code. First it builds a network request from a user * request. Then it proceeds to call the network. Finally it builds a user response from the network * response. */ 复制代码
它的意思是把用户的代码,也就是我们写的代码,转换为服务器标准的代码。解释:它会提取http请求所需要的一些参数,比如agent,heades,contenttype等,然后发送出去。响应的过程也是一样,把服务器的响应结果转换为我们需要的,比如把respones转换为我们需要的对象。实质就是按照http协议标准化格式。
/** Serves requests from the cache and writes responses to the cache. */ 复制代码
这个主要是处理缓存的,我们发送请求和获取响应结果的时候,不能每次都去重新创建吧,那样效率太低,首先去从缓存中去取没,如果缓存中有现有的连接,我们直接发请求,不用重复创建。响应结果也是一样的。
/** Opens a connection to the target server and proceeds to the next interceptor. */ 复制代码
如果缓存没有命中,那么就去新建连接啦,这个用来新建连接的。
/** This is the last interceptor in the chain. It makes a network call to the server. */ 复制代码
这个是责任链模式的尾端,它最终会去通过网络请求服务器资源。
这里我来着重分析一下ConnectInterceptor,CallServerInterceptor这两种,因为这是最终建议连接和发送请求的过程,可以说是最重要的。
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
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);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
复制代码
这个过程创建了HttpCodec,但是它并没有在这个拦截器中去使用,而是传递到后面发动请求的拦截器中去了也即是CallServerInterceptor。HttpCodec到底是什么,它是对HTTP协议做了抽象处理,在 Http1Codec 中,它利用 Okio 对 Socket 的读写操作进行封装,Okio 以后有机会再进行分析,现在让我们对它们保持一个简单地认识:它对 java.io 和 java.nio 进行了封装,让我们更便捷高效的进行 IO 操作。
@Override public Response intercept(Chain chain) throws IOException {
HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
Request request = chain.request();
long sentRequestMillis = System.currentTimeMillis();
httpCodec.writeRequestHeaders(request);
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
httpCodec.finishRequest();
Response response = httpCodec.readResponseHeaders()
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
// 省略部分检查代码
return response;
}
复制代码
这个里面代码比较长,我们只保留核心分心,上面不是传递了HttpCodec了嘛,这里就利用起来了,它是利用okio进行发送请求,这里okio不是我们分析的重点,读者可以单独找文章去看看。 总之:Okhttp发送请求是通过okio去做的,okio实质上就是对socket进行了封装,一层套一层而已,这就是神秘的面纱后面的简单。 这里做了几件事:
我们对okhttp做了个整体介绍,当然里面有很多细节没有解释,也不想去解释,读者有需要自己去研究。这里我总结一下:
OKhttp中使用的设计模式:建造者模式和责任链模式;
最后上一张图片,这个是从其他读者文章弄过来的,很用心,我就直接使用了;