那么我今天给大家简单地讲一下Okhttp这款网络框架及其原理。它是如何请求数据,如何响应数据的 有什么优点?它的应用场景是什么?6z
说到Okhtpp的原理他是基于原生的Http,他的优点主要是如下几点:
1.支持同步、异步。
2.支持GZIP减少数据流量
3.缓存响应数据(从而减少重复的网络请求)
4.自动重连(处理了代理服务问题和SSL握手失败的问题)
5.支持SPDY(共享同一个Socket来处理同一个服务器的请求,如果SPDY不可用,则通过连接池来减少请求延时)
它的应用场景呢是在数据量特别大重量级的网络请求
上面说了一下他的优点,下面说一下他是如何发起网络请求如何响应数据的
OkHttp中的重要类:
1,OkHttpClient:OkHttp请求客户端,Builder模式实现
2,Dispatcher:本质是异步请求的调度器,负责调度异步请求的执行,控制最大请求并发数和单个主机的最大并发数,并持有有一个线程池负责执行异步请求,对同步请求只是作统计操作。
3,Request:封装网络请求,就是构建请求参数(如url,header,请求方式,请求参数),Builder模式实现
4,Response:网络请求对应的响应,Builder模式实现,真正的Response是通过RealCall.getResponseWithInterceptorChain()方法获取的。
5,Call:是根据Request生成的一个具体的请求实例,且一个Call只能被执行一次。
6,ConnectionPool:Socket连接池
7,Interceptor:Interceptor可以说是OkHttp的核心功能,它就是通过Interceptor来完成监控管理,重写和重试请求的。
8,Cache:可以自定义是否采用缓存,缓存形式是磁盘缓存,DiskLruCache。
不管是同步请求还是异步请求,都是通过RealCall.getResponseWithInterceptorChain()方法获取请求结
果的,只不过在前者在主线程中执行,而后者在线程池中的线程中执行的。
一个典型的请求过程是这样的,用一个构造好的OkHttpClient和Request获取到一个Call,然后执行call的异步或者同步方法取得Response或者处理异常,如下所示:
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url("")
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
OkHttp3,网络请求库,同步请求RealCall.execute()和异步请求RealCall.enqueue(),请求任务都是交给Dispatcher调度请求任务的处理,请求通过一条拦截链,每一个拦截器处理一部分工作,最后一个拦截器,完成获取请求任务的响应,会将响应沿着拦截链向上传递。
这里实际上,Call的实现是一个RealCall的类,execute的代码如下:
final class RealCall implements Call {
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain(false);
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
}
检查这个call是否已经被执行了,每个call 只能被执行一次
而enqueue实际上是RealCall的将内部类AsyncCall扔进了dispatcher中:client.dispatcher().enqueue(new AsyncCall(responseCallback));
public final class Dispatcher {
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
。AsyncCall实际上是一个Runnable,我们看一下进入线程池后真正执行的代码:
final class AsyncCall extends NamedRunnable
public abstract class NamedRunnable implements Runnable
AsyncCall 实现NameRunnable接口,丹NameRunnable又实现了Runnable接口
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain(forWebSocket);
if (canceled) {
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!
logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
client.dispatcher().finished(this);这里边this代表着AsycCall
于是这里需要介绍一个Dispatcher的概念。Dispatcher的本质是异步请求的管理器,控制最大请求并发数和单个主机的最大并发数,并持有一个线程池负责执行异步请求。
对同步的请求只是用作统计。
他是如何做到控制并发呢,
其实原理就在上面的execute代码里面,
真正网络请求执行前后会调用executed和finished方法,执行和完成
而对于AsyncCall的finished方法后,
会根据当前并发数目选择是否执行队列中等待的AsyncCall。
并且如果修改Dispatcher的maxRequests或者maxRequestsPerHost也会触发这个过程。最大请求主机
好的,
在回到RealCall中,我们看到无论是execute还是enqueue,真正的Response是通过这个函数getResponseWithInterceptorChain获取的,
其他的代码都是用作控制与回调。而这里就是真正请求的入口,也是到了OkHttp的一个很精彩的设计:Interceptor与Chain
上面分析到了,网络请求的入口实质上是在这里getResponseWithInterceptorChain
private Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors. //构建一个完整的拦截器堆栈 List 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(
5.isForWebSocket()));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
下面这是OkHttp内置拦截器的思维导图↓
RetryAndFollowUpInterceptor 后继拦截器
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
后继拦截器 拦截
在OkHttp的RetryAndFollowUpInterceptor请求重定向的Interceptor中是根据当前请求获取的Response,来决定是否需要进行重定向操作。在followUpRequest方法中,将会根据响应userResponse,获取到响应码,并从连接池StreamAllocation中获取连接,然后根据当前连接,得到路由配置参数Route。观察以下代码可以看出,这里通过userResponse得到了响应码responseCode。
接下来该方法会通过switch…case…来进行不同的响应码处理操作。
从这段代码开始,都是3xx的响应码处理,这里就开始进行请求重定向的处理操作。
重试与重定向拦截器,用来实现重试和重定向功能,内部通过while(true)死循环来进行重试获取Response(有重试上限,超过会抛出异常)。followUpRequest主要用来根据响应码来判断属于哪种行为触发的重试和重定向(比如未授权,超时,重定向等),然后构建响应的Request进行下一次请求。当然,如果没有触发重新请求就会直接返回Response。
当网络连接失败时重试
当服务器返回当前请求需要进行重定向是直接发起新的去请求,并在条件允许的情况下复用此链接。
主要做了三件事StreamAllocation:流量分配
[if !supportLists] [endif]创建了StreamAllocation,用于Socket管理
[if !supportLists] [endif]处理重定向
[if !supportLists] [endif]失败重连
BridgeInterceptor 桥接拦截器
在请求阶段自动补全请求头,在响应阶段对GZIP进行解压缩
就是告诉服务器客户端能够接受的数据编码类型,OKHTTP默认就是 GZIP 类型
GZIP:
GZIP是网站压缩加速的一种技术,对于开启后可以加快我们网站的打开速度,原理是经过服务器压缩,客户端浏览器快速解压的原理,可以大大减少了网站的流量。
Gzip开启以后会将输出到用户浏览器的数据进行压缩的处理,这样就会减小通过网络传输的数据量,提高浏览的速度。
是一种流行的文件压缩算法,现在的应用十分广泛,尤其是在Linux平台。当应用Gzip压缩到一个纯文本文件时,效果是非常明显的,大约可以减少70%以上的文件大小。这取决于文件中的内容。
BridgeInterceptor 功能主要有以下三点:
是负责将用户构建的一个Request请求转化为能够进行网络访问的请求。
将这个符合网络请求的Request进行网络请求。
Response networkResponse = chain.proceed(requestBuilder.build());
将网络请求回来的响应Response转化为用户可用的 Response
Transfer-Encoding值为 chunked 表示请求体的内容大小是未知的。
Host请求的 url 的主机
Connection默认就是 "Keep-Alive",就是一个 TCP 连接之后不会关闭,保持连接状态。
Accept-Encoding默认是 "gzip" 告诉服务器客户端支持 gzip 编码的响应。
Cookie当请求设置了 Cookie 那么就是添加 Cookie 这个请求头。
User-Agent "okhttp/3.4.1"这个值根据 OKHTTP 的版本不一样而不一样,它表示客户端 的信息。
上面就是将一个普通的Request添加很多头信息,让其成为可以发送网络请求的 Request
CacheInterceptor缓存拦截器
CacheInterceptor负责在request阶段判断是否有缓存,是否需要重新请求。在response阶段负责把response缓存起来
缓存其实是一个非常复杂的逻辑,单独的功能模块,它其实不属于OkHttp上的功能,只是通过Http协议和DiskLruCache做了处理而已。
DiskLruCache是Android提供的一个管理磁盘缓存的类。该类可用于在程序中把从网络加载的数据
保存到磁盘上作为缓存数据,例如一个显示网络图片的gridView,可对从网络加载的图片进行缓存,
提高程序的可用性。
刚才也提到了OkHttp的缓存拦截器不是属于OkHttp上的功能,他是通过Http协议和DiskLruCache做的处理 一张图来看一下缓存策略↓
ETag响应头部字段值是一个实体标记,它提供一个“不透明”的缓存验证器
两种缓存的区别:
对于强制缓存,服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行对比缓存策略。
对于对比缓存,将缓存信息中的Etag和Last-Modified通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。
HTTP的缓存规则是优先考虑强制缓存,然后考虑对比缓存。
[if !supportLists] [endif]首先判断强制缓存中的数据的是否在有效期内。如果在有效期,则直接使用缓存。如果过了有效期,则进入对比缓存。
[if !supportLists] [endif]在对比缓存过程中,判断ETag是否有变动,如果服务端返回没有变动,说明资源未改变,使用缓存。如果有变动,判断Last-Modified。
[if !supportLists] [endif]判断Last-Modified,如果服务端对比资源的上次修改时间没有变化,则使用缓存,否则重新请求服务端的数据,并作缓存工作。
CacheStrategy缓存策略
直接看CacheStrategy的get方法。缓存策略是由请求和缓存响应共同决定的。
[if !supportLists] [endif]如果缓存响应为空,则缓存策略为不使用缓存。
[if !supportLists] [endif]如果请求是https但是缓存响应没有握手信息,同上不使用缓存。
[if !supportLists] [endif]如果请求和缓存响应都是不可缓存的,同上不使用缓存。
[if !supportLists] [endif]如果请求是noCache,并且又包含If-Modified-Since或If-None-Match,同上不使用缓存。
[if !supportLists] [endif]然后计算请求有效时间是否符合响应的过期时间,如果响应在有效范围内,则缓存策略使用缓存。
[if !supportLists] [endif]否则创建一个新的有条件的请求,返回有条件的缓存策略。
[if !supportLists] [endif]如果判定的缓存策略的网络请求不为空,但是只使用缓存,则返回两者都为空的缓存策略。
CacheInterceptor的总体流程大致是: