这是 OkHttp 深度解析系列文章
OkHttp深度解析(一) : 从一次完整请求看 OkHttp整体架构
OkHttp深度解析(二) : OkHttpClient 你没见过的那些属性
OkHttp深度解析(三) : 拦截器?连接复用&合并?Http2?
OkHttpClient 属性详解
OkHttpClient 中管理的是Okhttp 网络请求中各种参数配置,因此了解它里面的每个参数含义及作用非常重要
class Builder constructor() {
// 基于线程池的管理和分配请求任务的调度器
internal var dispatcher: Dispatcher = Dispatcher()
//连接池,负责批量管理连接对象,功能:查找、创建、复用、存储连接,实际工作对象是 RealConnectionPool
//核心是通过 ConcurrentLinkedQueue 实现的
internal var connectionPool: ConnectionPool = ConnectionPool()
//自定义拦截器,用于添加请求头,修改 URL 打印日志等
internal val interceptors: MutableList<Interceptor> = mutableListOf()
//网络拦截器,主要用于调试服务返回的原始数据
internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
//网络请求过程中各种事件的监听器
internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
//连接失败重试,重试指的是在有其它可行的路线下才会重试,比如多IP主机一个IP失败会重试其它的IP,
internal var retryOnConnectionFailure = true
//认证器,当服务器返回认证失败 401 or 407 时,会回调这个认证器,如果需要刷新Token则需要重写并在回调中处理
internal var authenticator: Authenticator = Authenticator.NONE
//是否重定向
internal var followRedirects = true
//是否允许协议切换的重定向(Http <=> Https)
internal var followSslRedirects = true
//cookieJar表示cookie存储器,用于在客户端保存一些请求状态信息,默认实现是空,移动端一般不使用
internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
//服务端数据的缓存
internal var cache: Cache? = null
//域名解析
internal var dns: Dns = Dns.SYSTEM
//正向代理配置:DIRECT,HTTP,SOCKS
internal var proxy: Proxy? = null
//代理选择器,它的默认配置是:当 proxy 为空时,ProxySelector会返回NullProxySelector,也就是直连
//当 proxy不为空时,NullProxySelector也会返回NullProxySelector,因为起作用的是proxy,也就是ProxySelector属性失效
internal var proxySelector: ProxySelector? = null
//与authenticator相同,当配置的代理服务器要求认证时也会回调authenticate函数
internal var proxyAuthenticator: Authenticator = Authenticator.NONE
//用于创建 Socket 连接的工厂类
internal var socketFactory: SocketFactory = SocketFactory.getDefault()
//用于创建 TLS连接的工厂类
internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
//证书管理器,验证证书有效性,x509 表示证书格式
internal var x509TrustManagerOrNull: X509TrustManager? = null
//连接规范,用于记录支持的TLS协议版本和加密套件(cipherSuites)
//默认连接规范:Https使用MODERN_TLS(最合适的TLS配置),Http 使用明文传输
internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS
//支持的协议 Http 1.0/1.1/2;SPDY(废弃,Http2 的前身);H2_PRIOR_KNOWLEDGE(HTTP2的明文版);QUIC(HTTP3 没有实现只提供拦截器入口)
internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
//主机名验证器,用于验证证书主机名与请求中的主机名是否一致,无特殊要求不可自定义这个属性
internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
//证书固定器,通过硬编码方式将证书固定,也就是除了验证证书合法性之外,还需要确定是指定的那个证书
internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
//证书链清理员,它的作用是:1.操作X509TrustManager 返回证书链列表 2. 清理与TLS握手无关的证书
internal var certificateChainCleaner: CertificateChainCleaner? = null
//整个调用周期的超时时间,默认不限制,但内部的连接,读写有超时限制
internal var callTimeout = 0
internal var connectTimeout = 10_000//连接超时
internal var readTimeout = 10_000//读超时
internal var writeTimeout = 10_000//写超时
internal var pingInterval = 0//http2 和 websocket 的心跳间隔
//websocket 中被压缩的最小的消息大小,默认 1024,即当消息>=1024时会被压缩
internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
//路由数据库,用于记录有故障的路由,便于寻找备用路由
internal var routeDatabase: RouteDatabase? = null
}
重点说明的几个属性:
-
connectionPool连接池的原理:
RealConnectionPool是实际的连接池管理类,它内部使用
ConcurrentLinkedQueue<RealConnection>的来存储连接对象,这表明对于连接池的单个操作是线程安全的,连接池负责 OkHttpClient 实例整个生命周期内TCP 连接的查找、复用、创建、存储、清理等工作PS :
ConcurrentLinkedQueue只能保证多线程操作中对它的单个操作是安全的,但如果是复合操作仍然需要加锁,这是多线程开发中要注意的点,OkHttp 源码注释中也强调了这一点:/** * Holding the lock of the connection being added or removed when mutating this, and check its * [RealConnection.noNewExchanges] property. This defends against races where a connection is * simultaneously adopted and removed. */ private val connections = ConcurrentLinkedQueue<RealConnection>()其中 OkHttp 的连接复用机制是体现 OkHttp 性能的重点技术,它的复用逻辑主要有两种:
- 对于 Http1.1 ,由于 Http 1.1 开始支持
Keep-Alive,因此一个当一个 Http 请求结束时,TCP 连接并不会立即释放,如果有新的请求发起,会经过一系列条件判断是否可以复用已有的连接,如果符合复用条件,这样就减少了一次 TCP 的握手动作,同时也减少了连接对象的创建,减少了内存开销。 - 对于 Http2,由于 Http2 支持Multiplexing(多路复用),也就是Http2支持在同一个 TCP 连接上同时可以发起多个 Http 请求,这样新请求也可以复用符合条件的TCP 连接,并且不需要等待连接空闲(不超负载)
- 对于 Http1.1 ,由于 Http 1.1 开始支持
-
followRedirects与followSslRedirects的关系followRedirects表示是否允许重定向,默认值是true,followSslRedirects表示在followRedirects=true的前提下,是否允许进行协议切换(Http ↔ Https)的重定向,默认值也是 true,但是在一些高安全场景要求下,需要将followSslRedirects设置为 false,以防止协议攻击 -
certificatePinner证书固定器,通过硬编码方式将证书的哈希信息固定,也就是除了验证证书合法性之外,还需要确定是指定的那个证书,否则也不能通过验证
使用方式:
//1. 先使用错误的哈希值测试一下,把服务器证书正确的哈希值打印出来 String hostname = "publicobject. com"; CertificatePinner certificatePinner = new CertificatePinner.Builder() .add(hostname, "sha256/ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=") .build(); OkHttpClient client = OkHttpClient. Builder() .certificatePinner(certificatePinner) .build(); Request request = new Request. Builder() .url("https://" + hostname) .build(); client.newCall(request).execute(); //2.打印出正确哈希值 //3.再把正确的哈希值替换过去⚠️注意:使用它比较危险,如果服务器证书更换了,可能导致客户端无法使用,因为由于是硬编码了证书哈希, 所以无法访问服务器了
PS: CertificatePinner 也可以用于防止那些基于 VPN 的抓包软件(如 Drony),因为抓包软件抓取 Https 请求的原理是让用户主动信任证书,如果固定了服务器的证书签名,抓包软件也就无法通过它们直接抓包了
-
pingIntervalQ:为什么H2 & ws 协议才需要心跳间隔,Http1.1 也是长连接,为什么不需要?
A:因为 Http1.1 中的请求是单个执行的,同一个 TCP 连接上两个请求之间的间隔时间超过限制,连接允许被释放,但在H2 中由于多个请求是并行的,TCP 长连接维持时间需要更久