[Android] Interpretação do código-fonte OkHttp na íntegra (2)-OkHttpClient

Imagem 1.pngEstá chegando como programado, o artigo do compromisso, está chegando~

Antes de começar, gostaria de dizer que esses dois artigos são um pouco longos, mas se você realmente puder lê-los e entendê-los com atenção, sua força interna aumentará. Ao mesmo tempo, você pode lidar com as perguntas correspondentes da entrevista com facilidade.

A transmissão anterior [Android] OkHttp interpretação do código-fonte na íntegra (1) - interceptor

0. Prefácio

Este artigo explora principalmente algumas configurações do OkHttpClient.

Primeiro, com base no último código, adicionamos um após OkHttpClient() e .podemos ver várias propriedades e métodos conforme mostrado abaixo. Então, qual é exatamente o papel deles? Essa é a nossa tarefa neste artigo. Vá com uma maldita curiosidade e vá conferir.


Entrando no código do OkHttpClient, no código no início, são listadas várias propriedades, que é o que queremos estudar. Vou salvá-los primeiro, para que possamos analisá-los um por um.

@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 {
      // Defer calls to ProxySelector.getDefault() because it can throw a SecurityException.
      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
private val sslSocketFactoryOrNull: SSLSocketFactory?
@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


1. Análise de cada atributo

01.despachante

Se você for cuidadoso, poderá descobrir que ele apareceu na primeira parte do código que iniciou a análise no artigo anterior . Na época, não analisamos, então vamos ver agora.

override fun execute(): Response {
  check(executed.compareAndSet(false, true)) { "Already Executed" }

  timeout.enter()
  callStart()
  try {
    client.dispatcher.executed(this)
    return getResponseWithInterceptorChain()
  } finally {
    client.dispatcher.finished(this)
  }
}

acompanhamento!

runningSyncCalls.add(call)

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private val runningSyncCalls = ArrayDeque<RealCall>()

Como você pode ver nos comentários acima, sabemos que colocar a chamada atual em uma fila ainda é uma chamada síncrona.

Na verdade, há sincronização, e naturalmente pensamos no conceito de assincronia.

Como esperávamos, de fato, havia outras duas filas. Um é readyAsyncCalls, runningAsyncCalls. Olhando para as notas, sabemos o que eles fazem.

/** Ready async calls in the order they'll be run. */
private val readyAsyncCalls = ArrayDeque<AsyncCall>()

/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private val runningAsyncCalls = ArrayDeque<AsyncCall>()

Neste momento, devemos lembrar. Quando usamos o OkHttp para fazer uma solicitação de rede, também podemos usar o método enqueue

client.newCall(request).enqueue(object : Callback{
    override fun onFailure(call: Call, e: IOException) {
    }
    override fun onResponse(call: Call, response: Response) {
    }
})

Da mesma forma, veja o método correspondente em RealCall

override fun enqueue(responseCallback: Callback) {
  check(executed.compareAndSet(false, true)) { "Already Executed" }

  callStart()
  client.dispatcher.enqueue(AsyncCall(responseCallback))
}

Descobriu que ele chama o método do dispatcher com o mesmo nome.

internal fun enqueue(call: AsyncCall) {
  synchronized(this) {
    readyAsyncCalls.add(call)

    // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
    // the same host.
    if (!call.call.forWebSocket) {
      val existingCall = findExistingCallWithHost(call.host)
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
    }
  }
  promoteAndExecute()
}

Veja também o valor de retorno, veja a última linha

/**
 * Promotes eligible calls from [readyAsyncCalls] to [runningAsyncCalls] and runs them on the
 * executor service. Must not be called with synchronization because executing calls can call
 * into user code.
 *
 * @return true if the dispatcher is currently running calls.
 */
private fun promoteAndExecute(): Boolean {
  this.assertThreadDoesntHoldLock()

  val executableCalls = mutableListOf<AsyncCall>()
  val isRunning: Boolean
  synchronized(this) {
    val i = readyAsyncCalls.iterator()
    while (i.hasNext()) {
      val asyncCall = i.next()

      if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
      if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.

      i.remove()
      asyncCall.callsPerHost.incrementAndGet()
      executableCalls.add(asyncCall)
      runningAsyncCalls.add(asyncCall)
    }
    isRunning = runningCallsCount() > 0
  }

  for (i in 0 until executableCalls.size) {
    val asyncCall = executableCalls[i]
    asyncCall.executeOn(executorService)
  }
  return isRunning
}

看到注释,我们可以得知,这个方法,就是将一个可以被执行的 call 用 executor ervices 跑起来。看上面两个 if。只要经历过这两个 if 的筛选的话,就可以 executableCalls.add(asyncCall) 。

那我们这两个if ,主要做了什么。

1.判断正在执行的 call 的个数是否大于 maxRequests

2.判断同一个Host下的 call 个数是否大于 maxRequestsPerHost

至于,maxRequests 和 maxRequestsPerHost 就不细讲了。见明知意,maxRequests 默认值为 5 ,maxRequestsPerHost 默认值是 64。

我们看看 dispatchtor 这个类的注释,做一个小结

/**
 * Policy on when async requests are executed.
 *
 * Each dispatcher uses an [ExecutorService] to run calls internally. If you supply your own
 * executor, it should be able to run [the configured maximum][maxRequests] number of calls
 * concurrently.
 */

总的来说,就是会通过一个 ExecutorService 去执行一个call。

最后,留一个疑问,“enqueue 方法,是通过 promoteAndExcute 方法,去调用一个 ExecutorService 去执行。那 execute() 是通过什么去运行一个 call的呢?”


02.connectionPool

这个跟我们的 ThreadPool ,具有异曲同工之妙

看下这个类的注释

* Manages reuse of HTTP and HTTP/2 connections for reduced network latency. HTTP requests that
* share the same [Address] may share a [Connection]. This class implements the policy
* of which connections to keep open for future use.

这里,单独说明了,HTTP 当同样的 IP 地址的时候,才可以共享一个连接。

那么 HTTP2 是具有多路复用(Multipex)的能力的。


03.interceptors & networkInterceptors

这个就是将我们自定义的拦截器接收下来,然后在上篇文章中的

getResponseWithInterceptorChain() 加入到拦截器链条中


04.eventListenerFactory

事件监听器工厂类,

EventListener

* Listener for metrics events. Extend this class to monitor the quantity, size, and duration of
* your application's HTTP calls.


05.retryOnConnectionFailure

这个返回的是个布尔值,所以这是个开关。

看名字,我们可以知道,“是否要在失败的时候,进行重试”


06.authenticator

自动认证修订的工具。

tokon 是一个有效期,这时候需要通过 refreshToken 去重新获取 token


07.followRedirects & followSslRedirects

同理,返回的是个布尔值,所以是个开关。

“再发生重定向的时候,是否要重定向”

“在发生协议重定向(http->https),是否要重定向”


08.cookieJar

CookieJar 类,主要有两个方法

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

从相应头中保存 对应 url 的 cookies 信息

在请求头中带上 对应 url 的 cookies 信息


09.cache

* Caches HTTP and HTTPS responses to the filesystem so they may be reused, saving time and * bandwidth.

这个是缓存器,主要缓存一些数据,在下次获取的时候,可以直接从cache中获取,从而可以达到节省带宽的效果


10.dns

Domain name service 域名解析器。将 host 解析成对应的 IP 地址

核心代码

InetAddress.getAllByName(hostname).toList()


11.proxy & proxySelector & proxyAuthenticator

代理,如果需要请求需要通过代理服务器来负责,则添加一个代理。

这里需要注意一下它的类型,其中包括 直连,这也是默认方法。也就是不需要代理。

public enum Type {
    /**
     * Represents a direct connection, or the absence of a proxy.
     */
    DIRECT,
    /**
     * Represents proxy for high level protocols such as HTTP or FTP.
     */
    HTTP,
    /**
     * Represents a SOCKS (V4 or V5) proxy.
     */
    SOCKS
};

而 proxy 的默认值为 null

internal var proxy: Proxy? = null

那怎么默认到 直连呢?那就是 proxySelector

when {
  // Defer calls to ProxySelector.getDefault() because it can throw a SecurityException.
  builder.proxy != null -> NullProxySelector
  else -> builder.proxySelector ?: ProxySelector.getDefault() ?: NullProxySelector
}

如果 proxy 为空 且 默认的 proxySelector 也是空的话,就默认为 NullProxySelector 。而它最后返回的是 Type.DIRECT 的代理

public final static Proxy NO_PROXY = new Proxy();

// Creates the proxy that represents a {@code DIRECT} connection.
private Proxy() {
    type = Type.DIRECT;
    sa = null;
}

最后 proxyAuthenticator ,就是用于验证 代理服务器 的合法性的。


12.socketFactory & sslSocketFactory

我们进行 HTTP 连接请求本质上是一个 socket。 就是通过 socketFactory 去创建。

同时,我们进行加密连接 SSL 连接的时候,这是的 socket 就是通过 sslSocketFactory 去创建的。


13.x509TrustManager

首先 x509 是一种证书格式。这个类就是验证证书的合法性的。(如果不太明白,可以自行先了解一下 HTTPS 连接流程其中的安全性是如何保证的?后续,有空有把一些相关基础性的东西补充成另一篇文章)


14.connectionSpecs

连接标准。

在请求连接的时候,客户端需要向服务器,发送支持的协议,加密套件等信息 (看不懂,同上)

这里,我们还需要知道是,提供的四种配置。

/** A secure TLS connection that requires a recent client platform and a recent server. */
@JvmField
val RESTRICTED_TLS = Builder(true)
    .cipherSuites(*RESTRICTED_CIPHER_SUITES)
    .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
    .supportsTlsExtensions(true)
    .build()
/**
 * A modern TLS configuration that works on most client platforms and can connect to most servers.
 * This is OkHttp's default configuration.
 */
@JvmField
val MODERN_TLS = Builder(true)
    .cipherSuites(*APPROVED_CIPHER_SUITES)
    .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
    .supportsTlsExtensions(true)
    .build()

/**
 * A backwards-compatible fallback configuration that works on obsolete client platforms and can
 * connect to obsolete servers. When possible, prefer to upgrade your client platform or server
 * rather than using this configuration.
 */
@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()

其中最后一种 明文传输就是 HTTP 。

第一种限制更多;第二种是比较流行的,同时也是默认值;第三种限制比较少,即兼容性更好。


15.protocols

支持的协议版本号,例如:HTTP_1_0; HTTP_1_1;HTTP_2;H2_PRIOR_KNOWLEDGE(不加密,明文传输的时候使用)


16.hostnameVerifier

在验证 证书的合法性的同时,我们还需要验证是这个证书是哪个网站的,那么就需要这个 hostnameVerifier 来验证。


17.certificatePinner

这个可以用来对某个网站,在验证证书的合法性同时,要满足我们指定的证书哈希值。但是不建议这么做,因为在更换验证机构后,会导致之前的用户无法正常使用我们的应用。


18.certificateChainCleaner

这是一个 X509TrustManager 的操作员


19.一组跟时间有关的属性 callTimeoutMillis & connectTimeoutMillis & readTimeoutMillis & writeTimeoutMillis & pingIntervalMillis

/**
 * Default call timeout (in milliseconds). By default there is no timeout for complete calls, but
 * there is for the connect, write, and read actions within a call.
 */
@get:JvmName("callTimeoutMillis") val callTimeoutMillis: Int = builder.callTimeout

/** Default connect timeout (in milliseconds). The default is 10 seconds. */
@get:JvmName("connectTimeoutMillis") val connectTimeoutMillis: Int = builder.connectTimeout

/** Default read timeout (in milliseconds). The default is 10 seconds. */
@get:JvmName("readTimeoutMillis") val readTimeoutMillis: Int = builder.readTimeout

/** Default write timeout (in milliseconds). The default is 10 seconds. */
@get:JvmName("writeTimeoutMillis") val writeTimeoutMillis: Int = builder.writeTimeout

/** Web socket and HTTP/2 ping interval (in milliseconds). By default pings are not sent. */
@get:JvmName("pingIntervalMillis") val pingIntervalMillis: Int = builder.pingInterval

其中需要额外了解下的是,最后一个 pingIntervalMillis。这是 HTTP2 的时候,可能是一个长连接,那么需要通过这个来进行保活,客户端发一个 ping,服务端回一个 pong


2.结语

Este artigo fala principalmente sobre essas propriedades, para que saibamos o que podemos usar para alguns requisitos de personalização. Então o uso específico, de fato, experimente você mesmo ou pesquise.

Se houver algo que você não entenda, ou houver erros no artigo, ou se você tiver sugestões e opiniões sobre a redação e o layout do meu artigo, você pode comentar ou me enviar uma mensagem privada.

Por último, mas não menos importante, estou participando da atividade de recrutamento do programa de assinatura de criadores da comunidade de tecnologia Nuggets, clique no link para se registrar e enviar .

Acho que você gosta

Origin juejin.im/post/7120541895377289246
Recomendado
Clasificación