自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

從設(shè)計模式看OkHttp源碼

開發(fā) 前端
說到源碼,很多朋友都覺得復(fù)雜,難理解。但是,如果是一個結(jié)構(gòu)清晰且完全解耦的優(yōu)質(zhì)源碼庫呢?OkHttp就是這樣一個存在,對于這個原生網(wǎng)絡(luò)框架,想必大家也看過很多很多相關(guān)的源碼解析了。

[[386497]]

前言

說到源碼,很多朋友都覺得復(fù)雜,難理解。

但是,如果是一個結(jié)構(gòu)清晰且完全解耦的優(yōu)質(zhì)源碼庫呢?

OkHttp就是這樣一個存在,對于這個原生網(wǎng)絡(luò)框架,想必大家也看過很多很多相關(guān)的源碼解析了。

它的源碼易讀,清晰。所以今天我準備從設(shè)計模式的角度再來讀一遍 OkHttp的源碼。

主要內(nèi)容就分為兩類:

  • OkHttp的基本運作流程
  • 涉及到的設(shè)計模式

(本文源碼版本為okhttp:4.9.0,攔截器會放到下期再講)

使用

讀源碼,首先就要從它的使用方法開始:

  1. val okHttpClient = OkHttpClient() 
  2.    val request: Request = Request.Builder() 
  3.        .url(url) 
  4.        .build() 
  5.    okHttpClient.newCall(request).enqueue(object : Callback { 
  6.        override fun onFailure(call: Call, e: IOException) { 
  7.            Log.d(TAG, "onFailure: "
  8.        } 
  9.  
  10.        override fun onResponse(call: Call, response: Response) { 
  11.            Log.d(TAG, "onResponse: " + response.body?.string()) 
  12.        } 
  13.    }) 

從這個使用方法來看,我抽出了四個重要信息:

  • okHttpClient
  • Request
  • newCall(request)
  • enqueue(Callback)

大體意思我們可以先猜猜看:

配置一個客戶端實例okHttpClient和一個Request請求,然后這個請求通過okHttpClient的newCall方法封裝,最后用enqueue方法發(fā)送出去,并收到Callback響應(yīng)。

接下來就一個個去認證,并找找其中的設(shè)計模式。

okHttpClient

首先看看這個okhttp的客戶端對象,也就是okHttpClient。

  1. OkHttpClient client = new OkHttpClient.Builder() 
  2.         .addInterceptor(new HttpLoggingInterceptor())  
  3.         .readTimeout(500, TimeUnit.MILLISECONDS) 
  4.         .build(); 

在這里,我們實例化了一個HTTP的客戶端client,然后配置了它的一些參數(shù),比如攔截器、超時時間。

這種我們通過一個統(tǒng)一的對象,調(diào)用一個接口或方法,就能完成我們的需求,而起內(nèi)部的各種復(fù)雜對象的調(diào)用和跳轉(zhuǎn)都不需要我們關(guān)心的設(shè)計模式就是外觀模式(門面模式)。

外觀模式(Facade Pattern)隱藏系統(tǒng)的復(fù)雜性,并向客戶端提供了一個客戶端可以訪問系統(tǒng)的接口。這種類型的設(shè)計模式屬于結(jié)構(gòu)型模式,它向現(xiàn)有的系統(tǒng)添加一個接口,來隱藏系統(tǒng)的復(fù)雜性。

其重點就在于系統(tǒng)內(nèi)部和各個子系統(tǒng)之間的復(fù)雜關(guān)系我們不需要了解,只需要去差遣這個門面 就可以了,在這里也就是OkHttpClient。

它的存在就像一個接待員,我們告訴它我們的需求,要做的事情。然后接待員去內(nèi)部處理,各種調(diào)度,最終完成。

外觀模式主要解決的就是降低訪問復(fù)雜系統(tǒng)的內(nèi)部子系統(tǒng)時的復(fù)雜度,簡化客戶端與之的接口。

這個模式也是三方庫很常用的設(shè)計模式,給你一個對象,你只需要對這個對象使喚,就可以完成需求。

當然,這里還有一個比較明顯的設(shè)計模式是建造者模式,下面會說到。

Request

  1. val request: Request = Request.Builder() 
  2.     .url(url) 
  3.     .build() 
  4.  
  5. //Request.kt 
  6. open class Builder { 
  7.     internal var url: HttpUrl? = null 
  8.     internal var method: String 
  9.     internal var headers: Headers.Builder 
  10.     internal var body: RequestBody? = null 
  11.  
  12.     constructor() { 
  13.       this.method = "GET" 
  14.       this.headers = Headers.Builder() 
  15.     } 
  16.  
  17.     open fun build(): Request { 
  18.       return Request( 
  19.           checkNotNull(url) { "url == null" }, 
  20.           method, 
  21.           headers.build(), 
  22.           body, 
  23.           tags.toImmutableMap() 
  24.       ) 
  25.     } 

從Request的生成代碼中可以看到,用到了其內(nèi)部類Builder,然后通過Builder類組裝出了一個完整的有著各種參數(shù)的Request類。

這也就是典型的 建造者(Builder)模式 。

建造者(Builder)模式,將一個復(fù)雜的對象的構(gòu)建與它的表示分離,是的同樣的構(gòu)建過程可以創(chuàng)建不同的表示。

我們可以通過Builder,構(gòu)建了不同的Request請求,只需要傳入不同的請求地址url,請求方法method,頭部信息headers,請求體body即可。(這也就是網(wǎng)絡(luò)請求中的請求報文的格式)

這種可以通過構(gòu)建形成不同的表示的 設(shè)計模式 就是 建造者模式,也是用的很多,主要為了方便我們傳入不同的參數(shù)進行構(gòu)建對象。

又比如上面okHttpClient的構(gòu)建。

newCall(request)

接下來是調(diào)用OkHttpClient類的newCall方法獲取一個可以去調(diào)用enqueue方法的接口。

  1. //使用 
  2. val okHttpClient = OkHttpClient() 
  3. okHttpClient.newCall(request) 
  4.  
  5. //OkHttpClient.kt 
  6. open class OkHttpClient internal constructor(builder: Builder) : Cloneable, Call.Factory, WebSocket.Factory { 
  7.   override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false
  8.  
  9. //Call接口 
  10. interface Call : Cloneable { 
  11.   fun execute(): Response 
  12.  
  13.   fun enqueue(responseCallback: Callback) 
  14.  
  15.   fun interface Factory { 
  16.     fun newCall(request: Request): Call 
  17.   } 

newCall方法,其實是Call.Factory接口里面的方法。

也就是創(chuàng)建Call的過程,是通過Call.Factory接口的newCall方法創(chuàng)建的,而真正實現(xiàn)這個方法交給了這個接口的子類OkHttpClient。

那這種定義了統(tǒng)一創(chuàng)建對象的接口,然后由子類來決定實例化這個對象的設(shè)計模式就是 工廠模式。

在工廠模式中,我們在創(chuàng)建對象時不會對客戶端暴露創(chuàng)建邏輯,并且是通過使用一個共同的接口來指向新創(chuàng)建的對象。

當然,okhttp這里的工廠有點小,只有一條生產(chǎn)線,就是Call接口,而且只有一個產(chǎn)品,RealCall。

enqueue(Callback)

接下來這個方法enqueue,肯定就是okhttp源碼的重中之重了,剛才說到newCall方法其實是獲取了RealCall對象,所以就走到了RealCall的enqueue方法:

  1. override fun enqueue(responseCallback: Callback) { 
  2.   client.dispatcher.enqueue(AsyncCall(responseCallback)) 

再轉(zhuǎn)向dispatcher。

  1. //Dispatcher.kt 
  2.  
  3.   val executorService: ExecutorService 
  4.     get() { 
  5.       if (executorServiceOrNull == null) { 
  6.         executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS, 
  7.             SynchronousQueue(), threadFactory("$okHttpName Dispatcher"false)) 
  8.       } 
  9.       return executorServiceOrNull!! 
  10.     } 
  11.  
  12.  
  13.   internal fun enqueue(call: AsyncCall) { 
  14.     promoteAndExecute() 
  15.   } 
  16.  
  17.  
  18.   private fun promoteAndExecute(): Boolean { 
  19.     //通過線程池切換線程 
  20.     for (i in 0 until executableCalls.size) { 
  21.       val asyncCall = executableCalls[i] 
  22.       asyncCall.executeOn(executorService) 
  23.     } 
  24.  
  25.     return isRunning 
  26.   } 
  27.  
  28.  
  29. //RealCall.kt 
  30.   fun executeOn(executorService: ExecutorService) { 
  31.  
  32.       try { 
  33.         executorService.execute(this) 
  34.         success = true 
  35.       }  
  36.     } 

這里用到了一個新的類Dispatcher,調(diào)用到的方法是asyncCall.executeOn(executorService)

這個executorService參數(shù)大家應(yīng)該都熟悉吧,線程池。最后是調(diào)用executorService.execute方法執(zhí)行線程池任務(wù)。

而線程池的概念其實也是用到了一種設(shè)計模式,叫做享元模式。

享元模式(Flyweight Pattern)主要用于減少創(chuàng)建對象的數(shù)量,以減少內(nèi)存占用和提高性能。這種類型的設(shè)計模式屬于結(jié)構(gòu)型模式,它提供了減少對象數(shù)量從而改善應(yīng)用所需的對象結(jié)構(gòu)的方式。

其核心就在于共享對象,所有很多的池類對象,比如線程池、連接池等都是采用了享元模式 這一設(shè)計模式。當然,okhttp中不止是有線程池,還有連接池提供連接復(fù)用,管理所有的socket連接。

再回到Dispatcher,所以這個類是干嘛的呢?就是切換線程用的,因為我們調(diào)用的enqueue是異步方法,所以最后會用到線程池切換線程,執(zhí)行任務(wù)。

繼續(xù)看看execute(this)中的this任務(wù)。

execute(this)

  1. override fun run() { 
  2.       threadName("OkHttp ${redactedUrl()}") { 
  3.         try { 
  4.           //獲取響應(yīng)報文,并回調(diào)給Callback 
  5.           val response = getResponseWithInterceptorChain() 
  6.           responseCallback.onResponse(this@RealCall, response) 
  7.         } catch (e: IOException) { 
  8.           if (!signalledCallback) { 
  9.             responseCallback.onFailure(this@RealCall, e) 
  10.           }  
  11.         } catch (t: Throwable) { 
  12.           cancel() 
  13.           if (!signalledCallback) { 
  14.              
  15.             responseCallback.onFailure(this@RealCall, canceledException) 
  16.           } 
  17.         }  
  18.       } 

沒錯,這里就是請求接口的地方了,通過getResponseWithInterceptorChain方法獲取響應(yīng)報文response,然后通過Callback的onResponse方法回調(diào),或者是有異常就通過onFailure方法回調(diào)。

那同步方法是不是就沒用到線程池呢?去找找execute方法:

  1. override fun execute(): Response { 
  2.   //... 
  3.   return getResponseWithInterceptorChain() 

果然,通過execute方法就直接返回了getResponseWithInterceptorChain,也就是響應(yīng)報文。

到這里,okhttp的大體流程就結(jié)束了,這部分的流程大概就是:

設(shè)置請求報文 -> 配置客戶端參數(shù) -> 根據(jù)同步或異步判斷是否用子線程 -> 發(fā)起請求并獲取響應(yīng)報文 -> 通過Callback接口回調(diào)結(jié)果

剩下的內(nèi)容就全部在getResponseWithInterceptorChain方法中,這也就是okhttp的核心。

getResponseWithInterceptorChain

  1. internal fun getResponseWithInterceptorChain(): Response { 
  2.     // Build a full stack of interceptors. 
  3.     val interceptors = mutableListOf<Interceptor>() 
  4.     interceptors += client.interceptors 
  5.     interceptors += RetryAndFollowUpInterceptor(client) 
  6.     interceptors += BridgeInterceptor(client.cookieJar) 
  7.     interceptors += CacheInterceptor(client.cache) 
  8.     interceptors += ConnectInterceptor 
  9.     if (!forWebSocket) { 
  10.       interceptors += client.networkInterceptors 
  11.     } 
  12.     interceptors += CallServerInterceptor(forWebSocket) 
  13.  
  14.     val chain = RealInterceptorChain( 
  15.         interceptors = interceptors 
  16.         //... 
  17.     ) 
  18.  
  19.     val response = chain.proceed(originalRequest) 
  20.   } 

代碼不是很復(fù)雜,就是 加加加 攔截器,然后組裝成一個chain類,調(diào)用proceed方法,得到響應(yīng)報文response。

  1. override fun proceed(request: Request): Response { 
  2.  
  3.   //找到下一個攔截器 
  4.   val next = copy(index = index + 1, request = request) 
  5.   val interceptor = interceptors[index
  6.  
  7.   
  8.   val response = interceptor.intercept(next
  9.   return response 

簡化了下代碼,主要邏輯就是獲取下一個攔截器(index+1),然后調(diào)用攔截器的intercept方法。

然后在攔截器里面的代碼統(tǒng)一都是這種格式:

  1. override fun intercept(chain: Interceptor.Chain): Response { 
  2.    //做事情A 
  3.  
  4.    response = realChain.proceed(request) 
  5.  
  6.    //做事情B 
  7.  } 

結(jié)合兩段代碼,會形成一條鏈,這條鏈組織了所有連接器的工作。類似這樣:

攔截器1做事情A -> 攔截器2做事情A -> 攔截器3做事情A -> 攔截器3做事情B -> 攔截器2做事情B -> 攔截器1做事情B

應(yīng)該是好理解的吧,通過proceed方法把每個攔截器連接起來了。

而最后一個攔截器ConnectInterceptor就是分割事情A和事情B,其作用就是進行真正的與服務(wù)器的通信,向服務(wù)器發(fā)送數(shù)據(jù),解析讀取的響應(yīng)數(shù)據(jù)。

所以事情A和事情B是什么意思呢?其實就代表了通信之前的事情和通信之后的事情。

再來個動畫:

 

這種思想是不是有點像..遞歸?沒錯,就是遞歸,先遞進執(zhí)行事情A,再回歸做事情B。

而這種遞歸循環(huán),其實也就是用到了設(shè)計模式中的 責任鏈模式。

責任鏈模式(Chain of Responsibility Pattern)為請求創(chuàng)建了一個接收者對象的鏈。這種模式給予請求的類型,對請求的發(fā)送者和接收者進行解耦。

簡單的說,就是讓每個對象都能有機會處理這個請求,然后各自完成自己的事情,一直到事件被處理。Android中的事件分發(fā)機制也是用到了這種設(shè)計模式。

接下來就是了解每個攔截器到底做了什么事,就可以了解到okhttp的整個流程了,這就是下期的內(nèi)容了。

先預(yù)告一波:

  • addInterceptor(Interceptor),這是由開發(fā)者設(shè)置的,會按照開發(fā)者的要求,在所有的攔截器處理之前進行最早的攔截處理,比如一些公共參數(shù),Header都可以在這里添加。
  • RetryAndFollowUpInterceptor,這里會對連接做一些初始化工作,以及請求失敗的重試工作,重定向的后續(xù)請求工作。
  • BridgeInterceptor,這里會為用戶構(gòu)建一個能夠進行網(wǎng)絡(luò)訪問的請求,同時后續(xù)工作將網(wǎng)絡(luò)請求回來的響應(yīng)Response轉(zhuǎn)化為用戶可用的Response,比如添加文件類型,content-length計算添加,gzip解包。
  • CacheInterceptor,這里主要是處理cache相關(guān)處理,會根據(jù)OkHttpClient對象的配置以及緩存策略對請求值進行緩存,而且如果本地有了可⽤的Cache,就可以在沒有網(wǎng)絡(luò)交互的情況下就返回緩存結(jié)果。
  • ConnectInterceptor,這里主要就是負責建立連接了,會建立TCP連接或者TLS連接,以及負責編碼解碼的HttpCodec。
  • networkInterceptors,這里也是開發(fā)者自己設(shè)置的,所以本質(zhì)上和第一個攔截器差不多,但是由于位置不同,用處也不同。這個位置添加的攔截器可以看到請求和響應(yīng)的數(shù)據(jù)了,所以可以做一些網(wǎng)絡(luò)調(diào)試。
  • CallServerInterceptor,這里就是進行網(wǎng)絡(luò)數(shù)據(jù)的請求和響應(yīng)了,也就是實際的網(wǎng)絡(luò)I/O操作,通過socket讀寫數(shù)據(jù)。

總結(jié)

讀完okhttp的源碼,感覺就一個字:舒服。

一份好的代碼應(yīng)該就是這樣,各模塊之間通過各種設(shè)計模式進行解耦,閱讀者可以每個模塊分別去去閱讀了解,而不是各個模塊纏綿在一起,雜亂無章。

最后再總結(jié)下okhttp中涉及到的設(shè)計模式:

  • 外觀模式。通過okHttpClient這個外觀去實現(xiàn)內(nèi)部各種功能。
  • 建造者模式。構(gòu)建不同的Request對象。
  • 工廠模式。通過OkHttpClient生產(chǎn)出產(chǎn)品RealCall。
  • 享元模式。通過線程池、連接池共享對象。
  • 責任鏈模式。將不同功能的攔截器形成一個鏈。

其實還是有一些設(shè)計模式?jīng)]說到的,比如

  • websocket相關(guān)用到的觀察者模式。
  • Cache集合相關(guān)的迭代器模式。
  • 以后遇到了再做補充吧。

參考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)系碼上積木學公眾號。

 

責任編輯:武曉燕 來源: 碼上積木
相關(guān)推薦

2013-06-07 11:31:36

面向?qū)ο?/a>設(shè)計模式

2021-08-14 08:17:49

Android設(shè)計模式OKHttp

2021-07-14 09:48:15

Linux源碼Epoll

2017-09-25 16:21:30

Spark on yacluster模式

2018-02-02 15:48:47

ChromeDNS解析

2021-07-15 14:27:47

LinuxSocketClose

2021-04-19 21:25:48

設(shè)計模式到元

2019-04-28 16:10:50

設(shè)計Redux前端

2017-04-05 20:00:32

ChromeObjectJS代碼

2021-06-10 09:52:33

LinuxTCPAccept

2020-10-10 07:00:16

LinuxSocketTCP

2020-09-23 12:32:18

網(wǎng)絡(luò)IOMySQL

2022-10-08 08:01:17

Spring源碼服務(wù)

2020-09-07 14:30:37

JUC源碼CAS

2023-03-13 17:18:09

OkHttp同步異步

2022-03-21 10:21:50

jQuery代碼模式

2021-12-30 08:55:41

Log4j2FastJson漏洞

2017-02-09 15:15:54

Chrome瀏覽器

2017-02-28 10:05:56

Chrome源碼

2017-11-21 14:56:59

點贊
收藏

51CTO技術(shù)棧公眾號