OkHttp作為一個網(wǎng)絡請求框架,地位是不言而喻的,研究它的好處就在于能夠將TCP、HTTP、HTTPS等這些基礎的網(wǎng)絡知識實例化,抽象變?yōu)樾蜗蟆?/p>
讀完這篇文章您將了解到:
- OkHttp的整體請求結構;
- 責任鏈模式下各個攔截器的實現(xiàn)細節(jié)與職責;
- 如何找到可用且健康的連接?即連接池的復用;
- 如何找到Http1和Http2的編/解碼器?
- NetworkInterceptor與ApplicationInterceptor攔截器的區(qū)別?
- 如何建立TCP/TLS連接?
本文源碼為okhttp:4.9.1版本,文中沒有貼大量源碼,結合源碼一起閱讀最佳。
OkHttp整體結構
OkHttp的使用不是本文的主要內容,它只是作為源碼解讀的一個入口。
val okHttpClient = OkHttpClient()
val request: Request = Request.Builder()
.url("https://cn.bing.com/")
.build()
okHttpClient.newCall(request).enqueue(object :Callback{
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
}
})
OkHttp使用起來很簡單,先創(chuàng)建OkHttpClient和Request對象,以Request來創(chuàng)建一個RealCall對象,利用它執(zhí)行異步enqueue或者同步execute操作將請求發(fā)送出去,并監(jiān)聽請求失敗或者成功的反饋Callback。
這里有三個主要的類需要說明一下:OkHttpClient、Request以及RealCall。
- OkHttpClient: 相當于配置中?,可用于發(fā)送 HTTP 請求并讀取其響應。它的配置有很多,例如connectTimeout:建?連接(TCP 或 TLS)的超時時間,readTimeout :發(fā)起請求到讀到響應數(shù)據(jù)的超時時間,Dispatcher:調度器,?于調度后臺發(fā)起的?絡請求,等等。還有其他配置可查看源碼。
- Request: 一個主要設置網(wǎng)絡請求Url、請求方法(GET、POST......)、請求頭、請求body的請求類。
- RealCall: RealCall是由newCall(Request)方法返回,是OkHttp執(zhí)行請求最核心的一個類之一,用作連接OkHttp的應用程序層和網(wǎng)絡層,也就是將OkHttpClient和Request結合起來,發(fā)起異步和同步請求。
從上面的使用步驟可以看到,OkHttp最后執(zhí)行的是okHttpClient.newCall(request).enqueue,也就是RealCall的enqueue方法,這是一個異步請求,同樣的,也可以執(zhí)行同步請求RealCall.execute()。
RealCall的同步請求最后其實會調用RealCall.getResponseWithInterceptorChain(),而RealCall的異步請求是使用線程池先將請求放置到后臺處理,但是最后還是會調用RealCall.getResponseWithInterceptorChain()來獲取網(wǎng)絡請求的返回值Response。從這里就基本能嗅到網(wǎng)絡請求的核心其實與getResponseWithInterceptorChain()方法有關,那到底如何與服務器連接進行網(wǎng)絡請求的?這個問題就先拋在這,后面再詳細說。
我們先從異步請求enqueue開始,來看異步請求的主要結構。
類:Dispatcher
private fun promoteAndExecute(): Boolean {
...
val executableCalls = mutableListOf<AsyncCall>()
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
}
異步請求首先會將AsyncCall添加到雙向隊列readyAsyncCalls中(即準備執(zhí)行但還沒有執(zhí)行的隊列),做請求的準備動作。接著遍歷準備執(zhí)行隊列readyAsyncCalls,尋找符合條件的請求,并將其加入到一個保存有效請求的列表executableCalls和正在執(zhí)行隊列runningAsyncCalls中,而這個篩選條件主要有兩條:
if (runningAsyncCalls.size >= this.maxRequests) break:并發(fā)執(zhí)行的請求數(shù)要小于最大的請求數(shù)64。if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue:某個主機的并發(fā)請求數(shù)不能超過最大請求數(shù)5
也就是說,當我們的并發(fā)請求量超過64個或者某個主機的的請求數(shù)超過5,則超過的請求暫時不能執(zhí)行,需要等一等才能再加入執(zhí)行隊列中。
將有效的請求篩選出后并保存,立即開始遍歷請求,一一利用調度器Dispatcher里的ExecutorService進行Runnable任務,也就是遍歷后加入到線程池中執(zhí)行這些有效的網(wǎng)絡請求。
類:RealCall.AsyncCall
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
...
try {
val response = getResponseWithInterceptorChain()
signalledCallback = true
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
...
responseCallback.onFailure(this@RealCall, e)
}
}
}
上面的代碼就是在線程池中執(zhí)行的請求任務,可以看到try-catch塊中有一句 val response = getResponseWithInterceptorChain() 得到網(wǎng)絡請求結果resonse ,將返回的response或者錯誤,通過callback告知給用戶。這個callback也就是一開始OkHttp使用時所注冊監(jiān)聽的callback。
另外,這個方法是不是很熟悉?因為在上面說明三個主要核心類時提到過,RealCall的同步請求或者異步請求,最后都會走到getResponseWithInterceptorChain()這一步。
網(wǎng)絡請求結果response就是通過這個getResponseWithInterceptorChain()方法返回的,那網(wǎng)絡請求結果到底是如何拿到的? 與服務器又是如何交互的呢? 我們就來剖析這個方法的內部結構。
攔截器內部實現(xiàn)
從上面OkHttp的結構分析知道,所有網(wǎng)絡請求的細節(jié)都封裝在getResponseWithInterceptorChain() 這個核心方法中。那我們就來研究一下它的具體實現(xiàn)。
類:RealCall
internal fun getResponseWithInterceptorChain(): Response {
// Build a full stack of interceptors.
val interceptors = mutableListOf<Interceptor>()
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
...
try {
val response = chain.proceed(originalRequest)
...
return response
}
...
}
getResponseWithInterceptorChain()的內部實現(xiàn)是通過一個責任鏈模式來完成,將網(wǎng)絡請求的各個階段封裝到各個鏈條中(即各個攔截器Interceptor),配置好各個Interceptor后將其放在?個List?,然后作為參數(shù),創(chuàng)建?個RealInterceptorChain對象,并調? chain.proceed(request)來發(fā)起請求和獲取響應。
在每一條攔截器中,會先做一些準備動作,例如對該請求進行是否可用的判斷,或者將請求轉換為服務器解析的格式,等等,接著就對請求執(zhí)行chain.proceed(request)。上面提到getResponseWithInterceptorChain()的內部實現(xiàn)是一個責任鏈模式,而chain.proceed(request)的作用就是責任鏈模式的核心所在,將請求移交給下一個攔截器。
OkHttp中連自定義攔截器包括在內,一共有7種攔截器,在這里,網(wǎng)絡請求的細節(jié)就封裝在各個攔截器中,每個攔截器也都有自己的職責,只要把每個攔截器研究清楚,整個網(wǎng)絡請求也就明了了。下面就來一一分析這些攔截器的職責。
7種攔截器的職責
1、用戶自定義攔截器interceptors
用戶自定義攔截器是在所有其他攔截器之前,開發(fā)者可根據(jù)業(yè)務需求進行網(wǎng)絡攔截器的自定義,例如我們常常自定義Token處理攔截器,日志打印攔截器等。
2、RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor是一個請求失敗和重定向時重試的攔截器。它的內部開啟了一個請求循環(huán),每次循環(huán)都會先做一個準備動作(call.enterNetworkInterceptorExchange(request, newExchangeFinder)),這個準備動作最主要的目的在于創(chuàng)建一個ExchangeFinder,為請求尋找可用的Tcl或者Tsl連接以及設置跟連接相關的一些參數(shù),如連接編碼解碼器等。 ExchangeFinder在后面網(wǎng)絡連接時,會詳細說明。
準備工作做好后便開始了一個網(wǎng)絡請求(response = realChain.proceed(request)),這句代碼的目的是為了將請求傳遞給下一個攔截器。同時,會判斷當前請求是否會出錯以及是否需要重定向。如果出錯或者需要重定向,那么就又開始新一輪的循環(huán),直到?jīng)]有出錯和需要重定向為止。
這里出錯和重定向的判斷標準也簡單說一下:
- 判斷出錯的標準: 利用try-catch塊對請求進行異常捕獲,這里會捕獲RouteException和IOException,并且在出錯后都會先判斷當前請求是否能夠進行重試的操作。
- 重定向標準: 這里判斷是否需要重定向,是對Response的狀態(tài)碼Code進行審查,當狀態(tài)碼為3xx時,則表示需要重定向,而后創(chuàng)建一個新的request,進行重試操作。
3、BridgeInterceptor
BridgeInterceptor是用來連接應用程序代碼和網(wǎng)絡代碼的一個攔截器。也就是說該攔截器會幫用戶準備好服務器請求所需要的一些配置??赡芏x太抽象,我們就先來看一下一個請求Url所對應的服務器請求頭是怎么樣的?
URL: https://wanandroid.com/wxarticle/chapters/json
方法: GET
那它所對應的請求頭如下:
GET /wxarticle/chapters/json HTTP/1.1
Host: wanandroid.com
Accept: application/json, text/plain, /
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
User-Agent: Mozilla/5.0 xxx
......
你可能會問,BridgeInterceptor攔截器和這個有什么關系?其實BridgeInterceptor的作用就是幫用戶處理網(wǎng)絡請求,它會幫助用戶填寫服務器請求所需要的配置信息,如上面所展示的User-Agent、Connection、Host、Accept-Encoding等。同時也會對請求的結果進行相應處理。
BridgeInterceptor的內部實現(xiàn)主要分為以下三步:
為用戶網(wǎng)絡請求設置Content-Type、Content-Length、Host、Connection、Cookie等參數(shù),也就是將一般請求轉換為適合服務器解析的格式,以適應服務器端;
通過 chain.proceed(requestBuilder.build())方法,將轉換后的請求移交給下一個攔截器CacheInterceptor,并接收返回的結果Response;
對結果Response也進行gzip、Content-Type轉換,以適應應用程序端。
所以說BridgeInterceptor是應用程序和服務器端的一個橋梁。
4、CacheInterceptor
CacheInterceptor是一個處理網(wǎng)絡請求緩存的攔截器。它的內部處理和一些圖片緩存的邏輯相似,首先會判斷是否存在可用的緩存,如果存在,則直接返回緩存,反之,調用chain.proceed(networkRequest)方法將請求移交給下一個攔截器,有了結果后,將結果put到cache中。
5、ConnectInterceptor
ConnectInterceptor是建立連接去請求的攔截器。
internal fun initExchange(chain: RealInterceptorChain): Exchange {
...
val exchangeFinder = this.exchangeFinder!!
val codec = exchangeFinder.find(client, chain)
val result = Exchange(this, eventListener, exchangeFinder, codec)
this.interceptorScopedExchange = result
this.exchange = result
...
if (canceled) throw IOException("Canceled")
return result
}
從它的源碼可以看到,它首先會通過ExchangeFinder查詢到codec,這個ExchangeFinder是不是很熟悉?在上面RetryAndFollowUpInterceptor分析中,每次循環(huán)都會先做創(chuàng)建ExchangeFinder的準備工作。
而這個codec是什么?它是一個編碼解碼器,來確定是用Http1的方式還是以Http2的方式進行請求。
在找到合適的codec后,作為參數(shù)創(chuàng)建Exchange。Exchange內部涉及了很多網(wǎng)絡連接的實現(xiàn),這個后面再詳細說,我們先看看是如何找到合適的codec?
如何找到可用連接?
找到合適的codec,就必須先找到一個可用的網(wǎng)絡連接,再利用這個可用的連接創(chuàng)建一個新的codec。
為了找到可用的連接,內部使用了大概5種方式進行篩選。
第一種:從連接池中查找
if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
val result = call.connection!!
return result
}
嘗試在連接池中查找可用的連接,在遍歷連接池中的連接時,就會判斷每個連接是否可用,而判斷連接是否可用的條件如下:
- 請求數(shù)要小于該連接最大能承受的請求數(shù),Http2以下,最大請求數(shù)為1個,并且此連接上可創(chuàng)建新的交換;
- 該連接的主機和請求的主機一致;
如果從連接池中拿到了合格的連接connection,則直接返回。
如果沒有拿到,那就進行第二種拿可用連接的方式。
第二種:傳入Route,從連接池中查找
if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
val result = call.connection!!
return result
}
第二種依然是從連接池中拿,但是這次不同的是,參數(shù)里傳入了routes,這個routes是包含路由Route的一個List集合,而Route其實指的是連接的IP地址、TCP端口以及代理模式。
而這次從連接池中拿,主要是針對Http2,路由必須共用一個IP地址,此連接的服務器證書必須包含新主機且證書必須與主機匹配。
第三種:自己創(chuàng)建連接
如果前兩次從連接池里都沒有拿到可用連接,那么就自己創(chuàng)建連接。
val newConnection = RealConnection(connectionPool, route)
call.connectionToCancel = newConnection
try {
newConnection.connect(
connectTimeout,
readTimeout,
writeTimeout,
pingIntervalMillis,
connectionRetryEnabled,
call,
eventListener
)
}
創(chuàng)建連接其實是內部自己在進行socket,tls的連接,這里拋出一個問題在后面解答:TCP/TLS連接是如何實現(xiàn)的?
自己創(chuàng)建好連接后,又做了一次從連接池中查找的操作。
第四種:多路復用置為true,依然從連接池中查找
if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
val result = call.connection!!
newConnection.socket().closeQuietly()
return result
}
這次從連接池中查找,requireMultiplexed置為了true,只查找支持多路復用的連接。并且在建立連接后,將新的連接保存到連接池中。
如何找到Http1和Http2的編/解碼器?
上面已經(jīng)分析出尋找可用且健康的連接的幾種方式,那對于codec的創(chuàng)建則需要根據(jù)這些連接進行Http1和Http2的區(qū)分。如果http2Connection不為null,則創(chuàng)建Http2ExchangeCodec,反之創(chuàng)建Http1ExchangeCodec。
找到編解碼器后,我們就回到ConnectInterceptor的一開始,利用編解碼器codec創(chuàng)建了一個Exchange,而這個Exchange的內部其實是利用Http1解碼器或者Http2解碼器,分別進行請求頭的編寫writeRequestHeaders,或者創(chuàng)建Request Body,發(fā)送給服務器。
Exchange初始化成功后,就又將請求移交給了下一個攔截器CallServerInterceptor。
6、CallServerInterceptor
CallServerInterceptor是鏈中最后一個攔截器,主要用于向服務器發(fā)送內容,主要傳輸http的頭部和body信息。
其內部利用上面創(chuàng)建的Exchange進行請求頭編寫,創(chuàng)建Request body,發(fā)送請求,得到結果后,對結果進行解析并回傳。
7、NetworkInterceptor
networkInterceptor也是屬于用戶自定義的一種攔截器,它的位置在ConnectInterceptor之后,CallServerInterceptor之前。我們知道第一個攔截器便是用戶自定義,那和這個有什么區(qū)別呢?
networkInterceptor前面已經(jīng)存在有多個攔截器的使用,在請求到達該攔截器時,請求信息已經(jīng)相當復雜了,其中就包括RetryAndFollowUpInterceptor重試攔截器,經(jīng)過分析知道,每當重試一次,其后面的攔截器也都會被調用一次,這樣就導致networkInterceptor也會被調用多次,而第一個自定義攔截器只會調用一次。當我們需要自定義攔截器時,如token、log,為了資源消耗這一點,一般都是使用第一個。
到這里為止,7種攔截器都分析完成。在分析ConnectInterceptor時拋出了一個問題:TCP/TLS連接是如何實現(xiàn)的?
如何建立TCP/TLS連接?
TCP連接
fun connect(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean,
call: Call,
eventListener: EventListener
) {
...
while (true) {
try {
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener)
if (rawSocket == null) {
// We were unable to connect the tunnel but properly closed down our resources.
break
}
} else {
connectSocket(connectTimeout, readTimeout, call, eventListener)
}
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener)
eventListener.connectEnd(call, route.socketAddress, route.proxy, protocol)
break
} catch (e: IOException) {
...
}
- 在connect的內部開啟了一個while循環(huán),可以看到第一步就是route.requiresTunnel()判斷,這個requiresTunnel()方法表示該請求是否使用了Proxy.Type.HTTP代理且目標是Https連接;
- 如果是,則創(chuàng)建一個代理隧道連接Tunnel(connectTunnel)。創(chuàng)建這個隧道的目的在于利用Http來代理請求Https;
- 如果不是,則直接建立一個TCP連接(connectSocket);
- 建立請求協(xié)議。
代理隧道是如何創(chuàng)建的?它的內部會先通過Http代理創(chuàng)建一個TLS的請求,也就是在地址url上增加Host、Proxy-Connection、User-Agent首部。接著最多21次的嘗試,利用connectSocket開啟TCP連接且利用TLS請求創(chuàng)建一個代理隧道。
從這里可以看見,不管是否需要代理隧道,都會開始建立一個TCP連接(connectSocket),那又是如何建立TCP連接的?
private fun connectSocket(
connectTimeout: Int,
readTimeout: Int,
call: Call,
eventListener: EventListener
) {
val proxy = route.proxy
val address = route.address
val rawSocket = when (proxy.type()) {
Proxy.Type.DIRECT, Proxy.Type.HTTP -> address.socketFactory.createSocket()!!
else -> Socket(proxy)
}
this.rawSocket = rawSocket
eventListener.connectStart(call, route.socketAddress, proxy)
rawSocket.soTimeout = readTimeout
try {
Platform.get().connectSocket(rawSocket, route.socketAddress, connectTimeout)
} catch (e: ConnectException) {
throw ConnectException("Failed to connect to ${route.socketAddress}").apply {
initCause(e)
}
}
...
}
從源碼上看,如果代理類型為直連或者HTTP/FTP代理,則直接創(chuàng)建一個socket,反之,則指定代理類型進行創(chuàng)建。我們看到創(chuàng)建后返回了一個rawSocket,這個就代表著TCP連接。在最后 調用Platform.get().connectSocket,而這實際就是調用socket的connect方法來打開一個TCP連接。
TLS連接
在建立TCP連接或者創(chuàng)建Http代理隧道后,就會開始建立連接協(xié)議(establishProtocol)。
private fun establishProtocol(
connectionSpecSelector: ConnectionSpecSelector,
pingIntervalMillis: Int,
call: Call,
eventListener: EventListener
) {
if (route.address.sslSocketFactory == null) {
if (Protocol.H2_PRIOR_KNOWLEDGE in route.address.protocols) {
socket = rawSocket
protocol = Protocol.H2_PRIOR_KNOWLEDGE
startHttp2(pingIntervalMillis)
return
}
socket = rawSocket
protocol = Protocol.HTTP_1_1
return
}
eventListener.secureConnectStart(call)
connectTls(connectionSpecSelector)
eventListener.secureConnectEnd(call, handshake)
if (protocol === Protocol.HTTP_2) {
startHttp2(pingIntervalMillis)
}
}
- 判斷當前地址是否是HTTPS;
- 如果不是HTTPS,則判斷當前協(xié)議是否是明文HTTP2,如果是的則調用startHttp2,開始Http2的握手動作,如果是Http/1.1則直接return返回;
- 如果是HTTPS,就開始建立TLS安全協(xié)議連接了(connectTls);
- 如果是HTTPS且為HTTP2,除了建立TLS連接外,還會調用startHttp2,開始Http2的握手動作。
在上述第3步時就提到了TLS的連接(connectTls),那我們就來看一下它的內部實現(xiàn):
private fun connectTls(connectionSpecSelector: ConnectionSpecSelector) {
val address = route.address
val sslSocketFactory = address.sslSocketFactory
var success = false
var sslSocket: SSLSocket? = null
try {
// Create the wrapper over the connected socket.
sslSocket = sslSocketFactory!!.createSocket(
rawSocket, address.url.host, address.url.port, true /* autoClose */) as SSLSocket
// Configure the socket's ciphers, TLS versions, and extensions.
val connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket)
if (connectionSpec.supportsTlsExtensions) {
Platform.get().configureTlsExtensions(sslSocket, address.url.host, address.protocols)
}
// Force handshake. This can throw!
sslSocket.startHandshake()
// block for session establishment
val sslSocketSession = sslSocket.session
val unverifiedHandshake = sslSocketSession.handshake()
// Verify that the socket's certificates are acceptable for the target host.
if (!address.hostnameVerifier!!.verify(address.url.host, sslSocketSession)) {
val peerCertificates = unverifiedHandshake.peerCertificates
if (peerCertificates.isNotEmpty()) {
val cert = peerCertificates[0] as X509Certificate
throw SSLPeerUnverifiedException("""
|Hostname ${address.url.host} not verified:
| certificate: ${CertificatePinner.pin(cert)}
| DN: ${cert.subjectDN.name}
| subjectAltNames: ${OkHostnameVerifier.allSubjectAltNames(cert)}
""".trimMargin())
} else {
throw SSLPeerUnverifiedException(
"Hostname ${address.url.host} not verified (no certificates)")
}
}
val certificatePinner = address.certificatePinner!!
handshake = Handshake(unverifiedHandshake.tlsVersion, unverifiedHandshake.cipherSuite,
unverifiedHandshake.localCertificates) {
certificatePinner.certificateChainCleaner!!.clean(unverifiedHandshake.peerCertificates,
address.url.host)
}
// Check that the certificate pinner is satisfied by the certificates presented.
certificatePinner.check(address.url.host) {
handshake!!.peerCertificates.map { it as X509Certificate }
}
// Success! Save the handshake and the ALPN protocol.
val maybeProtocol = if (connectionSpec.supportsTlsExtensions) {
Platform.get().getSelectedProtocol(sslSocket)
} else {
null
}
socket = sslSocket
source = sslSocket.source().buffer()
sink = sslSocket.sink().buffer()
protocol = if (maybeProtocol != null) Protocol.get(maybeProtocol) else Protocol.HTTP_1_1
success = true
} finally {
...
}
}
這段代碼很長,具體邏輯我就源碼總結了以下幾點:
- 利用請求地址host,端口以及TCP socket共同創(chuàng)建sslSocket;
- 為Socket 配置加密算法,TLS版本等;
- 調用startHandshake()進行強制握手;
- 驗證服務器證書的合法性;
- 利用握手記錄進行證書鎖定校驗(Pinner);
- 連接成功則保存握手記錄和ALPN協(xié)議。
Tsl加密連接的源碼內容其實與HTTPS所定義的客戶端與服務器通信的規(guī)則一致。創(chuàng)建好sslSocket后就會開始進行client和server的通信操作。
總結
OkHttp大致的請求實現(xiàn)如上面解析,跟著源碼走完了一個請求到處理再到返回結果的整個流程,期間OkHttp做了很多細節(jié)封裝,也使用了很多設計模式,如做核心的責任鏈模式、建造者模式、工廠模式以及策略模式等,都值得我們學習。
以上便是OkHttp的解析,希望這篇文章能幫到您,感謝閱讀。
參考資料
OkHttp源碼深度解析-OPPO互聯(lián)網(wǎng)技術