OkHttpClient 核心配置详解

前言

现在,我们就来看看 OkHttpClient 中可配置的参数,也就是下面的这些属性。

kotlin 复制代码
open class OkHttpClient internal constructor(
  builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {

  @get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher

  @get:JvmName("connectionPool") val connectionPool: ConnectionPool = builder.connectionPool

  @get:JvmName("interceptors") val interceptors: List<Interceptor> =
      builder.interceptors.toImmutableList()

  @get:JvmName("networkInterceptors") val networkInterceptors: List<Interceptor> =
      builder.networkInterceptors.toImmutableList()

  @get:JvmName("eventListenerFactory") val eventListenerFactory: EventListener.Factory =
      builder.eventListenerFactory

  @get:JvmName("retryOnConnectionFailure") val retryOnConnectionFailure: Boolean =
      builder.retryOnConnectionFailure

  @get:JvmName("authenticator") val authenticator: Authenticator = builder.authenticator

  @get:JvmName("followRedirects") val followRedirects: Boolean = builder.followRedirects

  @get:JvmName("followSslRedirects") val followSslRedirects: Boolean = builder.followSslRedirects

  @get:JvmName("cookieJar") val cookieJar: CookieJar = builder.cookieJar

  @get:JvmName("cache") val cache: Cache? = builder.cache

  @get:JvmName("dns") val dns: Dns = builder.dns

  @get:JvmName("proxy") val proxy: Proxy? = builder.proxy

  @get:JvmName("proxySelector") val proxySelector: ProxySelector =
      when {
        builder.proxy != null -> NullProxySelector
        else -> builder.proxySelector ?: ProxySelector.getDefault() ?: NullProxySelector
      }

  @get:JvmName("proxyAuthenticator") val proxyAuthenticator: Authenticator =
      builder.proxyAuthenticator

  @get:JvmName("socketFactory") val socketFactory: SocketFactory = builder.socketFactory

  @get:JvmName("sslSocketFactory") val sslSocketFactory: SSLSocketFactory
    get() = sslSocketFactoryOrNull ?: throw IllegalStateException("CLEARTEXT-only client")

  @get:JvmName("x509TrustManager") val x509TrustManager: X509TrustManager?

  @get:JvmName("connectionSpecs") val connectionSpecs: List<ConnectionSpec> =
      builder.connectionSpecs

  @get:JvmName("protocols") val protocols: List<Protocol> = builder.protocols

  @get:JvmName("hostnameVerifier") val hostnameVerifier: HostnameVerifier = builder.hostnameVerifier

  @get:JvmName("certificatePinner") val certificatePinner: CertificatePinner

  @get:JvmName("certificateChainCleaner") val certificateChainCleaner: CertificateChainCleaner?

  @get:JvmName("callTimeoutMillis") val callTimeoutMillis: Int = builder.callTimeout

  @get:JvmName("connectTimeoutMillis") val connectTimeoutMillis: Int = builder.connectTimeout

  @get:JvmName("readTimeoutMillis") val readTimeoutMillis: Int = builder.readTimeout

  @get:JvmName("writeTimeoutMillis") val writeTimeoutMillis: Int = builder.writeTimeout

  @get:JvmName("pingIntervalMillis") val pingIntervalMillis: Int = builder.pingInterval
  
}

dispatcher: Dispatcher

它是请求的调度中心 ,其内部维护了一个线程池,负责执行异步请求,它的默认值是 Dispatcher()

kotlin 复制代码
class Builder constructor() {
  internal var dispatcher: Dispatcher = Dispatcher()
  ...
}

我们可以通过自定义 Dispatcher 对象来控制最大并发请求数和每个主机的最大并发请求数,以降低资源消耗。

kotlin 复制代码
class Dispatcher constructor() {
  @get:Synchronized var maxRequests = 64
    set(maxRequests) {
      require(maxRequests >= 1) { "max < 1: $maxRequests" }
      synchronized(this) {
        field = maxRequests
      }
      promoteAndExecute()
    }

  @get:Synchronized var maxRequestsPerHost = 5
    set(maxRequestsPerHost) {
      require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
      synchronized(this) {
        field = maxRequestsPerHost
      }
      promoteAndExecute()
    }
  ...  
}

connectionPool: ConnectionPool

连接池,它的概念和线程池非常类似,内部存储了一批网络连接。

连接池的工作模式:

当需要一个连接时,会先从连接池中拿取现成、可用的连接。如果有,就拿走复用;如果没有,就创建新的连接。当一个连接使用完后,并不会立马销毁,而是会在连接池中等待后续使用。如果一个闲置的连接在连接池中等待过长时,可能会被销毁。具体是否销毁、等待多长后销毁,由连接池决定(连接池的配置)。

复用连接可以避免在内存中频繁创建和销毁对象,还可以省去 TCP 和 TLS 握手的过程,加快请求。

连接复用的两种形式:

  1. 在 HTTP/1.1 的连接中,请求结束后,连接变为空闲状态,后续对该主机的请求可以复用这个已经建立的连接。

  2. 在 HTTP2 的连接中(多路复用 Multiplexing),在同一个 TCP 连接上,可以同时发送多个请求。

interceptorsnetworkInterceptors

这两个的类型都是 List<Interceptor>,也就是拦截器列表,默认值都是空列表。

kotlin 复制代码
class Builder constructor() {
  internal val interceptors: MutableList<Interceptor> = mutableListOf()
  internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
  ...
}

其中 interceptors 是应用拦截器,是最外层的拦截器。在请求的整个生命周期中只会被调用一次,你可以看到最原始的请求。

networkInterceptors 是靠近网络层的拦截器,如果请求发生了重定向或是重试时,该拦截器就会被调用多次。

eventListenerFactory: EventListener.Factory

这是用于生产 eventListener 的工厂,而 EventListener 是监听器,监听网络请求中的各个过程,比如 TCP 连接建立、请求发起、接收到响应等。

retryOnConnectionFailure: Boolean

它表示连接失败时是否重试,默认值是 true。不过它不只是连接失败,某些请求失败的情况也会处理。

如果值为 true,那么当连接失败(如 TCP 连接建立失败)或某些请求失败(如连接建立成功,但对方未响应)时,OkHttp 会自动进行重试。

authenticator: Authenticator

它是自动进行认证修正的工具。

当 Token 失效(过期)时,服务器返回的状态码会是 401,表示未授权。而我们可以使用 Authenticator 在收到 401 响应时被自动回调,来无感刷新 Token 并使用新的 Token 发起请求。

像这样来使用:

kotlin 复制代码
val client = OkHttpClient.Builder()
    .authenticator(object : Authenticator {
        override fun authenticate(route: Route?, response: Response): Request {
            // 同步获取新的 Token
            val newToken = "newToken"

            // 获取响应对应的原始请求
            // 然后创建新的请求,设置新的 Token,并返回
            return response.request.newBuilder()
                .header("Authorization", "Bearer $newToken")
                .build()
        }
    }).build()

followRedirects: Boolean

跟随重定向,表示当服务器返回的状态码为 301、302 时,OkHttp 是否自动发送新的请求到重定向网址。

值为 true 时,OkHttp 会自动请求新的地址,并将最终的、非重定向的响应结果返回。而为 false 时,会直接将重定向状态的响应结果返回。

它的默认值为 true,我们一般也是开着的,因为我们开发者通常只关心最终的内容。

followSslRedirects: Boolean

表示在 followRedirectstrue 的前提下,重定向发生协议切换时,是否继续跟随。

协议切换指的是从 http:// 重定向到 https://,当然从 https:// 重定向到 http:// 也是。

cookieJar: CookieJar

CookieJar 的字面意思是饼干罐子,它其实就是用来存储和管理 Cookie 的工具。

其默认实现 CookieJar.NO_COOKIES 是空的。也就是存储时,什么也不干;当取用时,只返回一个空列表。

kotlin 复制代码
interface CookieJar {

  fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>)

  fun loadForRequest(url: HttpUrl): List<Cookie>

  companion object {
    @JvmField
    val NO_COOKIES: CookieJar = NoCookies()
    private class NoCookies : CookieJar {
      override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
      }

      override fun loadForRequest(url: HttpUrl): List<Cookie> {
        return emptyList()
      }
    }
  }
}

