关于Okhttp在之前有过一篇https://www.cnblogs.com/webor2006/p/10513950.html源码的解读,这里准备再对它进行温故知新,并最终手写整个OkHttp拦截链这块的逻辑,巩固再巩固。
http家族史【了解】:
先来巩固下基础,毕境OkHttp是一个网络框架。
网络分成模型:
![](https://i-blog.csdnimg.cn/blog_migrate/4b256dd7dedf7fc3fb44dc39c2eacfc6.png)
上面了解既可,关于网络分成的一个原因之前在这篇有写过:https://www.cnblogs.com/webor2006/p/10362197.html
OSI各层解释:
![](https://i-blog.csdnimg.cn/blog_migrate/ff4757af61f823fe4c2c0c15e39e75f4.png)
各层对应的设备:
![](https://i-blog.csdnimg.cn/blog_migrate/0ae0b636b9ddba10ab8de2b20d277f42.png)
各层对应协议:
![](https://i-blog.csdnimg.cn/blog_migrate/deae4cf4c0cfeb1aa2929f0fbca395d1.png)
这个图还是有点用,能够清楚知道我们常见的一些协议是处于哪一层的。
TCP/IP 三次握手和四个挥手:
![](https://i-blog.csdnimg.cn/blog_migrate/3d3c5d2ed2634d61d6e3cf272235ef21.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9ec95169ebdbf63919cdcbb64e601a28.png)
这个有时可能面试会问到,了解一下。
HTTP 1.1:
建立在TCP协议之上的”超文本传输协议”(HyperText Transfer Protocol),关于这块的之前也已经研究过了:https://www.cnblogs.com/webor2006/p/10324182.html
HTTPS:
HTTP1.x在传输数据时,所有传输的内容都是明文,无法保证数据的安全性。
网景在1994年创建了HTTPS,HTTPS就是安全版的HTTP。
在通讯时多了一个SSL握手的过程:
![](https://i-blog.csdnimg.cn/blog_migrate/5d6117bd602ebb2495499ce5fdf248f7.png)
具体握手过程大致过程如下:
![](https://i-blog.csdnimg.cn/blog_migrate/5df9c5a029199430570affeffe3e53ff.png)
具体整个Https的通信原理之前也有研究过:https://www.cnblogs.com/webor2006/p/10362197.html
当然对于Https的掌握肯定不是这么简单的,待之后再找个时间深入研究一下,这块在面试时也偶尔会被问到的。
OkHttp源码再次梳理:
接下来则正式入进OkHttp的探究。
OkHttp版本选择:
先上官网瞅一下如今最新的版本:
![](https://i-blog.csdnimg.cn/blog_migrate/cca2dcef92d246185c15a1f9059eaa5d.png)
但是!!这次分析肯定不是基于最新版本,为啥?
![](https://i-blog.csdnimg.cn/blog_migrate/4804e2e1835611766b5fa011f8d9a9a4.png)
不过从侧面来看Kotlin这门语言真的是越来越重要了,所以搞Android的学好Koltin势在必得,那学习选哪个版本呢?
![](https://i-blog.csdnimg.cn/blog_migrate/6c77785472dd4989d06b0fc48f13dbca.png)
当然是选3.x这个版本喽,这里选择3.14.2这个版本。
OkHttp简单使用:
在正式源码分析之前,还是回顾一下它的简单使用,分为同步和异步,人人皆知的事,就不多说了,直接上代码:
![](https://i-blog.csdnimg.cn/blog_migrate/1c7ea697a905463520f876bb9562bb21.png)
![](https://i-blog.csdnimg.cn/blog_migrate/dd8005f16c5b5d0b15ac1dd45bf930d2.png)
异步使用:
![](https://i-blog.csdnimg.cn/blog_migrate/18a4a9f88e5e6ef3383247e39256a62c.png)
其简单使用流程如下:
![](https://i-blog.csdnimg.cn/blog_migrate/b605d5543ceab1a03f9f4a24d3f9d8b3.png)
这块简单过一下,接下来则重点就是分析它的源码了。
主流程源码梳理:
初始化OkHttpClient:
先来分析同步的情况:
![](https://i-blog.csdnimg.cn/blog_migrate/56cc1f70b1d992b6eb05d1398d5ff2b8.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7476369de3708cadadcf746db747c26c.png)
我们知道它里面用了经典的构建者模式,这里在默认构中实例化了Builder,看后瞅一下这个Builder构造里面做了啥?
![](https://i-blog.csdnimg.cn/blog_migrate/5dfb2517da2341c42f6d2044edd6a1fe.png)
最终再将此Builder中的数据再一一赋值给当前类的成员变量,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/841cbc45bb6a3cbdfd5c6bd166361ab7.png)
其中这个Builder中定义了很多的参数,下面就不一一看了,以代码注释的方式贴出来供没事回来复习用:
public static final class Builder {
Dispatcher dispatcher; //调度器
/**
* 代理类,默认有三种代理模式DIRECT(直连),HTTP(http代理),SOCKS(socks代理)
*/
@Nullable Proxy proxy;
/**
* 协议集合,协议类,用来表示使用的协议版本,比如`http/1.0,`http/1.1,`spdy/3.1,`h2等
*/
List<Protocol> protocols;
/**
* 连接规范,用于配置Socket连接层。对于HTTPS,还能配置安全传输层协议(TLS)版本和密码套件
*/
List<ConnectionSpec> connectionSpecs;
//拦截器,可以监听、重写和重试请求等
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
EventListener.Factory eventListenerFactory;
/**
* 代理选择类,默认不使用代理,即使用直连方式,当然,我们可以自定义配置,
* 以指定URI使用某种代理,类似代理软件的PAC功能
*/
ProxySelector proxySelector;
//Cookie的保存获取
CookieJar cookieJar;
/**
* 缓存类,内部使用了DiskLruCache来进行管理缓存,匹配缓存的机制不仅仅是根据url,
* 而且会根据请求方法和请求头来验证是否可以响应缓存。此外,仅支持GET请求的缓存
*/
@Nullable Cache cache;
//内置缓存
@Nullable InternalCache internalCache;
//Socket的抽象创建工厂,通过createSocket来创建Socket
SocketFactory socketFactory;
/**
* 安全套接层工厂,HTTPS相关,用于创建SSLSocket。一般配置HTTPS证书信任问题都需要从这里着手。
* 对于不受信任的证书一般会提示
* javax.net.ssl.SSLHandshakeException异常。
*/
@Nullable SSLSocketFactory sslSocketFactory;
/**
* 证书链清洁器,HTTPS相关,用于从[Java]的TLS API构建的原始数组中统计有效的证书链,
* 然后清除跟TLS握手不相关的证书,提取可信任的证书以便可以受益于证书锁机制。
*/
@Nullable CertificateChainCleaner certificateChainCleaner;
/**
* 主机名验证器,与HTTPS中的SSL相关,当握手时如果URL的主机名
* 不是可识别的主机,就会要求进行主机名验证
*/
HostnameVerifier hostnameVerifier;
/**
* 证书锁,HTTPS相关,用于约束哪些证书可以被信任,可以防止一些已知或未知
* 的中间证书机构带来的攻击行为。如果所有证书都不被信任将抛出SSLPeerUnverifiedException异常。
*/
CertificatePinner certificatePinner;
/**
* 身份认证器,当连接提示未授权时,可以通过重新设置请求头来响应一个
* 新的Request。状态码401表示远程服务器请求授权,407表示代理服务器请求授权。
* 该认证器在需要时会被RetryAndFollowUpInterceptor触发。
*/
Authenticator proxyAuthenticator;
Authenticator authenticator;
/**
* 连接池
*
* 我们通常将一个客户端和服务端和连接抽象为一个 connection,
* 而每一个 connection 都会被存放在 connectionPool 中,由它进行统一的管理,
* 例如有一个相同的 http 请求产生时,connection 就可以得到复用
*/
ConnectionPool connectionPool;
//域名解析系统
Dns dns;
//是否遵循SSL重定向
boolean followSslRedirects;
//是否重定向
boolean followRedirects;
//失败是否重新连接
boolean retryOnConnectionFailure;
//回调超时
int callTimeout;
//连接超时
int connectTimeout;
//读取超时
int readTimeout;
//写入超时
int writeTimeout;
//与WebSocket有关,为了保持长连接,我们必须间隔一段时间发送一个ping指令进行保活;
int pingInterval;
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
/**
* 代理选择类,默认不使用代理,即使用直连方式,当然,我们可以自定义配置,以指定URI使用某种代理,类似代理软件的PAC功能
*/
proxySelector = ProxySelector.getDefault();
if (proxySelector == null) {
proxySelector = new NullProxySelector();
}
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
callTimeout = 0;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
Builder(OkHttpClient okHttpClient) {
this.dispatcher = okHttpClient.dispatcher;
this.proxy = okHttpClient.proxy;
this.protocols = okHttpClient.protocols;
.....
}
}
初始化Request:
![](https://i-blog.csdnimg.cn/blog_migrate/ec28931b26095c58e3ecd15f05ff98f1.png)
又是经典的建造者模式,大致瞅一下:
![](https://i-blog.csdnimg.cn/blog_migrate/3bfa45ff149669efa0c6b72dae76ba6c.png)
创建Call:
![](https://i-blog.csdnimg.cn/blog_migrate/2b5bc70e40be4a2b06712ea3f69a386d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/8da2bbfb7d7f4b34f82363601e532aed.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6c843bb12a2d0ec9726bb56be2363524.png)
发起同步请求:
![](https://i-blog.csdnimg.cn/blog_migrate/5bcac6716695290d262a55aaa291345b.png)
![](https://i-blog.csdnimg.cn/blog_migrate/74adc55b14e0d0047d4fecc04e51c5c6.png)
![](https://i-blog.csdnimg.cn/blog_migrate/99c383bcce2acd24e6924e1878f126f0.png)
其实分发器的作用是用来管理请求的,瞅一下它里面定义的成员变量就晓得了:
![](https://i-blog.csdnimg.cn/blog_migrate/fadedf421c93bd982a7e686ae479901a.png)
而在请求前与请求后执行其实就是改变里面的状态,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/9cbd5c434d6ae862ba5bf298277e1edf.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d436c26f7d664277f3d910e00dc58789.png)
接下来到最最核心的东东了,也是OkHttp框架的设计之魂,也是最终要手写来实现的功能,闪亮让它登场:
![](https://i-blog.csdnimg.cn/blog_migrate/0126d206ee7c1a7845ef64b56c915b49.png)
![](https://i-blog.csdnimg.cn/blog_migrate/2bbf56971e9b519638f0c204a86f81e8.png)
关于拦截器的细节下一次再来分析,这里以全局的流程为重,先忽略细节,现在只要知道经过这个拦截器链之后reponse就返回了,整个请求就结了。
发起异步请求:
基于上同步差不多,这里只分析跟同步不一样的,当然就是发起请求这块喽:
![](https://i-blog.csdnimg.cn/blog_migrate/1414a7108ed1b2f91cbe424654acebc1.png)
![](https://i-blog.csdnimg.cn/blog_migrate/db7f97d071e63c91c15df97007c0d193.png)
此时分发器又出现了,然后传了一个AsyncCall对像,一看不是一个实现了Runnable的类:
![](https://i-blog.csdnimg.cn/blog_migrate/ef5ca1900ed6067a592afc29d05afda9.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9f935e28ec57ab84763244f28609887f.png)
也就是最终会执行:
![](https://i-blog.csdnimg.cn/blog_migrate/308e2b0c5f2e5f6033189d02120a7bd0.png)
接下来线程的发起肯定是在分发器中的enqueue()方法中:
![](https://i-blog.csdnimg.cn/blog_migrate/a10f8942f365c34f1c1a606914a27bee.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9b53a1d75266dc6452bc586fadd7f628.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7797b7e1950acf149b6c797ef0d474b1.png)
然后这个线程池作为AsyncCall.executeOn()方法的参数,那接下来要干嘛想都不用想嘛:
![](https://i-blog.csdnimg.cn/blog_migrate/e5632d73f1e4d9831e9a43ee6d6b21d8.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4697262a5f45361be03aa8db281cf63c.png)
关于拦截器链发起请求的流程先不管,之后再分析,先来对上面整个同步和异步的请求流程做个总结:
![](https://i-blog.csdnimg.cn/blog_migrate/74f16510628c583388728f59ade877c7.jpeg)
拦截器机制剖析:
对于上面的流程分析中对于拦截器这块请求细节一带而过了,接下来则再来挼一挼它的整个流程:
![](https://i-blog.csdnimg.cn/blog_migrate/749d36a27bc730b1244ce82b98bf4138.png)
Response getResponseWithInterceptorChain() throws IOException {
List<Interceptor> interceptors = new ArrayList();
//用户添加的全局拦截器
interceptors.addAll(this.client.interceptors());
//错误、重定向拦截器
interceptors.add(new RetryAndFollowUpInterceptor(this.client));
//桥接拦截器,桥接应用层与网络层,添加必要的头
interceptors.add(new BridgeInterceptor(this.client.cookieJar()));
//缓存处理,Last-Modified、ETag、DiskLruCache等
interceptors.add(new CacheInterceptor(this.client.internalCache()));
//连接拦截器
interceptors.add(new ConnectInterceptor(this.client));
if (!this.forWebSocket) {
//通过okHttpClient.Builder#addNetworkInterceptor()传进来的拦截器只对非网页的请求生效
interceptors.addAll(this.client.networkInterceptors());
}
//真正访问服务器的拦截器
interceptors.add(new CallServerInterceptor(this.forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
Response response = chain.proceed(originalRequest);
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
上面则是整个拦截器链方法的代码,不是很多,但是理解起来不是很容易,有多个拦截器组成,下面分析一下:
拦截器【由于以前都详细分析过了,这里先暂且过一下,重点是分析整个拦截器链的流程,为手写做准备】:
用户自定义应用拦截器:
![](https://i-blog.csdnimg.cn/blog_migrate/225c9b142d336fbebd1bc4cadb4724a3.png)
而它则是在我们生成OkHttpClient时添加的,实际是用得最多的,拿它做日志打印,头信息处理等等,使用如下:
![](https://i-blog.csdnimg.cn/blog_migrate/5d3ad6b4c2738616175de4c273da08c9.png)
它有啥用?看一下它的请求时机就知道了,它是在我们发起请求之前的一个自定义拦截器,所以一般可以搞一些请求前的数据处理。
RetryAndFollowUpInterceptor:错误、重定向拦截器
关于它这里就不细看了,之前已经分析过:https://www.cnblogs.com/webor2006/p/10513950.html
BridgeInterceptor:桥接拦截器,桥接应用层与网络层,添加必要的头
它也略过了,其中关于gzip的处理就是在这个拦截器中处理的,面试时有可能会问到,如下:
![](https://i-blog.csdnimg.cn/blog_migrate/665e7a33f0ba9101e2fd80bedaff9780.png)
CacheInterceptor:缓存处理,Last-Modified、ETag、DiskLruCache等
贴一个关键请求头的代码:
![](https://i-blog.csdnimg.cn/blog_migrate/f432c97c85b384bb9e99ece88d3c89e0.png)
也就是根据缓存相关的请求头来做一些缓存处理,细节也略过。
ConnectInterceptor:连接拦截器
关于它的具体下次再来细分,总之这一步骤会和服务端建立socket通信,也就是其实OkHttp底层是通过Socket来实现的。
用户自定义的网络拦截器:
![](https://i-blog.csdnimg.cn/blog_migrate/86c524197fc7f66d8683f2b376e90631.png)
CallServerInterceptor:真正访问服务器的拦截器
这里就真正的会发起跟服务器的具体通信,最终返回Resonse了,这里也不多说了。
拦截器链原理:
![](https://i-blog.csdnimg.cn/blog_migrate/d1edce094f2a0fac7b691af78368554b.png)
此时有个细节需要注意,传了一个index=0,很显然会先取第一个拦截器进行处理,那么下面来看一下整个链式的调用过程,直接来看一下它的procced方法:
![](https://i-blog.csdnimg.cn/blog_migrate/4b589a8deeaa9c055590147866f37212.png)
看具体子类:
![](https://i-blog.csdnimg.cn/blog_migrate/26d6c48bad89d0e4f61ed89cfc18822c.png)
这里又创建了一个拦截器链对象,但是跟之前的不同的是:
![](https://i-blog.csdnimg.cn/blog_migrate/704322705671ea9610e98a2e4c773f6c.png)
而包装了之后,接下来则会真正取出index的拦截器,然后再执行这个拦截器的intercept方法了,然后将新包装的拦截器链又传给这个在处理的拦截器的方法了:
![](https://i-blog.csdnimg.cn/blog_migrate/a2997568eb64fe5218496cdc66c78525.png)
此时调用会转到第一个拦截器了:
![](https://i-blog.csdnimg.cn/blog_migrate/813842decbc25c642e111d54c23acde3.png)
![](https://i-blog.csdnimg.cn/blog_migrate/07eeeb501a4898d8bc3010df11c2f88f.png)
此时又回到了拦截器链了,同样的先包装index+1=2的新拦截器链,然后取出当前index=1的链接器进行调用:
![](https://i-blog.csdnimg.cn/blog_migrate/e77b087e525f4bb85ea60f65ce079a47.png)
接着就取出第二个拦截器开始处理了:
![](https://i-blog.csdnimg.cn/blog_migrate/2741ffeeaffdfa8cd6217956a18c47b9.png)
然后又用同样的套路:
![](https://i-blog.csdnimg.cn/blog_migrate/0b82b2ee6423fe71230c4d5f6a8a8a39.png)
其它链接的流程也类似就不一一分析了,直到最后一个拦截器执行:
![](https://i-blog.csdnimg.cn/blog_migrate/f0a72a93bf26d306afd0df8aa1b139c2.png)
在最后一个拦截器中可以发现,并没有责任链procced的代码了,而是处理完之后就返回response了,很简单,因为整个请求链条执行完了,当然不需要再往下链了,此时就得往上一层层返,最终整个拦截器链的response就返回了。关于整个链式的过程之后会手动完整的来敲一遍的,目前了解整个的链式调用的关系既可,等手动自己实现一遍之后,到那时对于OkHttp的拦截器链这块的东东就彻底的给掌握了,另外这里对于各个具体的拦截器只是一带而过了,因为不想重复再看了,之前对这块也已经详细研究过了,不过有一个非常核心的拦截器需要细看一下,那就是ConnectInterceptor,为啥?就是要看OkHttp底层的连接是通过啥方式来实现的,这块下次继续。