Okhttp攔截器Interceptor學習和使用
前年的這個時候我們專案將網路框架替換為 okhttp+retrofit
,然後我對 retrofit
原始碼進行了學習和分享,寫了幾篇相關的文章同時更新了專案的網路框架。
需求是推動任何事物向前發展的動力,這次我們專案需要對網路介面進行加密了,開發過程涉及到了okhttp的網路層的處理,所以我又將其原始碼翻了一番。
回顧一下我們曾經學習過的因特網五層協議棧:
- 網路請求發出時:應用層->傳輸層->網路層->連線層->物理層
- 收到響應後:物理層->連線層->網路層->傳輸層->應用層
這個很像我們這次要講的 okhttp
中的 interceptor
的責任鏈模式。
Interceptor
按照以往的慣例我們先上圖,然後在對每個步驟進行詳細的講述。
為什麼會有攔截器
我們在進行應用開發的時候都會在請求中增加一些我們應用需要和服務端互動的通用資訊,比如在 header
中增加使用者的登入態資訊等等。或者像 Retrofit2.0+Okhttp不依賴服務端的資料快取 這篇文章中不依賴服務端的快取,在請求的過程中我們自己修改一些請求的 request
和 response
。
這個時候攔截器就是我們的強大的助力。
okhttp中的攔截器
我們從 okhttp
處理一條普通的url請求的程式碼執行過程中觀察 interceptors
的工作。
下邊的程式碼是從okhttp官網 摘的一段示例程式碼:
我們跟進 okhttpclient.newcall
方法:
public class OkHttpClient implements Cloneable, Call.Factory, Socket/">WebSocket.Factory { /***部分程式碼省略***/ /** * Prepares the {@code request} to be executed at some point in the future. */ @Override public Call newCall(Request request) { return new RealCall(this, request, false /* for web socket */); } }
獲取一個真正用來進行請求的Call RealCall.execute
:
final class RealCall implements Call { final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor; RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { final EventListener.Factory eventListenerFactory = client.eventListenerFactory(); this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); // TODO(jwilson): this is unsafe publication and not threadsafe. this.eventListener = eventListenerFactory.create(this); } @Override public Response execute() throws IOException { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); try { //進行網路請求 client.dispatcher().executed(this); //經過一層層網路攔截器之後,獲取網路請求的返回值 Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } finally { client.dispatcher().finished(this); } } Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); //Application攔截器 interceptors.addAll(client.interceptors()); //重定向和失敗後重新請求攔截器 interceptors.add(retryAndFollowUpInterceptor); //網橋攔截器,顧名思義client和Server之前的橋樑 interceptors.add(new BridgeInterceptor(client.cookieJar())); //快取處理攔截器 interceptors.add(new CacheInterceptor(client.internalCache())); //Socket層的握手鍊接 interceptors.add(new ConnectInterceptor(client)); if (!forWebSocket) { //網路爛攔截器 interceptors.addAll(client.networkInterceptors()); } //client和Server之前的讀寫操作 interceptors.add(new CallServerInterceptor(forWebSocket)); //責任鏈開始執行 Interceptor.Chain chain = new RealInterceptorChain( interceptors, null, null, null, 0, originalRequest); return chain.proceed(originalRequest); } }
Interceptor介紹
我們先看攔截器的介面定義:
public interface Interceptor { //攔截處理 Response intercept(Chain chain) throws IOException; interface Chain { //獲取請求的request Request request(); //處理request獲取response Response proceed(Request request) throws IOException; /** * Returns the connection the request will be executed on. This is only available in the chains * of network interceptors; for application interceptors this is always null. */ @Nullable Connection connection(); } }
在我們跟蹤原始碼的執行的過程我們回憶下最開始的時候的流程圖,涉及到的攔截器以及他們各自的位置和在網路請求的作用。
Application Interceptor
我們可以自定義設定 Okhttp
的攔截器之一。
從流程圖中我們可以看到一次網路請求它只會執行一次攔截,而且它是第一個觸發攔截的,這裡攔截到的url請求的資訊都是最原始的資訊。所以我們可以在該攔截器中新增一些我們請求中需要的通用資訊,列印一些我們需要的日誌。
當然我們可以定義多個這樣的攔截器,一個處理 header
資訊,一個處理 介面請求的 加解密
。
NetwrokInterceptor
NetwrokInterceptor
也是我們可以自定義的攔截器之一。
它位於倒數第二層,會經過 RetryAndFollowIntercptor
進行重定向並且也會通過 BridgeInterceptor
進行 request
請求頭和 響應 resposne
的處理,因此這裡可以得到的是更多的資訊。在列印結果可以看到它內部重定向操作和失敗重試,這裡會有比 Application Interceptor
更多的日誌。
RetryAndFollowInterceptor
RetryAndFollowUpInterceptor
的作用,看到該攔截器的名稱就知道,它就是一個負責失敗重連的攔截器。它是 Okhttp
內建的第一個攔截器,通過 while (true)
的死迴圈來進行對異常結果或者響應結果判斷是否要進行重新請求。
BridgeInterceptor
BridgeInterceptor
為使用者構建的一個 Request 請求轉化為能夠進行網路訪問的請求,同時將網路請求回來的響應 Response 轉化為使用者可用的 Response。比如,涉及的網路檔案的型別和網頁的編碼,返回的資料的解壓處理等等。
CacheInterceptor
CacheInterceptor
根據 OkHttpClient
物件的配置以及快取策略對請求值進行快取。
ConnectInterceptor
ConnectInterceptor
在 OKHTTP 底層是通過 SOCKET 的方式於服務端進行連線的,並且在連線建立之後會通過 OKIO 獲取通向 server 端的輸入流 Source 和輸出流 Sink。
CallServerInterceptor
CallServerInterceptor
在 ConnectInterceptor
攔截器的功能就是負責與伺服器建立 Socket 連線,並且建立了一個 HttpStream 它包括通向伺服器的輸入流和輸出流。而接下來的 CallServerInterceptor
攔截器的功能使用 HttpStream 與伺服器進行資料的讀寫操作的。
有關每個攔截器的具體程式碼分析: https://www.jianshu.com/u/8173f323f5bb
攔截器中的騷操作
HttpUrl httpurl = request.url(); //獲取request中的原始url地址 String requestUrl = httpurl.url().toString(); /** * 獲取url中的引數 * @param httpUrl * @return */ private Map<String, String> getHttpUrlParams(HttpUrl httpUrl) { Set<String> paramsSet = httpUrl.queryParameterNames(); Map<String, String> paramMap = new HashMap<>(); if (paramsSet != null && paramsSet.size() > 0) { for (String s: paramsSet) { paramMap.put(s, httpUrl.queryParameter(s)); } } return paramMap; } //一般POST請求引數都是放在RequestBody中,使用時需要判斷RequestBody是否為子類FormBody的例項 RequestBody requestBody = request.body(); /** * 獲取請求form中的引數 * @param formBody * @return */ private Map<String, String> getHttpUrlParams(FormBody formBody) { Map<String, String> paramMap = new HashMap<>(); if (formBody != null) { for (int i = 0; i < formBody.size(); i++) { paramMap.put(formBody.name(i), formBody.value(i)); } } return paramMap; } //構造新的POST表單 FormBody.Builder formBuilder = new FormBody.Builder(); for (String key: map.keySet()) { formBuilder.add(key, map.get(key)); } //構造新的HttpUrl,動態修改請求url地址 HttpUrl httpUrl = HttpUrl.parse(requestUrlTrue); //構造新的request請求 request = request.newBuilder() .method(POST, formBuilder.build()) .url(httpUrl) .build(); //獲取相應體對應的請求體,請求和返回一一對應 Request request = response.request() //獲取請求的相應體 ResponseBody responseBody = response.body(); //獲取返回值型別 MediaType mediaType = responseBody.contentType(); //獲取相應體重的資料流,只能獲取一次,在獲取之後資料流會關閉,再次獲取會有異常丟擲 byte[] responseBytes = responseBytes = responseBody.bytes(); //利用修改後的返回值,構造新的相應體 response = response.newBuilder() .body(ResponseBody.create(mediaType, responseBytes)) .build();
文章到這裡就全部講述完啦,若有其他需要交流的可以留言哦~!~!
想閱讀作者的更多文章,可以檢視我個人部落格 和公共號: