关于Okhttp在之前有过一篇https://www.cnblogs.com/webor2006/p/10513950.html源码的解读,这里准备再对它进行温故知新,并最终手写整个OkHttp拦截链这块的逻辑,巩固再巩固。
http家族史【了解】:
先来巩固下基础,毕境OkHttp是一个网络框架。
网络分成模型:
![](https://img-blog.csdnimg.cn/img_convert/bc8a0e1931adc77904f2f9eae779b2fc.png)
上面了解既可,关于网络分成的一个原因之前在这篇有写过:https://www.cnblogs.com/webor2006/p/10362197.html
OSI各层解释:
![](https://img-blog.csdnimg.cn/img_convert/f70e75214ffeccf7365961be3cfd6e61.png)
各层对应的设备:
![](https://img-blog.csdnimg.cn/img_convert/56f98af1965af43dea950151f04e2cd1.png)
各层对应协议:
![](https://img-blog.csdnimg.cn/img_convert/05e15e77d3f82aadb7200f5a843d2c32.png)
这个图还是有点用,能够清楚知道我们常见的一些协议是处于哪一层的。
TCP/IP 三次握手和四个挥手:
![](https://img-blog.csdnimg.cn/img_convert/2a8e0a54c525f7ecb57ae82dc8ea0b86.png)
![](https://img-blog.csdnimg.cn/img_convert/6427acc722e92ccb42bd5e32ad1c7b84.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://img-blog.csdnimg.cn/img_convert/aed65ff52b734918eadbe11a290a336d.png)
具体握手过程大致过程如下:
![](https://img-blog.csdnimg.cn/img_convert/6a167dbf1ef69d0f722985b6ad2c9358.png)
具体整个Https的通信原理之前也有研究过:https://www.cnblogs.com/webor2006/p/10362197.html
当然对于Https的掌握肯定不是这么简单的,待之后再找个时间深入研究一下,这块在面试时也偶尔会被问到的。
OkHttp源码再次梳理:
接下来则正式入进OkHttp的探究。
OkHttp版本选择:
先上官网瞅一下如今最新的版本:
![](https://img-blog.csdnimg.cn/img_convert/c789bfea56aaaf59e52f00e305573e69.png)
但是!!这次分析肯定不是基于最新版本,为啥?
![](https://img-blog.csdnimg.cn/img_convert/553474bae0374e1f742b6fe4cf6400bf.png)
不过从侧面来看Kotlin这门语言真的是越来越重要了,所以搞Android的学好Koltin势在必得,那学习选哪个版本呢?
![](https://img-blog.csdnimg.cn/img_convert/dd745db45ef4ad7f401f64521d1df2f2.png)
当然是选3.x这个版本喽,这里选择3.14.2这个版本。
OkHttp简单使用:
在正式源码分析之前,还是回顾一下它的简单使用,分为同步和异步,人人皆知的事,就不多说了,直接上代码:
![](https://img-blog.csdnimg.cn/img_convert/99fc658690b9999f10418ac864ad935d.png)
![](https://img-blog.csdnimg.cn/img_convert/7e26c4c4a5ae2cbefa698a049a7fc471.png)
异步使用:
![](https://img-blog.csdnimg.cn/img_convert/934fb41178eb8627246ff786d6e705bd.png)
其简单使用流程如下:
![](https://img-blog.csdnimg.cn/img_convert/4c7ad40d4ecc174c8e8429888e998eeb.png)
这块简单过一下,接下来则重点就是分析它的源码了。
主流程源码梳理:
初始化OkHttpClient:
先来分析同步的情况:
![](https://img-blog.csdnimg.cn/img_convert/a79858eb0715514af82afefbaa002716.png)
![](https://img-blog.csdnimg.cn/img_convert/75ec4a1672e50797d0b884c48bc76ce5.png)
我们知道它里面用了经典的构建者模式,这里在默认构中实例化了Builder,看后瞅一下这个Builder构造里面做了啥?
![](https://img-blog.csdnimg.cn/img_convert/5acefbf43520b96e848df6b6bf68257d.png)
最终再将此Builder中的数据再一一赋值给当前类的成员变量,如下:
![](https://img-blog.csdnimg.cn/img_convert/dd6ca70f1f665bfd3e641cbb86d0c13e.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://img-blog.csdnimg.cn/img_convert/17a260a04663d206fd7f222d59f8c5fd.png)
又是经典的建造者模式,大致瞅一下:
![](https://img-blog.csdnimg.cn/img_convert/d1d925106b8bec0c90fb5d0a16010a3d.png)
创建Call:
![](https://img-blog.csdnimg.cn/img_convert/1ea9f7f22317e17ae54507f30e6bcd3d.png)
![](https://img-blog.csdnimg.cn/img_convert/b3ce9e578fc59a33d2b12da62ca4b513.png)
![](https://img-blog.csdnimg.cn/img_convert/1486f47bef714aba6cddf851770f891f.png)
发起同步请求:
![](https://img-blog.csdnimg.cn/img_convert/fa1e7256577861bb428253fee4a0dd4a.png)
![](https://img-blog.csdnimg.cn/img_convert/74bed08a2e9e8b5c55aabfd843cd5ca0.png)
![](https://img-blog.csdnimg.cn/img_convert/081bf5224a013e3ec452f7a9135e7102.png)
其实分发器的作用是用来管理请求的,瞅一下它里面定义的成员变量就晓得了:
![](https://img-blog.csdnimg.cn/img_convert/35b915e15039567ba114210a42f299cb.png)
而在请求前与请求后执行其实就是改变里面的状态,如下:
![](https://img-blog.csdnimg.cn/img_convert/ee3133c1f3753542af66aac50b5c93b0.png)
![](https://img-blog.csdnimg.cn/img_convert/6bca36472a2fd498cee163754993bb86.png)
接下来到最最核心的东东了,也是OkHttp框架的设计之魂,也是最终要手写来实现的功能,闪亮让它登场:
![](https://img-blog.csdnimg.cn/img_convert/64b2dd29e0d3da66b6327d9cc41c7edf.png)
![](https://img-blog.csdnimg.cn/img_convert/331acebce279678268be40155bc941ee.png)
关于拦截器的细节下一次再来分析,这里以全局的流程为重,先忽略细节,现在只要知道经过这个拦截器链之后reponse就返回了,整个请求就结了。
发起异步请求:
基于上同步差不多,这里只分析跟同步不一样的,当然就是发起请求这块喽:
![](https://img-blog.csdnimg.cn/img_convert/b96af05a8eac6ea1126157fb508bb843.png)
![](https://img-blog.csdnimg.cn/img_convert/158074cd52a40bee0c7673d1805ecc98.png)
此时分发器又出现了,然后传了一个AsyncCall对像,一看不是一个实现了Runnable的类:
![](https://img-blog.csdnimg.cn/img_convert/b522952f5fdc7b232be9c4e2a619cde3.png)
![](https://img-blog.csdnimg.cn/img_convert/1805d5e01557f5cc91d160ef19873a15.png)
也就是最终会执行:
![](https://img-blog.csdnimg.cn/img_convert/144fad3d67ff5cd81cc2b13aea0b63ca.png)
接下来线程的发起肯定是在分发器中的enqueue()方法中:
![](https://img-blog.csdnimg.cn/img_convert/185a234835d870ad961740e5258806fd.png)
![](https://img-blog.csdnimg.cn/img_convert/606babf44f5f65f7582e2137f1e12f6a.png)
![](https://img-blog.csdnimg.cn/img_convert/43a359d6b1016763c98c55665dd9e3e1.png)
然后这个线程池作为AsyncCall.executeOn()方法的参数,那接下来要干嘛想都不用想嘛:
![](https://img-blog.csdnimg.cn/img_convert/ddc1a70424fd0799b96a3780e0500529.png)
![](https://img-blog.csdnimg.cn/img_convert/d37df9db5d8100948bbd9bcdf8d809b0.png)
关于拦截器链发起请求的流程先不管,之后再分析,先来对上面整个同步和异步的请求流程做个总结:
![](https://img-blog.csdnimg.cn/img_convert/5f29da655a2a1c6671c04c3a107fdfd8.png)
拦截器机制剖析:
对于上面的流程分析中对于拦截器这块请求细节一带而过了,接下来则再来挼一挼它的整个流程:
![](https://img-blog.csdnimg.cn/img_convert/a7775d949224b037d0efde88d36cfc16.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://img-blog.csdnimg.cn/img_convert/ad6f0fca67d370e1a129ec15a38493d2.png)
而它则是在我们生成OkHttpClient时添加的,实际是用得最多的,拿它做日志打印,头信息处理等等,使用如下:
![](https://img-blog.csdnimg.cn/img_convert/4d3a18108ce9ef76a1c4c714e530aec5.png)
它有啥用?看一下它的请求时机就知道了,它是在我们发起请求之前的一个自定义拦截器,所以一般可以搞一些请求前的数据处理。
RetryAndFollowUpInterceptor:错误、重定向拦截器
关于它这里就不细看了,之前已经分析过:https://www.cnblogs.com/webor2006/p/10513950.html
BridgeInterceptor:桥接拦截器,桥接应用层与网络层,添加必要的头
它也略过了,其中关于gzip的处理就是在这个拦截器中处理的,面试时有可能会问到,如下:
![](https://img-blog.csdnimg.cn/img_convert/9d7074133ff3204e3a71163c66d8e5c0.png)
CacheInterceptor:缓存处理,Last-Modified、ETag、DiskLruCache等
贴一个关键请求头的代码:
![](https://img-blog.csdnimg.cn/img_convert/042580d0fc53770790eb80ac166cdea5.png)
也就是根据缓存相关的请求头来做一些缓存处理,细节也略过。
ConnectInterceptor:连接拦截器
关于它的具体下次再来细分,总之这一步骤会和服务端建立socket通信,也就是其实OkHttp底层是通过Socket来实现的。
用户自定义的网络拦截器:
![](https://img-blog.csdnimg.cn/img_convert/e959c6d752db4dc958fa124201a1f91f.png)
CallServerInterceptor:真正访问服务器的拦截器
这里就真正的会发起跟服务器的具体通信,最终返回Resonse了,这里也不多说了。
拦截器链原理:
![](https://img-blog.csdnimg.cn/img_convert/6885d20b1c32b0fdb9a60200e50b5599.png)
此时有个细节需要注意,传了一个index=0,很显然会先取第一个拦截器进行处理,那么下面来看一下整个链式的调用过程,直接来看一下它的procced方法:
![](https://img-blog.csdnimg.cn/img_convert/3828bb458839e10817f5a026aef0e34c.png)
看具体子类:
![](https://img-blog.csdnimg.cn/img_convert/5bce58ec5a5aabb2dddb20716d0d6758.png)
这里又创建了一个拦截器链对象,但是跟之前的不同的是:
![](https://img-blog.csdnimg.cn/img_convert/0f4091633be6584b287f725144c4ff63.png)
而包装了之后,接下来则会真正取出index的拦截器,然后再执行这个拦截器的intercept方法了,然后将新包装的拦截器链又传给这个在处理的拦截器的方法了:
![](https://img-blog.csdnimg.cn/img_convert/9629358f551537a261de49c580b9e4d8.png)
此时调用会转到第一个拦截器了:
![](https://img-blog.csdnimg.cn/img_convert/0ba21ab69aff238034bc9e404fb2b1bc.png)
![](https://img-blog.csdnimg.cn/img_convert/e8e808431a6e7ec72c727170b67f2beb.png)
此时又回到了拦截器链了,同样的先包装index+1=2的新拦截器链,然后取出当前index=1的链接器进行调用:
![](https://img-blog.csdnimg.cn/img_convert/5dd6c9810a4ccfd2a2f065298a3f3efb.png)
接着就取出第二个拦截器开始处理了:
![](https://img-blog.csdnimg.cn/img_convert/c3ead351d2b2866ca50a3ea438e146fb.png)
然后又用同样的套路:
![](https://img-blog.csdnimg.cn/img_convert/e8344383ab32702a8156a77dad19e703.png)
其它链接的流程也类似就不一一分析了,直到最后一个拦截器执行:
![](https://img-blog.csdnimg.cn/img_convert/485f0fd1f5806990ed5697e7d5db3435.png)
在最后一个拦截器中可以发现,并没有责任链procced的代码了,而是处理完之后就返回response了,很简单,因为整个请求链条执行完了,当然不需要再往下链了,此时就得往上一层层返,最终整个拦截器链的response就返回了。关于整个链式的过程之后会手动完整的来敲一遍的,目前了解整个的链式调用的关系既可,等手动自己实现一遍之后,到那时对于OkHttp的拦截器链这块的东东就彻底的给掌握了,另外这里对于各个具体的拦截器只是一带而过了,因为不想重复再看了,之前对这块也已经详细研究过了,不过有一个非常核心的拦截器需要细看一下,那就是ConnectInterceptor,为啥?就是要看OkHttp底层的连接是通过啥方式来实现的,这块下次继续。