okHttp3

OkHttp3

Google在android 4.4以后已替换掉HttpURLConnection作为默认的HTTP连接,6.0以后在Android sdk已经移除了httpClient,既然Android系统都用了OkHttp那么,它有什么优势呢?

  • 支持SPDY、Http2
  • 使用GZIP压缩下载内容,且压缩操作对用户是透明的
  • 利用响应缓存来避免重复的网络请求
  • 能从常见的连接问题当中恢复
  • 最佳请求路线选择
  • Socket复用
  • 拦截器

请求流程

先上一张大致类图

Untitled

在来一张时序图

okhttp-request-sequence

上面的两张图,是我根据OKHttp3大致请求流程画出来的类图和时序图,下面介绍下我个人认为在okhttp3请求过程中能借鉴的涉及方案

Intercept

在上述流程图中,我们可以看到Intercept在一个请求流程中占据了很大的比重,那么它是个什么东西了?先来二张盗图

Intercept扮演的角色就是劫持,在请求前劫持一下对构造的Request进行处理,在获取到请求完成后劫持一次对返回的Response进行额外的处理,所以每个继承于Intercept接口的类在整个请求流程中都分别对RequestResponse做出了劫持。以proceed方法作为分割,在调用proceed之前是对Request做出劫持,在调用proceed后对Response做出了劫持。

这里我联想到了动态代理装饰设计模式,貌似这两种方案和上面有点相似,但感觉OkHttp3中的这种处理方式在这里是最佳的。下面看看请求流程中涉及到了哪些Intercept分别干了什么

RetryAndFollowUpInterceptor

  1. 创建StreamAllocation对象,为后面流程的执行准备条件。
  2. 处理重定向的HTTP响应。
  3. 错误恢复。

BridgeInterceptor

  1. 请求前,补全一些http header
  2. 请求后,保存Cookie。如果服务器返回的响应的content是以gzip压缩过的,则会先进行解压缩

CacheInterceptor

  1. 请求发送阶段,主要是尝试从cache中获取响应,获取成功的话,且响应可用未过期,从缓存中获取Response,响应会被直接返回
  2. 获取到响应之后,若需要缓存的,则缓存起来

ConnectInterceptor
建立与服务器之间的连接,但这个事情它主要是委托给StreamAllocation来完成的

CallServerInterceptor
处理IO,与服务器进行数据交换。对后续Interceptor的执行的影响:为Interceptor链中的最后一个Interceptor,没有后续Interceptor

想要详细看看上述涉及到的Interceptor类的作用请看OkHttp3 HTTP请求执行流程分析

连接池

OkHttp3将客户端与服务器之间的连接抽象为Connection/RealConnection,为了管理这些连接的复用而设计了ConnectionPool。共享相同Address的请求可以复用连接,ConnectionPool实现了哪些连接保持打开状态以备后用的策略。

ConnectionPool这个类中的核心就是一个装有RealConnection的双端队列Deque<RealConnection>,顺序容器

我们做项目的时候一般也有类似需求,我们知道要个管理者,但是不知道怎么去设计这个管理者,下面我们来看看OKHttp怎么做的

void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (!cleanupRunning) {
        cleanupRunning = true;
        executor.execute(cleanupRunnable);
    }
    connections.add(connection);
}
  1. 检查当前是否有必要,要清理连接
  2. 若有必要,则执行cleanupRunnable任务
  3. 若没有必要,则直接加入队列

千万要记得同步,给put操作和clean操作加上同步锁

遍历队列中的所有 RealConnection 寻找同时满足如下三个条件的RealConnection

for (RealConnection connection : connections) {
    if (connection.allocations.size() < connection.allocationLimit
    && address.equals(connection.route().address)
    && !connection.noNewStreams) {
        streamAllocation.acquire(connection);
        return connection;
    }
}
  1. RealConnection的allocations的数量小于allocationLimit每个allocation代表在该RealConnection上正在执行的一个请求。这个条件用于控制相同连接上,同一时间执行的并发请求的个数。对于HTTP/2连接而言,allocationLimit限制是在连接建立阶段由双方协商的。对于HTTP或HTTPS连接而言,这个值则总是1
  2. RealConnectionaddress 与传入的 Address 参数相等
  3. RealConnection还有可分配的Stream(这是Http2的概念)。对于HTTP或HTTPS而言,不能同时在相同的连接上执行多个请求
清理

清理操作时在cleanRunnable中的cleanup方法来完成的,处理了如下几种情况

  • 找到一个处于空闲状态的RealConnection,且该RealConnection处于空闲状态的时间超出了设置的保活时间,或者当前ConnectionPool中处于空闲状态的连接数超出了设置的最大空闲连接数,将该RealConnectionconnections中移除,并关闭该RealConnection关联的底层socket,然后返回0,以此请求cleanupRunnable立即再次检查所有的连接。
  • 找到一个处于空闲状态的RealConnection,但该RealConnection处于空闲状态的时间尚未超出设置的保活时间,且当前ConnectionPool中处于空闲状态的连接数尚未超出设置的最大空闲连接数,则返回保活时间与该RealConnection处于空闲状态的时间之间的差值,请求cleanupRunnable等待这么长一段时间之后再次检查所有的连接。
  • 没有找到处于空闲状态的连接,但找到了使用中的连接,则返回保活时间,请求cleanupRunnable等待这么长一段时间之后再次检查所有的连接。
  • 没有找到处于空闲状态的连接,也没有找到使用中的连接,也就意味着连接池中尚没有任何连接,则将 cleanupRunning 置为false,并返回 -1,请求 cleanupRunnable 退出。

总结下连接池的作用:

  1. 通过参数设置连接的最大数,和最长存活时间
  2. 通过上面参数,维护连接池的生命周期

疑惑

为什么对于设置了HTTP代理,且安全的连接 (SSL) 要建立隧道连接,隧道连接是个啥东东?
HTTP 代理原理及实现(一)

隧道连接要解决的问题是即要用代理服务器,又要保证安全性的问题HTTP 代理原理及实现(一)这篇文章中说了应用数据要等到 TLS 握手成功之后通过 Application Data 协议传输,代理服务器由于无法得知用于流量加密的 master-secret,无法解密数据,所以这种方式并没有增加不安全性,肯定有人问为什么代理服务器无法得知master-secret,那么你就要了解TLS/SSL协议了

我们客户端开发什么时候要用到代理了?一般是给内部员工用的APP,保证数据安全用了代理

参考文献

OkHttp3源码分析[综述]
OkHttp3源码分析[复用连接池]
OkHttp3源码分析[缓存策略]
OkHttp3源码分析[DiskLruCache]
OkHttp3源码分析[任务队列]

OkHttp3 HTTP请求执行流程分析
OkHttp3的连接池及连接建立过程分析
OkHttp3中的代理与路由

OkHttp3中Interceptor的使用心得

okhttp、retrofit、android-async-http、volley对比

发表评论

电子邮件地址不会被公开。 必填项已用*标注

返回主页看更多
狠狠的抽打博主 支付宝 扫一扫