OkHttp框架设计<一>---http家族史、OkHttp源码分析、拦截器原理

关于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()方法的参数,那接下来要干嘛想都不用想嘛:

关于拦截器链发起请求的流程先不管,之后再分析,先来对上面整个同步和异步的请求流程做个总结:

高清大图如:https://files.cnblogs.com/files/webor2006/okhttp%E4%B8%BB%E6%B5%81%E7%A8%8B%E6%97%B6%E5%BA%8F%E5%9B%BE.jpg.zip

拦截器机制剖析:

对于上面的流程分析中对于拦截器这块请求细节一带而过了,接下来则再来挼一挼它的整个流程:

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底层的连接是通过啥方式来实现的,这块下次继续。

原文地址:https://www.cnblogs.com/webor2006/p/12362710.html