一、Okhttp的简单使用
使用步骤
- 构建网络请求控制对象OkHttpClient
- 构建请求对象request
- 创建Call对象
- 创建接收返回数据的对象response
- 发送网络请求
第一步和第二步我都用了“构建”这个词,这是因为这两个对象内部都是通过建造者设计模式来创建的。
当请求准备好了后,就开始建立和服务器的连接。连接成功后,执行最后两步,也就是就是发送请求和接收返回数据。
1.下面是一个同步请求的示例代码
//1.构建OkHttpClient对象
OkHttpClient okHttpClient=new OkHttpClient();
Request.Builder builder=new Request.Builder(); //2.构建一个请求对象request,比如请求的url,请求方式
Request request=builder.url("http://ip.taobao.com/service/getIpInfo.php?ip=223.68.134.166")
.get()
.build();
//3.创建一个Call对象
final Call call= okHttpClient.newCall(request);
new Thread(new Runnable() {
@Override
public void run() {
try {
//4.发送请求,并接收回复的返回数据
Response response=call.execute();
Log.d("response", "run: "+response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
同步请求是什么意思呢?
简单的说就是有多个请求的情况下,一次只能处理一个请求,只有获得这次请求返回的响应时,才能发送下一个请求。
OkHttp的请求有两种方式:同步和异步请求
两者在使用方式上最大的不同在于最后一步调用的方法:
同步请求调用的是 execute方法,异步请求调用的是 enqueue方法
2.发送异步请求的代码示例
//发送异步请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, "异步请求失败"+e.toString());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, "异步请求成功"+response.body().string());
}
});
但它们在实现方式上确有很大的不同。
关于Okhttp的更多使用方法请看这里:
OkHttp用法
二、源码解析
1.核心控制类
从流程的步骤可以看出OkHttpClient是我们的核心控制类,它控制着整个网络请求,就拿它当切入口
先看它的定义的一大堆东西(这里先随便过一眼就好了)
final Dispatcher dispatcher; //分发器
final Proxy proxy; //代理
final List<Protocol> protocols; //协议
final List<ConnectionSpec> connectionSpecs; //传输层版本和连接协议
final List<Interceptor> interceptors; //拦截器
final List<Interceptor> networkInterceptors; //网络拦截器
final ProxySelector proxySelector; //代理选择
final CookieJar cookieJar; //cookie
final Cache cache; //缓存
final InternalCache internalCache; //内部缓存
final SocketFactory socketFactory; //socket 工厂
final SSLSocketFactory sslSocketFactory; //安全套接层socket 工厂,用于HTTPS
final CertificateChainCleaner certificateChainCleaner; // 验证确认响应证书 适用 HTTPS 请求连接的主机名。
final HostnameVerifier hostnameVerifier; // 主机名字确认
final CertificatePinner certificatePinner; // 证书链
final Authenticator proxyAuthenticator; //代理身份验证
final Authenticator authenticator; // 本地身份验证
final ConnectionPool connectionPool; //连接池,复用连接
final Dns dns; //域名
final boolean followSslRedirects; //安全套接层重定向
final boolean followRedirects; //本地重定向
final boolean retryOnConnectionFailure; //重试连接失败
final int connectTimeout; //连接超时
final int readTimeout; //read 超时
final int writeTimeout; //write 超时
final int pingInterval; //ping的间隔时间
接下来看它的构造方法
public OkHttpClient() {
this(new Builder());
}
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
//.....、此处省略大量代码
}
可以看出来它使用了建造者模式。我们可以针对性选择它在Builder里定义的参数进行设置,如果不设置它的参数,就会使用它的默认值,像这样
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
//.....、此处省略大量代码
}
同样Request对象也是通过建造者模式构建的
public final class Request {
final HttpUrl url; //请求的url
final String method; //请求方式
final Headers headers; //请求头
final @Nullable RequestBody body; //请求体
final Map<Class<?>, Object> tags;
Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tags = Util.immutableMap(builder.tags);
}
把控制类对象和请求对象都准备好了后,我们需要创建一个Call对象,进入源码我们可以看到它其实是一个接口,那我们就需要找它的实现类-RealCall,它才是真正的请求执行者。
通过调用newCall方法,创建RealCall对象
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
到这里,都算是为我们正式请求之前的准备工作。其实,真正的请求在背后做了什么,才是Okhttp的优秀之处,精华所在。同时也是重难点所在,接下来,才是真正的大餐。
2.拦截器机制
在继续分析源码之前,先介绍一个很重要的概念-拦截器机制。okhttp就是通过拦截器机制来获取和处理网络请求响应的。
先从三个层看这个图
OkhttpInterceptor.png在图中,可以看到在请求和获取响应时,都会经过Okhttp的核心,并且都会经过一系列OkHttp自带的拦截器(图中的橙色区域)。当然在应用层和网络层也有应用拦截器和网络拦截器。我们主要分析Okhttp自带的拦截器
那么这些拦截器是负责做什么的呢?
首先这些拦截器构成一条链(可以理解成链条),这些拦截器会在发起请求前对request进行处理,然后调用下一个拦截器,获取response,最后对response进行处理,返回给上一个拦截器。
接下来先大致看一下它们的作用,在后面会详细介绍这些拦截器的实现流程
1.RetryAndFollowUpInterceptor(重试重定向拦截器)
重试那些失败或者重定向的请求。因为在整个网络请求的过程中,网络可能不稳定
2.BridgeInterceptor(桥接拦截器)
请求之前对响应头做了一些检查,并添加一些头。负责将用户构建好的Request请求转换成可以进行网络访问的请求
3.CacheInterceptor(缓存拦截器)
做一些缓存的工作。获取响应时先从缓存里去获取,如果缓存没有才会进行网络请求,并把获取到响应后放入缓存
4.ConnectInterceptor(连接拦截器)
负责建立和目标服务器的连接
5.CallServerInterceptor(发送服务请求拦截器)
负责向服务器发起真正的网络请求
这些拦截器流程走向如下
interceptor.jpg这些拦截器是什么时候被创建的呢?
这个时候我们就可以进入同步的请求execute方法
public Response execute() throws IOException{
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
//通过拦截器链获取响应
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 {
client.dispatcher().finished(this);
}
}
通过一个ArrayList集合创建这些拦截器,形成一条链条,互相影响。也可以看成是一个栈,内部是递归的形式一层层返回响应结果。
Response getResponseWithInterceptorChain() throws IOException {
//把所有拦截器放入list集合中
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);
}
这里我没有分析异步的过程,因为它比同步复杂多了,不信你看
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));
}
确定没有逗我???我可是受过九年义务教育的。这代码量比enqueue简短多了。这是因为它把职责交给了Dispatcher类了,而Dispatcher又牵涉到线程池等。
调用Dispatcher.enqueue时传入了一个AsyncCall对象
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
protected void execute() {
boolean signalledCallback = false;
timeout.enter();
try {
Response response = getResponseWithInterceptorChain();
}
//...
}
public abstract class NamedRunnable implements Runnable {
//...
public final void run() {
try {
execute(); //执行子类的execute方法
}
//...
}
protected abstract void execute();
}
可以看到,在异步请求的过程中,它最终还是会调用 getResponseWithInterceptorChain方法,也就是通过拦截器链获取返回的响应。
到这里我们对拦截器有了一个浅层的认识了,下面开始详细介绍每个拦截器的实现流程
2.1 RetryAndFollowUpInterceptor
public final class RetryAndFollowUpInterceptor implements Interceptor {
private volatile StreamAllocation streamAllocation; //建立执行http请求的所有网络组件
//......
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
//1.创建StreamAllocation对象
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
//进行网络请求连接
while (true) {
//...
//2.调用RealInterceptorChain.proceed(),调用下一个拦截器进行网络请求连接,获取response
try {
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) { }
//...
try {
//重定向请求
followUp = followUpRequest(response, streamAllocation.route());
} catch (IOException e) {
streamAllocation.release();
throw e;
}
//超过一定的重连次数,抛出异常
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
}
}
大致流程
- 创建StreamAllocation对象,用于建立执行http请求的所有网络组件
- 调用RealInterceptorChain.proceed()来调用下一个拦截器,获取响应结果response
- 根据响应结果判断是否进行重新请求
2.2 BridgeInterceptor
public final class BridgeInterceptor implements Interceptor {
public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
//进行请求头的包装
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
}
//....
}
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
大致流程:
- 对请求头做了一些检查,并添加一些头,目的是Request请求转换成可以进行网络访问的请求
- 将这个符合网络请求的request进行网络请求
- 然后将请求回来的响应结果Response转换为用户可见的Response
2.3 CacheInterceptor
CacheInterceptor内部实现了一个缓存策略类CacheStrategy
public final class CacheInterceptor implements Interceptor {public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null? cache.get(chain.request()): null;
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
//调用下一个拦截器
try {
networkResponse = chain.proceed(networkRequest);
} finally {
//...
}
}
先判断缓存有没有响应,如果有就从缓存里获取;如果缓存没有就从网络中获取响应
//如果缓存有响应
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
如果缓存没有响应且网络请求结果为空,抛出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();
}
大致流程如下:
- 获取本地缓存cacheCandidate,如果本地缓存可用则打断interceptor链,返回cacheCandidate,
- 调用下一个interceptor获取networkResponse
- 用networkResponse、cacheResponse构造新的response
- 根据新的response里的header通过缓存策略存入缓存中
2.4 ConnectInterceptor
okhttp的一大特点就是通过连接池来减小响应延迟。如果连接池中没有可用的连接,则会与服务器建立连接,并将socket的io封装到HttpStream(发送请求和接收response)中,这些都在ConnectInterceptor中完成。
public final class ConnectInterceptor implements Interceptor {
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 httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
//通过streamAllocation获取RealConnection对象
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
大致流程:
- ConnectInterceptor获取之前的拦截器传过来的 StreamAllocation
- 通过streamAllocation获取RealConnection对象,它是真正用于连接网络的对象
- 将RealConnection对象,以及对于与服务器交互最为关键的HttpCodec等对象传递给后面的拦截器
HttpCodec的作用简单的理解就是对request编码,对response解码
2.5 CallServerInterceptor
CallServerInterceptor是最后一个拦截器,它负责发起真正的网络请求并接收服务器返回的响应数据
public final class CallServerInterceptor implements Interceptor {
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
realChain.eventListener().requestHeadersStart(realChain.call());
//写入请求头信息
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
//写入请求体的信息
request.body().writeTo(bufferedRequestBody);
//完成网络请求工作
httpCodec.finishRequest();
if(responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
//读取网络响应的头信息
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
//读取网络响应的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();
}
}
return response;
}
大致流程:
- 写入请求头和请求体信息,发起正在的网络请求
- 获取网络请求返回的响应,读取响应头和响应体的信息
- 返回最终的响应结果
三、总结
一图胜千言
同步和异步请求.jpg到这里把OkHttp的执行流程都介绍了一遍,但是具体到每一个部分内部实现原理远没有我说的这么简单,牵涉到很多的知识点。这里就不进行分析了,一篇文章也是不能讲清楚的。相信你看完这篇文章之后,再去看它的Okhttp的源码,会更轻松的。
网友评论