新聞中心
前言

創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供沛縣網(wǎng)站建設(shè)、沛縣做網(wǎng)站、沛縣網(wǎng)站設(shè)計(jì)、沛縣網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)與制作、沛縣企業(yè)網(wǎng)站模板建站服務(wù),十載沛縣做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
說到源碼,很多朋友都覺得復(fù)雜,難理解。
但是,如果是一個(gè)結(jié)構(gòu)清晰且完全解耦的優(yōu)質(zhì)源碼庫呢?
OkHttp就是這樣一個(gè)存在,對于這個(gè)原生網(wǎng)絡(luò)框架,想必大家也看過很多很多相關(guān)的源碼解析了。
它的源碼易讀,清晰。所以今天我準(zhǔn)備從設(shè)計(jì)模式的角度再來讀一遍 OkHttp的源碼。
主要內(nèi)容就分為兩類:
- OkHttp的基本運(yùn)作流程
- 涉及到的設(shè)計(jì)模式
(本文源碼版本為okhttp:4.9.0,攔截器會放到下期再講)
使用
讀源碼,首先就要從它的使用方法開始:
- val okHttpClient = OkHttpClient()
- val request: Request = Request.Builder()
- .url(url)
- .build()
- okHttpClient.newCall(request).enqueue(object : Callback {
- override fun onFailure(call: Call, e: IOException) {
- Log.d(TAG, "onFailure: ")
- }
- override fun onResponse(call: Call, response: Response) {
- Log.d(TAG, "onResponse: " + response.body?.string())
- }
- })
從這個(gè)使用方法來看,我抽出了四個(gè)重要信息:
- okHttpClient
- Request
- newCall(request)
- enqueue(Callback)
大體意思我們可以先猜猜看:
配置一個(gè)客戶端實(shí)例okHttpClient和一個(gè)Request請求,然后這個(gè)請求通過okHttpClient的newCall方法封裝,最后用enqueue方法發(fā)送出去,并收到Callback響應(yīng)。
接下來就一個(gè)個(gè)去認(rèn)證,并找找其中的設(shè)計(jì)模式。
okHttpClient
首先看看這個(gè)okhttp的客戶端對象,也就是okHttpClient。
- OkHttpClient client = new OkHttpClient.Builder()
- .addInterceptor(new HttpLoggingInterceptor())
- .readTimeout(500, TimeUnit.MILLISECONDS)
- .build();
在這里,我們實(shí)例化了一個(gè)HTTP的客戶端client,然后配置了它的一些參數(shù),比如攔截器、超時(shí)時(shí)間。
這種我們通過一個(gè)統(tǒng)一的對象,調(diào)用一個(gè)接口或方法,就能完成我們的需求,而起內(nèi)部的各種復(fù)雜對象的調(diào)用和跳轉(zhuǎn)都不需要我們關(guān)心的設(shè)計(jì)模式就是外觀模式(門面模式)。
外觀模式(Facade Pattern)隱藏系統(tǒng)的復(fù)雜性,并向客戶端提供了一個(gè)客戶端可以訪問系統(tǒng)的接口。這種類型的設(shè)計(jì)模式屬于結(jié)構(gòu)型模式,它向現(xiàn)有的系統(tǒng)添加一個(gè)接口,來隱藏系統(tǒng)的復(fù)雜性。
其重點(diǎn)就在于系統(tǒng)內(nèi)部和各個(gè)子系統(tǒng)之間的復(fù)雜關(guān)系我們不需要了解,只需要去差遣這個(gè)門面 就可以了,在這里也就是OkHttpClient。
它的存在就像一個(gè)接待員,我們告訴它我們的需求,要做的事情。然后接待員去內(nèi)部處理,各種調(diào)度,最終完成。
外觀模式主要解決的就是降低訪問復(fù)雜系統(tǒng)的內(nèi)部子系統(tǒng)時(shí)的復(fù)雜度,簡化客戶端與之的接口。
這個(gè)模式也是三方庫很常用的設(shè)計(jì)模式,給你一個(gè)對象,你只需要對這個(gè)對象使喚,就可以完成需求。
當(dāng)然,這里還有一個(gè)比較明顯的設(shè)計(jì)模式是建造者模式,下面會說到。
Request
- val request: Request = Request.Builder()
- .url(url)
- .build()
- //Request.kt
- open class Builder {
- internal var url: HttpUrl? = null
- internal var method: String
- internal var headers: Headers.Builder
- internal var body: RequestBody? = null
- constructor() {
- this.method = "GET"
- this.headers = Headers.Builder()
- }
- open fun build(): Request {
- return Request(
- checkNotNull(url) { "url == null" },
- method,
- headers.build(),
- body,
- tags.toImmutableMap()
- )
- }
- }
從Request的生成代碼中可以看到,用到了其內(nèi)部類Builder,然后通過Builder類組裝出了一個(gè)完整的有著各種參數(shù)的Request類。
這也就是典型的 建造者(Builder)模式 。
建造者(Builder)模式,將一個(gè)復(fù)雜的對象的構(gòu)建與它的表示分離,是的同樣的構(gòu)建過程可以創(chuàng)建不同的表示。
我們可以通過Builder,構(gòu)建了不同的Request請求,只需要傳入不同的請求地址url,請求方法method,頭部信息headers,請求體body即可。(這也就是網(wǎng)絡(luò)請求中的請求報(bào)文的格式)
這種可以通過構(gòu)建形成不同的表示的 設(shè)計(jì)模式 就是 建造者模式,也是用的很多,主要為了方便我們傳入不同的參數(shù)進(jìn)行構(gòu)建對象。
又比如上面okHttpClient的構(gòu)建。
newCall(request)
接下來是調(diào)用OkHttpClient類的newCall方法獲取一個(gè)可以去調(diào)用enqueue方法的接口。
- //使用
- val okHttpClient = OkHttpClient()
- okHttpClient.newCall(request)
- //OkHttpClient.kt
- open class OkHttpClient internal constructor(builder: Builder) : Cloneable, Call.Factory, WebSocket.Factory {
- override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)
- }
- //Call接口
- interface Call : Cloneable {
- fun execute(): Response
- fun enqueue(responseCallback: Callback)
- fun interface Factory {
- fun newCall(request: Request): Call
- }
- }
newCall方法,其實(shí)是Call.Factory接口里面的方法。
也就是創(chuàng)建Call的過程,是通過Call.Factory接口的newCall方法創(chuàng)建的,而真正實(shí)現(xiàn)這個(gè)方法交給了這個(gè)接口的子類OkHttpClient。
那這種定義了統(tǒng)一創(chuàng)建對象的接口,然后由子類來決定實(shí)例化這個(gè)對象的設(shè)計(jì)模式就是 工廠模式。
在工廠模式中,我們在創(chuàng)建對象時(shí)不會對客戶端暴露創(chuàng)建邏輯,并且是通過使用一個(gè)共同的接口來指向新創(chuàng)建的對象。
當(dāng)然,okhttp這里的工廠有點(diǎn)小,只有一條生產(chǎn)線,就是Call接口,而且只有一個(gè)產(chǎn)品,RealCall。
enqueue(Callback)
接下來這個(gè)方法enqueue,肯定就是okhttp源碼的重中之重了,剛才說到newCall方法其實(shí)是獲取了RealCall對象,所以就走到了RealCall的enqueue方法:
- override fun enqueue(responseCallback: Callback) {
- client.dispatcher.enqueue(AsyncCall(responseCallback))
- }
再轉(zhuǎn)向dispatcher。
- //Dispatcher.kt
- val executorService: ExecutorService
- get() {
- if (executorServiceOrNull == null) {
- executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
- SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
- }
- return executorServiceOrNull!!
- }
- internal fun enqueue(call: AsyncCall) {
- promoteAndExecute()
- }
- private fun promoteAndExecute(): Boolean {
- //通過線程池切換線程
- for (i in 0 until executableCalls.size) {
- val asyncCall = executableCalls[i]
- asyncCall.executeOn(executorService)
- }
- return isRunning
- }
- //RealCall.kt
- fun executeOn(executorService: ExecutorService) {
- try {
- executorService.execute(this)
- success = true
- }
- }
這里用到了一個(gè)新的類Dispatcher,調(diào)用到的方法是asyncCall.executeOn(executorService)
這個(gè)executorService參數(shù)大家應(yīng)該都熟悉吧,線程池。最后是調(diào)用executorService.execute方法執(zhí)行線程池任務(wù)。
而線程池的概念其實(shí)也是用到了一種設(shè)計(jì)模式,叫做享元模式。
享元模式(Flyweight Pattern)主要用于減少創(chuàng)建對象的數(shù)量,以減少內(nèi)存占用和提高性能。這種類型的設(shè)計(jì)模式屬于結(jié)構(gòu)型模式,它提供了減少對象數(shù)量從而改善應(yīng)用所需的對象結(jié)構(gòu)的方式。
其核心就在于共享對象,所有很多的池類對象,比如線程池、連接池等都是采用了享元模式 這一設(shè)計(jì)模式。當(dāng)然,okhttp中不止是有線程池,還有連接池提供連接復(fù)用,管理所有的socket連接。
再回到Dispatcher,所以這個(gè)類是干嘛的呢?就是切換線程用的,因?yàn)槲覀冋{(diào)用的enqueue是異步方法,所以最后會用到線程池切換線程,執(zhí)行任務(wù)。
繼續(xù)看看execute(this)中的this任務(wù)。
execute(this)
- override fun run() {
- threadName("OkHttp ${redactedUrl()}") {
- try {
- //獲取響應(yīng)報(bào)文,并回調(diào)給Callback
- val response = getResponseWithInterceptorChain()
- responseCallback.onResponse(this@RealCall, response)
- } catch (e: IOException) {
- if (!signalledCallback) {
- responseCallback.onFailure(this@RealCall, e)
- }
- } catch (t: Throwable) {
- cancel()
- if (!signalledCallback) {
- responseCallback.onFailure(this@RealCall, canceledException)
- }
- }
- }
沒錯(cuò),這里就是請求接口的地方了,通過getResponseWithInterceptorChain方法獲取響應(yīng)報(bào)文response,然后通過Callback的onResponse方法回調(diào),或者是有異常就通過onFailure方法回調(diào)。
那同步方法是不是就沒用到線程池呢?去找找execute方法:
- override fun execute(): Response {
- //...
- return getResponseWithInterceptorChain()
- }
果然,通過execute方法就直接返回了getResponseWithInterceptorChain,也就是響應(yīng)報(bào)文。
到這里,okhttp的大體流程就結(jié)束了,這部分的流程大概就是:
設(shè)置請求報(bào)文 -> 配置客戶端參數(shù) -> 根據(jù)同步或異步判斷是否用子線程 -> 發(fā)起請求并獲取響應(yīng)報(bào)文 -> 通過Callback接口回調(diào)結(jié)果
剩下的內(nèi)容就全部在getResponseWithInterceptorChain方法中,這也就是okhttp的核心。
getResponseWithInterceptorChain
- internal fun getResponseWithInterceptorChain(): Response {
- // Build a full stack of interceptors.
- val interceptors = mutableListOf
() - 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(
- interceptors = interceptors
- //...
- )
- val response = chain.proceed(originalRequest)
- }
代碼不是很復(fù)雜,就是 加加加 攔截器,然后組裝成一個(gè)chain類,調(diào)用proceed方法,得到響應(yīng)報(bào)文response。
- override fun proceed(request: Request): Response {
- //找到下一個(gè)攔截器
- val next = copy(index = index + 1, request = request)
- val interceptor = interceptors[index]
- val response = interceptor.intercept(next)
- return response
- }
簡化了下代碼,主要邏輯就是獲取下一個(gè)攔截器(index+1),然后調(diào)用攔截器的intercept方法。
然后在攔截器里面的代碼統(tǒng)一都是這種格式:
- override fun intercept(chain: Interceptor.Chain): Response {
- //做事情A
- response = realChain.proceed(request)
- //做事情B
- }
結(jié)合兩段代碼,會形成一條鏈,這條鏈組織了所有連接器的工作。類似這樣:
攔截器1做事情A -> 攔截器2做事情A -> 攔截器3做事情A -> 攔截器3做事情B -> 攔截器2做事情B -> 攔截器1做事情B
應(yīng)該是好理解的吧,通過proceed方法把每個(gè)攔截器連接起來了。
而最后一個(gè)攔截器ConnectInterceptor就是分割事情A和事情B,其作用就是進(jìn)行真正的與服務(wù)器的通信,向服務(wù)器發(fā)送數(shù)據(jù),解析讀取的響應(yīng)數(shù)據(jù)。
所以事情A和事情B是什么意思呢?其實(shí)就代表了通信之前的事情和通信之后的事情。
再來個(gè)動畫:
這種思想是不是有點(diǎn)像..遞歸?沒錯(cuò),就是遞歸,先遞進(jìn)執(zhí)行事情A,再回歸做事情B。
而這種遞歸循環(huán),其實(shí)也就是用到了設(shè)計(jì)模式中的 責(zé)任鏈模式。
責(zé)任鏈模式(Chain of Responsibility Pattern)為請求創(chuàng)建了一個(gè)接收者對象的鏈。這種模式給予請求的類型,對請求的發(fā)送者和接收者進(jìn)行解耦。
簡單的說,就是讓每個(gè)對象都能有機(jī)會處理這個(gè)請求,然后各自完成自己的事情,一直到事件被處理。Android中的事件分發(fā)機(jī)制也是用到了這種設(shè)計(jì)模式。
接下來就是了解每個(gè)攔截器到底做了什么事,就可以了解到okhttp的整個(gè)流程了,這就是下期的內(nèi)容了。
先預(yù)告一波:
- addInterceptor(Interceptor),這是由開發(fā)者設(shè)置的,會按照開發(fā)者的要求,在所有的攔截器處理之前進(jìn)行最早的攔截處理,比如一些公共參數(shù),Header都可以在這里添加。
- RetryAndFollowUpInterceptor,這里會對連接做一些初始化工作,以及請求失敗的重試工作,重定向的后續(xù)請求工作。
- BridgeInterceptor,這里會為用戶構(gòu)建一個(gè)能夠進(jìn)行網(wǎng)絡(luò)訪問的請求,同時(shí)后續(xù)工作將網(wǎng)絡(luò)請求回來的響應(yīng)Response轉(zhuǎn)化為用戶可用的Response,比如添加文件類型,content-length計(jì)算添加,gzip解包。
- CacheInterceptor,這里主要是處理cache相關(guān)處理,會根據(jù)OkHttpClient對象的配置以及緩存策略對請求值進(jìn)行緩存,而且如果本地有了可?的Cache,就可以在沒有網(wǎng)絡(luò)交互的情況下就返回緩存結(jié)果。
- ConnectInterceptor,這里主要就是負(fù)責(zé)建立連接了,會建立TCP連接或者TLS連接,以及負(fù)責(zé)編碼解碼的HttpCodec。
- networkInterceptors,這里也是開發(fā)者自己設(shè)置的,所以本質(zhì)上和第一個(gè)攔截器差不多,但是由于位置不同,用處也不同。這個(gè)位置添加的攔截器可以看到請求和響應(yīng)的數(shù)據(jù)了,所以可以做一些網(wǎng)絡(luò)調(diào)試。
- CallServerInterceptor,這里就是進(jìn)行網(wǎng)絡(luò)數(shù)據(jù)的請求和響應(yīng)了,也就是實(shí)際的網(wǎng)絡(luò)I/O操作,通過socket讀寫數(shù)據(jù)。
總結(jié)
讀完okhttp的源碼,感覺就一個(gè)字:舒服。
一份好的代碼應(yīng)該就是這樣,各模塊之間通過各種設(shè)計(jì)模式進(jìn)行解耦,閱讀者可以每個(gè)模塊分別去去閱讀了解,而不是各個(gè)模塊纏綿在一起,雜亂無章。
最后再總結(jié)下okhttp中涉及到的設(shè)計(jì)模式:
- 外觀模式。通過okHttpClient這個(gè)外觀去實(shí)現(xiàn)內(nèi)部各種功能。
- 建造者模式。構(gòu)建不同的Request對象。
- 工廠模式。通過OkHttpClient生產(chǎn)出產(chǎn)品RealCall。
- 享元模式。通過線程池、連接池共享對象。
- 責(zé)任鏈模式。將不同功能的攔截器形成一個(gè)鏈。
其實(shí)還是有一些設(shè)計(jì)模式?jīng)]說到的,比如
- websocket相關(guān)用到的觀察者模式。
- Cache集合相關(guān)的迭代器模式。
- 以后遇到了再做補(bǔ)充吧。
參考https://www.runoob.com/design-pattern/design-pattern-tutorial.html https://www.jianshu.com/p/ae2fe5481994 https://juejin.cn/post/6895369745445748749
本文轉(zhuǎn)載自微信公眾號「碼上積木」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系碼上積木學(xué)公眾號。
網(wǎng)頁名稱:從設(shè)計(jì)模式看OkHttp源碼
網(wǎng)站網(wǎng)址:http://www.5511xx.com/article/dhoedop.html


咨詢
建站咨詢
