Okhttp网络框架的学习及源码分析
HTTP及相关协议
HTTP协议
出现及发展
- 1960年美国人Ted Nelson提出想法
- 由万维网协会(www)和互联网工程工作共同研究
- 目前使用最广泛的是HTTP1.1
HTTP协议结构
HTTP协议的结构有四个主要组成部分:
- 请求头
- 表明本次请求的客户端
- 本次请求所使用的cookie(标明本次请求的用户身份)
- 本次请求希望返回的数据类型(json、string…)
- 本次请求是否采用数据压缩等一系列设置
- 请求体
- 指定本次请求使用的方法
- 携带本次请求参数
- 响应头
- 服务器标识
- 状态码
- 内容编码
- cookie(返回给客户端用于下次请求使用)
- 响应体
- 主要是我们本次请求所返回的数据
HTTP工作流程
- 首先,客户机与服务器要建立连接
- 建立连接后,客户机发送请求给服务器
- 服务器接收到请求后,给予响应的响应
- 客户端接收服务器返回的信息,断开连接
优势
- 简单、快速
- 灵活
- 无连接
- 无状态
SPDY协议
在早期HTTP中,由于互联网的协议都比较简单,基本都是一些静态的页面。因此无连接无状态的特点可以使简单、快速。灵活的优势发挥到最大。但随着业务逻辑的复杂、安全性要求提高等等,无连接无状态正在成为HTTP的劣势。因此才会出现一些更加高级的协议,如SPDY。
SPDY是HTTP的兼容协议,基于HTTP。除了HTTP的几个最基本的功能外,还有以下的优点:
- 多路复用请求
- 对请求划分优先级
- 压缩Http头
HTTP2.0协议
HTTP2.0协议是完全基于SPDY,IETF定制的新一代HTTP协议。它相比SPDY有着更安全的SSL协议,采用了更安全的加密算法等等…
okhttp网络框架
简介
既然我们的协议已经支持了HTTP2.0,但我们的客户端该如何使用HTTP2.0呢?
okhttp框架为Android提供了解决方案。它由著名的square团队开发,并且开源。这里是它的github地址
优势
- 支持SPDY,HTTP2.0,HTTP1.0及HTTP1.1
- SPDY,HTTP2.0共享同一个Socket来处理同一个服务器的所有请求,来提高连接的效率
- 如果SPDY不支持,则通过连接池来减少请求的延时
- 缓存相应数据来减少重复的网络请求
- 可以从很多常见连接问题中自动恢复
- 使用简单
使用方法
GET
下面我们来看一下okhttp的使用方法。
首先,我们需要通过Request的Builder来为它设置url等参数。
Request request = new Request.Builder()
.url("https://www.baidu.com")
.build();
之后,我们需要通过OkHttpCilent来构建一个Call,需要将Request传入。
然后我们可调用Call的enqueue()方法,并传入一个Callback,来响应我们的请求。
OkHttpClient client = new OkHttpClient();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//do sth
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String msg = response.body().string();
//do sth
}
});
这样就成功发送了一个GET请求
POST
要发送POST请求,首先仍然是构建我们的Request。这次需要用到FormBody来添加键值对参数。再在构建Request时用post方法来将FormBody传入。
FormBody.Builder formBodyBuild = new Builder();
formBodyBuild.add("username","USERNAME")
.add("password","PASSWORD");
Request request = new Request.Builder().url(LOGIN_URL).post(formBodyBuild.build()).build();
然后我们就可以像之前那样构建一个Call:
OkHttpClient client = new OkHttpClient();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
//do sth
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String msg = response.body().string();
//do sth
}
});
这样就达到了发送POST请求的效果。z
原理解析
总体设计
okhttp的总体设计图如下,首先通过构建者模式构建Request。(okhttp中几乎所有的类都用到了构建者模式)之后将request分发到Dispatcher中,然后再分发到HttpEngine中。
HttpEngine首先查看本次请求是否有缓存(Cache)。如果有,就从Cache中拿到信息直接给Response。如果没有,则会把这次请求发到ConnectionPool中,从连接池中获取到Connection以后,通过Connection去发送真正的请求。请求到之后,又通过Route和Platform找到一个合适的平台。最后通过Server Socket去获取Data。
流程图
以下是okhttp的流程图。由于我们常用异步请求,因此从异步请求的分支来讲解。
首先要创建请求Call,然后发起异步请求。异步请求在发起之前会通过一系列的拦截,拦截一些无效或恶意的请求。之后到达HttpEngine中,在有缓存的情况下通过缓存文件系统来获取Response。没有缓存则真正的去发送网络请求获取Response。之后相应Request
核心类图
- Dispatcher是分发器,主要用于分发异步任务。
- Protocol则是列举了okhttp支持的协议(SPDY,HTTP2.0,HTTP1.0及HTTP1.1)的枚举
- ConnectionSpec用于指定每个Connection中的一些常量和变量等。
- ConnectionPool就是用于管理Connection的连接池。内部维护了一个列表,这个列表就是本系统内所有可用的Connection。
- Interceptor是过滤器,由过滤器构成了Interceptor.Chain,也就是过滤器列。
- InternalCache和Cache共同构成了okhttp的缓存系统。
这些子类都是为OkhttpClient类服务的。OkhttpClient几乎引用了所有的类,是整个okhttp框架的核心类。它调用对应的模块去达到发送请求、相应请求等操作。
okhttp的多路复用机制
首先,HttpEngine类在发送请求前会调用nextConnection方法,来获取Connection对象。如果能从ConnectionPool中获取到Connection对象,则不会新建一个Connection。如果获取不到,则调用createNextConnection方法去创建Connection。
下面是连接执行的时序图:
okhttp的重连机制
Call发送到HttpEngine中时,每次都会判断是否能getResponse()。如果不可以,则会执行recover()方法,开始Retry()机制
下面是自动重连机制的时序图:
源码分析
Route类
Route就是一个路由类。它封装了一个Address类,里面就包含了所有请求的信息,如端口号、服务器ip地址等等。最后要建立Connection时会从Address中获取相应数据。
public final class Route {
final Address address;
final Proxy proxy;
final InetSocketAddress inetSocketAddress;
...
}
Address类
public final class Address {
final HttpUrl url;
final Dns dns;
final SocketFactory socketFactory;
final Authenticator proxyAuthenticator;
final List<Protocol> protocols;
final List<ConnectionSpec> connectionSpecs;
final ProxySelector proxySelector;
final Proxy proxy;
final SSLSocketFactory sslSocketFactory;
final HostnameVerifier hostnameVerifier;
final CertificatePinner certificatePinner;
...
}
ResponseBody类
从名字就可以看出,它就是一个响应体类。对应了响应中的响应体。它有一个最核心的方法,就是bytes()方法。因为我们返回的所有数据,它最初返回的样子都是我们的字节码。可以通过responseBody,去获取对应的数据。
public final byte[] bytes() throws IOException {
long contentLength = contentLength();
if (contentLength > Integer.MAX_VALUE) {
throw new IOException("Cannot buffer entire body for content length: " + contentLength);
}
BufferedSource source = source();
byte[] bytes;
try {
bytes = source.readByteArray();
} finally {
Util.closeQuietly(source);
}
if (contentLength != -1 && contentLength != bytes.length) {
throw new IOException("Content-Length and stream length disagree");
}
return bytes;
}
比如如果要获取String类型的数据,可以通过byte数组转化成String。如果想获取图片,也是一样的道理。
Response类
Response就是我们的响应。它包含了响应头Headers与响应体responseBody,它们也是Response最主要的两个成员变量。它内部是一个构建者模式,来构建Response。
public final class Response {
private final Request request;
private final Protocol protocol;
private final int code;
private final String message;
private final Handshake handshake;
private final Headers headers;
private final ResponseBody body;
private Response networkResponse;
private Response cacheResponse;
private final Response priorResponse;
...
}
RequestBody类
显而易见,RequestBody对应的就是我们的请求体了,它其实是一个抽象类,定义了一些抽象的方法。它有两个实现的类,分别是FormBody以及MultipartBody。
FormBody
FormBody是RequestBody的其中一个实现类,在发送Post请求时,会构建一个FormBody,将键值对分别传入。是以构建者方法来创建的。
public final class FormBody extends RequestBody {
private static final MediaType CONTENT_TYPE =
MediaType.parse("application/x-www-form-urlencoded");
private final List<String> encodedNames;
private final List<String> encodedValues;
...
}
MultipartBody
MultipartBody主要用于上传文件时使用,因为FormBody只能上传key,value这样简单类型的数据。而MultipartBody就可以上传对象类型的数据。它也是用构建者模式来创造,内部有个关键的方法,在Builder中的addFormDataPart方法。它有几种重载,一种可以简单地以key value的方式添加,另一种则可以通过RequestBody来上传文件等。
Request类
Request也是一个构建者模式,它内部封装了本次请求的URL,请求要用的method,以及请求头和请求体。而tag,则是用于取消请求。
public final class Request {
private final HttpUrl url;
private final String method;
private final Headers headers;
private final RequestBody body;
private final Object tag;
...
}
Call接口
这里的Call是一个接口,它代表了一个任务,就是真正需要HttpEngine去执行的任务。实现这个接口的则是RealCall类
RealCall类
RealCall是一个Runnable,实现了Call接口。它真正去执行了对请求的处理。比如可以通过它的getResponse方法拿到我们请求中的数据。然后通过HttpEngine去发送请求。
Response getResponse(Request request, boolean forWebSocket) throws IOException {
...
RequestBody body = request.body();
...
request = requestBuilder.build();
...
engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null)
...
}
HttpUrl类
HttpUrl是一个HTTP的Url工具类,它封装了一系列的对url处理的方法。
Headers类
Headers类代表了请求和响应的头。它内部有个很巧妙的设计,它也是键值对存放,但并没有用两个列表或者一个HashMap,而是通过一个String类型的数组,就实现了存放一个二维的key value。
public final class Headers {
private final String[] namesAndValues;
...
}
ConnectionPool类
ConnectionPool是okhttp中设计的连接池,是通过SynchronousQueue这种队列来实现的。在连接池中包含了各种管理连接的方法。
Callback类
Callback就是我们的相应的回调。最终就是要继承它来完成一些获得响应后的处理等等。里面有两个方法onFailure与onResponse。
Dispatcher类
Dispatcher类在okhttp起到分发器的作用。它内部有一个ExecutorService线程池,以及三种请求的队列。分别是一个异步准备就绪的任务队列,一个异步的正在执行的任务队列,以及一个同步的任务队列。
它内部最核心的方法就是executed方法,里面传入了RealCall参数。会将RealCall添加到正在执行的队列中。然后依次执行RealCall。RealCall又会调用HttpEngine来构建Connection,发送请求等,最后获取到一个最终的Response。最终的Response封装在了ResponseBody中。最后通过Callback的onResponse方法拿到Response,进行相应操作。
HttpEngine类
HttpEngine从字面意思上来看,就是okhttp的引擎了。它是真正需要发送请求、负责请求的重连机制的类。
sendRequest方法
首先看它的sendRequest方法。它先通过networkRequest方法去创建对应的Request。有了这个Request方法后,在缓存策略中查看是否有这样的缓存。如果有,则直接返回。如果没有,则真正的去构建一个请求,直到相应后获得相应的Response。
public void sendRequest() throws RequestException, RouteException, IOException {
if (cacheStrategy != null) return; // Already sent.
if (httpStream != null) throw new IllegalStateException();
Request request = networkRequest(userRequest);
InternalCache responseCache = Internal.instance.internalCache(client);
Response cacheCandidate = responseCache != null
? responseCache.get(request)
: null;
long now = System.currentTimeMillis();
cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
networkRequest = cacheStrategy.networkRequest;
cacheResponse = cacheStrategy.cacheResponse;
if (responseCache != null) {
responseCache.trackResponse(cacheStrategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
userResponse = new Response.Builder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(EMPTY_BODY)
.build();
return;
}
// If we don't need the network, we're done.
if (networkRequest == null) {
userResponse = cacheResponse.newBuilder()
.request(userRequest)
.priorResponse(stripBody(priorResponse))
.cacheResponse(stripBody(cacheResponse))
.build();
userResponse = unzip(userResponse);
return;
}
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean success = false;
try {
httpStream = connect();
httpStream.setHttpEngine(this);
if (writeRequestHeadersEagerly()) {
long contentLength = OkHeaders.contentLength(request);
if (bufferRequestBody) {
if (contentLength > Integer.MAX_VALUE) {
throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
+ "setChunkedStreamingMode() for requests larger than 2 GiB.");
}
if (contentLength != -1) {
// Buffer a request body of a known length.
httpStream.writeRequestHeaders(networkRequest);
requestBodyOut = new RetryableSink((int) contentLength);
} else {
// Buffer a request body of an unknown length. Don't write request headers until the
// entire body is ready; otherwise we can't set the Content-Length header correctly.
requestBodyOut = new RetryableSink();
}
} else {
httpStream.writeRequestHeaders(networkRequest);
requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
}
}
success = true;
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (!success && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
}
recover方法
HttpEngine中,recover的作用就是重连的方法。在它内部进行的操作就是重置一些参数,再去发送请求。
public HttpEngine recover(IOException e, Sink requestBodyOut) {
if (!streamAllocation.recover(e, requestBodyOut)) {
return null;
}
if (!client.retryOnConnectionFailure()) {
return null;
}
StreamAllocation streamAllocation = close();
// For failure recovery, use the same route selector with a new connection.
return new HttpEngine(client, userRequest, bufferRequestBody, callerWritesRequestBody,
forWebSocket, streamAllocation, (RetryableSink) requestBodyOut, priorResponse);
}
OkHttpCilent类
OkHttpCilent是okhttp的核心类,它里面几乎持有了所有的类的引用。它也是一个构建者类来进行构建的。它的作用就是调用各个模块而达到okhttp的功能的实现。
广告时间
我是N0tExpectErr0r,一名广东工业大学的大二学生
欢迎来到我的个人博客,所有文章均在个人博客中同步更新哦
http://blog.N0tExpectErr0r.cn