征服面試官:OkHttp 原理篇 掌握這篇面試題彙總,吊打面試官!

Jaynm發表於2020-12-31

前言

如今面試中高階開發工程師崗位,OKhttp 原理是必問環節,只會使用已經無法滿足 Android 開發市場的需求,優秀的第三方框架原始碼剖析不僅能深度理解框架,也能對自己學習帶來很大的幫助。

本篇文章根據朋友反饋和親身經歷簡單整理的一些關於 Okhttp 常見面試題目。

1.Okhttp 基本實現原理

OkHttp 主要是通過 5 個攔截器和 3 個雙端佇列(2 個非同步佇列,1 個同步佇列)工作。內部實現通過一個責任鏈模式完成,將網路請求的各個階段封裝到各個鏈條中,實現了各層的解耦。

OkHttp 的底層是通過 Socket 傳送 HTTP 請求與接受響應,但是 OkHttp 實現了連線池的概念,即對於同一主機的多個請求,可以公用一個 Socket 連線,而不是每次傳送完 HTTP 請求就關閉底層的 Socket,這樣就實現了連線池的概念。而 OkHttp 對 Socket 的讀寫操作使用的 OkIo 庫進行了一層封裝。

執行流程

  • 通過構建者構建出OkHttpClient物件,再通過newCall方法獲得RealCall請求物件.

  • 通過RealCall發起同步或非同步請求,而決定是非同步還是同步請求的是由執行緒分發器dispatcher來決定.

  • 當發起同步請求時會將請求加入到同步佇列中依次執行,所以會阻塞UI執行緒,需要開啟子執行緒執行.

  • 當發起非同步請求時會建立一個執行緒池,並且判斷請求佇列是否大於最大請求佇列64,請求主機數是否大於5,如果大於請求新增到非同步等待佇列中,否則新增到非同步執行佇列,並執行任務.

2.Okhttp 網路快取如何實現?

OKHttp 預設只支援 get 請求的快取。

  • 第一次拿到響應後根據頭資訊決定是否快取。
  • 下次請求時判斷是否存在本地快取,是否需要使用對比快取、封裝請求頭資訊等等。
  • 如果快取失效或者需要對比快取則發出網路請求,否則使用本地快取。

3.Okhttp 網路連線怎麼實現複用?

HttpEngine 在發起請求之前,會先呼叫nextConnection()來獲取一個Connection物件,如果可以從ConnectionPool中獲取一個Connection物件,就不會新建,如果無法獲取,就會呼叫createnextConnection()來新建一個Connection物件,這就是 Okhttp 多路複用的核心,不像之前的網路框架,無論有沒有,都會新建Connection物件。

image

4.Dispatcher 的功能是什麼?

Dispatcher中文是分發器的意思,和攔截器不同的是分發器不做事件處理,只做事件流向。他負責將每一次Requst進行分發,壓棧到自己的執行緒池,並通過呼叫者自己不同的方式進行非同步和同步處理。 通俗的講就是主要維護任務佇列的作用

  • 記錄同步任務、非同步任務及等待執行的非同步任務。
  • 排程執行緒池管理非同步任務。
  • 發起/取消網路請求 API:execute、enqueue、cancel。

Dispatcher 類,該類中維護了三個雙端佇列(Deque):

  • readyAsyncCalls:準備執行的非同步請求
  • runningAsyncCalls:正在執行的非同步請求
  • runningSyncCalls:正在執行的同步請求

OkHttp 設定了預設的最大併發請求量 maxRequests = 64 和單個 Host 主機支援的最大併發量 maxRequestsPerHost = 5

5.addInterceptor 與 addNetworkInterceptor 的區別?