当然客户端也用不上,因为 Cookie 主要是浏览器的功能,客户端会更多使用 Authorization Header 进行认证。

cache: Cache?

它用于配置 HTTP 缓存。配置后,OkHttp 会根据 HTTP 响应头的缓存规则,来存储响应内容。

dns: Dns

域名解析系统,用于将网址(域名)解析成服务器的 IP 地址。

其默认值是 Dns.SYSTEM,也就是下面的伴生对象。

kotlin 复制代码
interface Dns {
  @Throws(UnknownHostException::class)
  fun lookup(hostname: String): List<InetAddress>

  companion object {
    @JvmField
    val SYSTEM: Dns = DnsSystem()
    private class DnsSystem : Dns {
      override fun lookup(hostname: String): List<InetAddress> {
        try {
          return InetAddress.getAllByName(hostname).toList()
        } catch (e: NullPointerException) {
          throw UnknownHostException("Broken system behaviour for dns lookup of $hostname").apply {
            initCause(e)
          }
        }
      }
    }
  }
}

默认使用 Java 原生的 InetAddress.getAllByName(hostname) 方法,来进行 DNS 查询,返回的是一个 InetAddress[] 数组。

proxy: Proxy?proxySelector: ProxySelector

Proxy 是代理的意思,用于配置一个代理服务器。

