关于Okhttp在之前有过一篇https://www.cnblogs.com/webor2006/p/10513950.html源码的解读,这里准备再对它进行温故知新,并最终手写整个OkHttp拦截链这块的逻辑,巩固再巩固。
http家族史【了解】:
先来巩固下基础,毕境OkHttp是一个网络框架。
网络分成模型:
上面了解既可,关于网络分成的一个原因之前在这篇有写过:https://www.cnblogs.com/webor2006/p/10362197.html
OSI各层解释:
各层对应的设备:
各层对应协议:
这个图还是有点用,能够清楚知道我们常见的一些协议是处于哪一层的。
TCP/IP 三次握手和四个挥手:
这个有时可能面试会问到,了解一下。
HTTP 1.1:
建立在TCP协议之上的”超文本传输协议”(HyperText Transfer Protocol),关于这块的之前也已经研究过了:https://www.cnblogs.com/webor2006/p/10324182.html
HTTPS:
HTTP1.x在传输数据时,所有传输的内容都是明文,无法保证数据的安全性。
网景在1994年创建了HTTPS,HTTPS就是安全版的HTTP。
在通讯时多了一个SSL握手的过程:
具体握手过程大致过程如下:
具体整个Https的通信原理之前也有研究过:https://www.cnblogs.com/webor2006/p/10362197.html
当然对于Https的掌握肯定不是这么简单的,待之后再找个时间深入研究一下,这块在面试时也偶尔会被问到的。
OkHttp源码再次梳理:
接下来则正式入进OkHttp的探究。
OkHttp版本选择:
先上官网瞅一下如今最新的版本:
但是!!这次分析肯定不是基于最新版本,为啥?
不过从侧面来看Kotlin这门语言真的是越来越重要了,所以搞Android的学好Koltin势在必得,那学习选哪个版本呢?
当然是选3.x这个版本喽,这里选择3.14.2这个版本。
OkHttp简单使用:
在正式源码分析之前,还是回顾一下它的简单使用,分为同步和异步,人人皆知的事,就不多说了,直接上代码:
异步使用:
其简单使用流程如下:
这块简单过一下,接下来则重点就是分析它的源码了。
主流程源码梳理:
初始化OkHttpClient:
先来分析同步的情况:
我们知道它里面用了经典的构建者模式,这里在默认构中实例化了Builder,看后瞅一下这个Builder构造里面做了啥?
最终再将此Builder中的数据再一一赋值给当前类的成员变量,如下:
其中这个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:
又是经典的建造者模式,大致瞅一下:
创建Call:
发起同步请求:
其实分发器的作用是用来管理请求的,瞅一下它里面定义的成员变量就晓得了:
而在请求前与请求后执行其实就是改变里面的状态,如下:
接下来到最最核心的东东了,也是OkHttp框架的设计之魂,也是最终要手写来实现的功能,闪亮让它登场:
关于拦截器的细节下一次再来分析,这里以全局的流程为重,先忽略细节,现在只要知道经过这个拦截器链之后reponse就返回了,整个请求就结了。
发起异步请求:
基于上同步差不多,这里只分析跟同步不一样的,当然就是发起请求这块喽:
此时分发器又出现了,然后传了一个AsyncCall对像,一看不是一个实现了Runnable的类:
也就是最终会执行:
接下来线程的发起肯定是在分发器中的enqueue()方法中:
然后这个线程池作为AsyncCall.executeOn()方法的参数,那接下来要干嘛想都不用想嘛:
关于拦截器链发起请求的流程先不管,之后再分析,先来对上面整个同步和异步的请求流程做个总结:
拦截器机制剖析:
对于上面的流程分析中对于拦截器这块请求细节一带而过了,接下来则再来挼一挼它的整个流程:
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); } } }
上面则是整个拦截器链方法的代码,不是很多,但是理解起来不是很容易,有多个拦截器组成,下面分析一下:
拦截器【由于以前都详细分析过了,这里先暂且过一下,重点是分析整个拦截器链的流程,为手写做准备】:
用户自定义应用拦截器:
而它则是在我们生成OkHttpClient时添加的,实际是用得最多的,拿它做日志打印,头信息处理等等,使用如下:
它有啥用?看一下它的请求时机就知道了,它是在我们发起请求之前的一个自定义拦截器,所以一般可以搞一些请求前的数据处理。
RetryAndFollowUpInterceptor:错误、重定向拦截器
关于它这里就不细看了,之前已经分析过:https://www.cnblogs.com/webor2006/p/10513950.html
BridgeInterceptor:桥接拦截器,桥接应用层与网络层,添加必要的头
它也略过了,其中关于gzip的处理就是在这个拦截器中处理的,面试时有可能会问到,如下:
CacheInterceptor:缓存处理,Last-Modified、ETag、DiskLruCache等
贴一个关键请求头的代码:
也就是根据缓存相关的请求头来做一些缓存处理,细节也略过。
ConnectInterceptor:连接拦截器
关于它的具体下次再来细分,总之这一步骤会和服务端建立socket通信,也就是其实OkHttp底层是通过Socket来实现的。
用户自定义的网络拦截器:
CallServerInterceptor:真正访问服务器的拦截器
这里就真正的会发起跟服务器的具体通信,最终返回Resonse了,这里也不多说了。
拦截器链原理:
此时有个细节需要注意,传了一个index=0,很显然会先取第一个拦截器进行处理,那么下面来看一下整个链式的调用过程,直接来看一下它的procced方法:
看具体子类:
这里又创建了一个拦截器链对象,但是跟之前的不同的是:
而包装了之后,接下来则会真正取出index的拦截器,然后再执行这个拦截器的intercept方法了,然后将新包装的拦截器链又传给这个在处理的拦截器的方法了:
此时调用会转到第一个拦截器了:
此时又回到了拦截器链了,同样的先包装index+1=2的新拦截器链,然后取出当前index=1的链接器进行调用:
接着就取出第二个拦截器开始处理了:
然后又用同样的套路:
其它链接的流程也类似就不一一分析了,直到最后一个拦截器执行:
在最后一个拦截器中可以发现,并没有责任链procced的代码了,而是处理完之后就返回response了,很简单,因为整个请求链条执行完了,当然不需要再往下链了,此时就得往上一层层返,最终整个拦截器链的response就返回了。关于整个链式的过程之后会手动完整的来敲一遍的,目前了解整个的链式调用的关系既可,等手动自己实现一遍之后,到那时对于OkHttp的拦截器链这块的东东就彻底的给掌握了,另外这里对于各个具体的拦截器只是一带而过了,因为不想重复再看了,之前对这块也已经详细研究过了,不过有一个非常核心的拦截器需要细看一下,那就是ConnectInterceptor,为啥?就是要看OkHttp底层的连接是通过啥方式来实现的,这块下次继续。