【Android】OkHttp原始碼解讀逐字稿(2)-OkHttpClient
如期而至,相約的文章,它來咯~
在開始之前,我想說,這兩篇文章都稍微比較長,但是如果真的能認真的看完,理解完,內功會蹭蹭往上漲。同時,面對相應的面試題,也能自如應對。
上一篇的傳送 【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.dispatcher
如果仔細點的道友,可以發現在上篇文章中的開始分析的第一段程式碼中,就出現了它。當時,我們並沒有分析它,那現在我們再來看看吧。 ``` 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
居然有同步,我們很自然就會想到非同步這個概念。
正如我們所料,確實,還有另外兩個佇列。一個是 readyAsyncCalls, runningAsyncCalls。看註釋,我們就知道他們的作用了。
```
/* Ready async calls in the order they'll be run. /
private val readyAsyncCalls = ArrayDeque
/* Running asynchronous calls. Includes canceled calls that haven't finished yet. /
private val runningAsyncCalls = ArrayDeque
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)) } ```
發現,它呼叫了 dispatcher 的同名方法。
``` 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
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.結語
這次的文章,主要是講了這些屬性的,以便我們在一些定製化需求上,知道我們可以用什麼來做。然後具體的用法,其實自己嘗試一下,或者搜一下就出來了。
如果文章中,有不懂的,或者紕漏,或者對我的文章寫作排版有建議和意見的,都可以評論或者私信我。
最後的最後,我正在參與掘金技術社群創作者簽約計劃招募活動,點選連結報名投稿。