客户端不会直接向目标服务器发送请求,而是会发给代理服务器,让它帮助我们转发请求和响应。

使用场景:当你被限制访问某个资源时,你可以让不被限制的代理服务器帮助你访问该资源。

代理有三种类型,分别是 DIRECTHTTP 以及 SOCKS。不过 proxy 的默认值是 null,那么代理是什么?

如果通过 .proxy() 方法设置了代理,就会始终使用该代理。在没有设置代理的情况下 ,才会通过 proxySelector 代理选择器来决定的,其选择逻辑为:

kotlin 复制代码
// OkHttpClient.kt
@get:JvmName("proxySelector") val proxySelector: ProxySelector =
  when {
    // Defer calls to ProxySelector.getDefault() because it can throw a SecurityException.
    builder.proxy != null -> NullProxySelector // proxy 不为空时,proxySelector 不起作用
    else -> builder.proxySelector ?: ProxySelector.getDefault() ?: NullProxySelector
  }
  1. 如果用户已经设置了代理,就赋值为 NullProxySelector(无意义)。
  2. 如果代理没被设置,会依次尝试使用自定义的 proxySelector 和 Java 系统的默认代理选择器。如果两者都没有,会使用 NullProxySelector

NullProxySelector 最终会返回 Proxy.NO_PROXY,即直连

所以,在大多数情况下,OkHttp 的默认代理行为就是直连。

kotlin 复制代码
// NullProxySelector.kt
object NullProxySelector : ProxySelector() {
  // 用于选择代理
  override fun select(uri: URI?): List<Proxy> {
    requireNotNull(uri) { "uri must not be null" }
    return listOf(Proxy.NO_PROXY)
  }

  override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: IOException?) {
  }
}
java 复制代码
// Proxy.java
public static final Proxy NO_PROXY = new Proxy();

private Proxy() {
    type = Type.DIRECT; // 直连
    sa = null;
}

proxyAuthenticator: Authenticator

authenticator 类似,当代理服务器的认证失效时,就使用它来处理。

如果代理服务器返回 407 Proxy Authentication Required 状态码时,它的回调会触发。

