[안드로이드] OkHttp 소스코드 해석 (2)-OkHttpClient

사진 1.png예정대로 온다 약속 기사 온다~

시작하기 전에 이 두 글이 좀 길다는 말씀을 드리고 싶지만 정말 잘 읽고 이해하시면 내면의 힘이 솟구칠 것입니다. 동시에 해당 면접 질문을 쉽게 처리할 수 있습니다.

이전 전송 [Android] OkHttp 소스 코드 해석 그대로 (1) - 인터셉터

0. 서문

이 문서에서는 주로 OkHttpClient의 일부 구성을 살펴봅니다.

먼저 마지막 코드를 기반으로 OkHttpClient() 뒤에 하나를 추가 .하면 아래와 같이 여러 속성과 메서드를 볼 수 있습니다. 그렇다면 그들의 역할은 정확히 무엇입니까? 그것이 이 기사에서 우리의 임무입니다. 미친 호기심을 가지고 가서 확인하십시오.


OkHttpClient의 코드를 입력하면 시작 부분의 코드에 몇 가지 속성이 나열되는데, 이것이 우리가 연구하고자 하는 것입니다. 하나씩 분석할 수 있도록 먼저 표시하겠습니다.

@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. 속성별 분석

01.디스패처

주의를 기울이면 이전 기사 에서 분석을 시작한 첫 번째 코드 조각에 나타났음을 알 수 있습니다. 당시에는 분석하지 않았으니 지금부터 살펴보자.

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)
  }
}

후속 조치!

runningSyncCalls.add(call)

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

위의 주석에서 볼 수 있듯이 현재 호출을 대기열에 넣는 것이 여전히 동기 호출이라는 것을 알고 있습니다.

실제로 동기화가 있으며 우리는 자연스럽게 비동기의 개념을 생각합니다.

예상대로 실제로 대기열이 두 개 더 있었습니다. 하나는 readyAsyncCalls, runningAsyncCalls입니다. 메모를 보면 그들이 하는 일을 압니다.

/** 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>()

이때 우리는 기억해야 합니다. OkHttp를 사용하여 네트워크 요청을 할 때 enqueue 메소드도 사용할 수 있습니다.

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

마찬가지로 RealCall에서 해당 메소드를 참조하십시오.

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

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

동일한 이름의 디스패처 메소드를 호출하는 것을 발견했습니다.

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()
}

또한 반환 값을 살펴보고 마지막 줄을 참조하십시오.

/**
 * 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.结语

이 기사에서는 주로 이러한 속성에 대해 설명하므로 일부 사용자 지정 요구 사항에 사용할 수 있는 항목을 알 수 있습니다. 그런 다음 특정 사용법은 실제로 직접 시도하거나 검색하십시오.

글에 이해가 안가는 부분이 있거나 잘못된 부분이 있거나 제 글의 작성과 레이아웃에 대한 제안이나 의견이 있으시면 댓글이나 비밀댓글로 부탁드립니다.

마지막으로 Nuggets 기술 커뮤니티의 크리에이터 서명 프로그램 모집 활동에 참여하고 있습니다 . 링크를 클릭하여 등록하고 제출 하십시오.

추천

출처juejin.im/post/7120541895377289246