二者通常的叫法為應用攔截器網路攔截器,從整個責任鏈路來看,應用攔截器是最先執行的攔截器,也就是使用者自己設定request屬性後的原始請求,而網路攔截器位於ConnectInterceptorCallServerInterceptor之間,此時網路鏈路已經準備好,只等待傳送請求資料。

  1. 首先,應用攔截器在RetryAndFollowUpInterceptorCacheInterceptor之前,所以一旦發生錯誤重試或者網路重定向,網路攔截器可能執行多次,因為相當於進行了二次請求,但是應用攔截器永遠只會觸發一次。另外如果在CacheInterceptor中命中了快取就不需要走網路請求了,因此會存在短路網路攔截器的情況。
  2. 其次,如上文提到除了CallServerInterceptor,每個攔截器都應該至少呼叫一次realChain.proceed方法。實際上在應用攔截器這層可以多次呼叫proceed方法(本地異常重試)或者不呼叫proceed方法(中斷),但是網路攔截器這層連線已經準備好,可且僅可呼叫一次proceed方法。
  3. 最後,從使用場景看,應用攔截器因為只會呼叫一次,通常用於統計客戶端的網路請求發起情況;而網路攔截器一次呼叫代表了一定會發起一次網路通訊,因此通常可用於統計網路鏈路上傳輸的資料。

6、Okhttp 攔截器的作用是什麼?

1、應用攔截器

拿到的是原始請求,可以新增一些自定義header、通用引數、引數加密、閘道器接入等等。

  • RetryAndFollowUpInterceptor 處理錯誤重試和重定向

  • BridgeInterceptor 應用層和網路層的橋接攔截器,主要工作是為請求新增cookie、新增固定的header,比如Host、Content-Length、Content-Type、User-Agent等等,然後儲存響應結果的cookie,如果響應使用gzip壓縮過,則還需要進行解壓。

  • CacheInterceptor 快取攔截器,如果命中快取則不會發起網路請求。

  • ConnectInterceptor 連線攔截器,內部會維護一個連線池,負責連線複用、建立連線(三次握手等等)、釋放連線以及建立連線上的socket流。

2、網路攔截器

使用者自定義攔截器,通常用於監控網路層的資料傳輸。

  • CallServerInterceptor 請求攔截器,在前置準備工作完成後,真正發起了網路請求。

7、Okhttp 有哪些優勢?

  1. 支援 http2,對一臺機器的所有請求共享同一個 Socket
  2. 內建連線池,支援連線複用,減少延遲
  3. 支援透明的 gzip 壓縮響應體
  4. 響應快取可以完全避免網路重複請求
  5. 請求失敗時自動重試主機的其他 ip,自動重定向
  6. 豐富的 API,可擴充套件性好

8、response.body().string() 為什麼只能呼叫一次?

我們可能習慣在獲取到Response物件後,先response.body().string()列印一遍 Log,再進行資料解析,卻發現第二次直接拋異常,其實直接跟原始碼進去看就發現,通過source拿到位元組流以後,直接呼叫closeQuietly()方法關閉了,這樣第二次再去通過source讀取就直接流已關閉的異常了。

public final String string() throws IOException {
  BufferedSource source = source();
  try {
    Charset charset = Util.bomAwareCharset(source, charset());
    return source.readString(charset);
  } finally {
    //這裡講resource給悄悄close了
    Util.closeQuietly(source);
  }
}

解決方案:1.記憶體快取一份response.body().string();2.自定義攔截器處理 Log。

9、Okhttp 運用了哪些設計模式?

Okhttp 運用了六種設計模式:

  • 構造者模式(OkhttpClient,Request 等各種物件的建立)
  • 工廠模式(在 Call 介面中,有一個內部工廠 Factory 介面。)
  • 單例模式(Platform 類,已經使用 Okhttp 時使用單例)
  • 策略模式(在 CacheInterceptor 中,在響應資料的選擇中使用了策略模式,選擇快取資料還是選擇網路訪問。)
  • 責任鏈模式(攔截器的鏈式呼叫)
  • 享元模式(Dispatcher 的執行緒池中,不限量的執行緒池實現了物件複用)

相關文章