SSL/TLS 相关配置

  • socketFactory: SocketFactory

    创建 TCP 连接(Socket)的工厂

  • sslSocketFactory: SSLSocketFactory

    创建在 TCP 连接之上的加密连接(TSL、SSL 连接)的工厂

  • x509TrustManager: X509TrustManager

    证书验证器,验证建立 HTTPS 连接时,服务器证书链的合法性。X509 是当前证书的标准格式,

  • connectionSpecs: List<ConnectionSpec>

    连接规范列表。也就是 HTTPS 连接时,允许使用的 TLS 版本和加密套件(包含非对称加密、对称加密、哈希算法)。

    OkHttp 给我们提供了几个配置,分别是:

    kotlin 复制代码
    class ConnectionSpec internal constructor(...) {
      ...
      
      @Suppress("DEPRECATION")
      companion object {
        ...
        
        @JvmField
        val RESTRICTED_TLS = Builder(true)
            .cipherSuites(*RESTRICTED_CIPHER_SUITES)
            .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
            .supportsTlsExtensions(true)
            .build()
    
        @JvmField
        val MODERN_TLS = Builder(true)
            .cipherSuites(*APPROVED_CIPHER_SUITES)
            .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
            .supportsTlsExtensions(true)
            .build()
    
        @JvmField
        val COMPATIBLE_TLS = Builder(true)
            .cipherSuites(*APPROVED_CIPHER_SUITES)
            .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
            .supportsTlsExtensions(true)
            .build()
    
        /** Unencrypted, unauthenticated connections for `http:` URLs. */
        @JvmField
        val CLEARTEXT = Builder(false).build()
      }
    }

    RESTRICTED_TLS 是最严格的规范,它的安全性最高,但兼容性最弱。MODERN_TLS(默认值),是目前最流行的规范。而 COMPATIBLE_TLS 会比 MODERN_TLS 宽松一些,支持更多 TLS 的版本,有最强的兼容性。

    CLEARTEXT 是明文的意思,也就是 HTTP(明文传输)。

  • protocols: List<Protocol>

    表示客户端支持的 HTTP 协议列表:

    kotlin 复制代码
    enum class Protocol(private val protocol: String) {
    
      HTTP_1_0("http/1.0"),
    
      HTTP_1_1("http/1.1"),
    
      @Deprecated("OkHttp has dropped support for SPDY. Prefer {@link #HTTP_2}.")
      SPDY_3("spdy/3.1"),
    
      HTTP_2("h2"),
    
      H2_PRIOR_KNOWLEDGE("h2_prior_knowledge"),
    
      QUIC("quic");
    }

    其中 SPDY_3HTTP_2 的前身,现在已经被废弃了。H2_PRIOR_KNOWLEDGE 也是 HTTP_2,只不过是非加密版本。

  • hostnameVerifier: HostnameVerifiercertificatePinner: CertificatePinnercertificateChainCleaner: CertificateChainCleaner?

    这三个是和证书验证相关的。

    其中 certificateChainCleaner 是实际操作 X509TrustManager 进行证书链验证的操作员。hostnameVerifier 是主机名验证器,在证书被验证合法后,会使用它来验证证书的归属主机名是否为我们当前请求的域名。 certificatePinner 是证书锁定,就是在代码中硬编码服务器证书的公钥哈希值,只信任指定的证书,来加强安全性。

    例如下面代码中,如果服务器证书链中的证书公钥哈希值不包含 sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=,证书链就会被拒绝。

    kotlin 复制代码
    val url = "https://www.baidu.com"
    val hostname = "www.baidu.com"
    val certificatePinner = CertificatePinner.Builder()
        .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
        .build()
    val client = OkHttpClient.Builder()
        .certificatePinner(certificatePinner)
        .build()
    val request = Request.Builder()
        .url(url)
        .build()
    client.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            e.printStackTrace()
        }
    
        override fun onResponse(call: Call, response: Response) {
            println("Status Code: ${response.code}")
        }
    })

    如何获取服务器的证书公钥哈希值呢?也是使用上述代码,可以在日志中查看:

    less 复制代码
    W/System.err    com.example.okhttptest    javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
    W/System.err    com.example.okhttptest      Peer certificate chain:
    W/System.err    com.example.okhttptest        sha256/aqQ4L+Pac7Qy3Or7l6f9IypN8w1H64i48B4weiXJ2v4=: CN=baidu.com,O=Beijing Baidu Netcom Science Technology Co.\, Ltd,L=beijing,ST=beijing,C=CN
    W/System.err    com.example.okhttptest        sha256/hETpgVvaLC0bvcGG3t0cuqiHvr4XyP2MTwCiqhgRWwU=: CN=GlobalSign RSA OV SSL CA 2018,O=GlobalSign nv-sa,C=BE
    W/System.err    com.example.okhttptest        sha256/cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A=: CN=GlobalSign,O=GlobalSign,OU=GlobalSign Root CA - R3
    W/System.err    com.example.okhttptest      Pinned certificates for www.baidu.com:
    W/System.err    com.example.okhttptest        sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=

超时与心跳

  • callTimeoutMillis, connectTimeout, readTimeout, writeTimeout 分别是整个调用、连接、读取和写入的超时设置。

    其中整个调用的超时是指,从 client.newCall(request) 开始,到 Response 完全被读取结束的总时间。默认值是 0,表示不限制总时长。

  • pingIntervalMillis: Int

    WebSocketHTTP/2 这类长连接的心跳间隔。

相关推荐
coder_pig1 小时前
🤡 公司Android老项目升级踩坑小记
android·flutter·gradle
死就死在补习班2 小时前
Android系统源码分析Input - InputReader读取事件
android
死就死在补习班2 小时前
Android系统源码分析Input - InputChannel通信
android
死就死在补习班2 小时前
Android系统源码分析Input - 设备添加流程
android
死就死在补习班2 小时前
Android系统源码分析Input - 启动流程
android
tom4i3 小时前
Launcher3 to Launchpad 01 布局修改
android
淡淡的香烟3 小时前
Android auncher3实现简单的负一屏功能
android
RabbitYao4 小时前
Android 项目 通过 AndroidStringsTool 更新多语言词条
android·python
RabbitYao4 小时前
使用 Gemini 及 Python 更新 Android 多语言 Excel 文件
android·python