一篇文章帶你熟悉OkHttp
一、Okhttp的簡單使用
使用步驟
- 構建網路請求控制物件OkHttpClient
- 構建請求物件request
- 建立Call物件
- 建立接收返回資料的物件response
- 傳送網路請求
第一步和第二步我都用了“構建”這個詞,這是因為這兩個物件內部都是通過建造者設計模式來建立的。
當請求準備好了後,就開始建立和伺服器的連線。連線成功後,執行最後兩步,也就是就是傳送請求和接收返回資料。
1.下面是一個同步請求的示例程式碼
//1.構建OkHttpClient物件 OkHttpClient okHttpClient=new OkHttpClient(); Request.Builder builder=new Request.Builder();//2.構建一個請求物件request,比如請求的url,請求方式 Request request=builder.url("http://ip.taobao.com/service/getIpInfo.php?ip=223.68.134.166") .get() .build(); //3.建立一個Call物件 final Call call= okHttpClient.newCall(request); new Thread(new Runnable() { @Override public void run() { try { //4.傳送請求,並接收回復的返回資料 Response response=call.execute(); Log.d("response", "run: "+response.body().string()); } catch (IOException e) { e.printStackTrace(); } } }).start();
同步請求是什麼意思呢?
簡單的說就是有多個請求的情況下,一次只能處理一個請求,只有獲得這次請求返回的響應時,才能傳送下一個請求。
OkHttp的請求有兩種方式:同步和非同步請求
兩者在使用方式上最大的不同在於最後一步呼叫的方法:
同步請求呼叫的是 execute方法,非同步請求呼叫的是 enqueue方法
2.傳送非同步請求的程式碼示例
//傳送非同步請求 call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d(TAG, "非同步請求失敗"+e.toString()); } @Override public void onResponse(Call call, Response response) throws IOException { Log.d(TAG, "非同步請求成功"+response.body().string()); } });
但它們在實現方式上確有很大的不同。
關於Okhttp的更多使用方法請看這裡:
ofollow,noindex">OkHttp用法二、原始碼解析
1.核心控制類
從流程的步驟可以看出OkHttpClient是我們的核心控制類,它控制著整個網路請求,就拿它當切入口
先看它的定義的一大堆東西(這裡先隨便過一眼就好了)
final Dispatcher dispatcher;//分發器 final Proxy proxy;//代理 final List<Protocol> protocols; //協議 final List<ConnectionSpec> connectionSpecs; //傳輸層版本和連線協議 final List<Interceptor> interceptors; //攔截器 final List<Interceptor> networkInterceptors; //網路攔截器 final ProxySelector proxySelector; //代理選擇 final CookieJar cookieJar; //cookie final Cache cache; //快取 final InternalCache internalCache;//內部快取 final SocketFactory socketFactory;//socket 工廠 final SSLSocketFactory sslSocketFactory; //安全套接層socket 工廠,用於HTTPS final CertificateChainCleaner certificateChainCleaner; // 驗證確認響應證書 適用 HTTPS 請求連線的主機名。 final HostnameVerifier hostnameVerifier;//主機名字確認 final CertificatePinner certificatePinner;//證書鏈 final Authenticator proxyAuthenticator;//代理身份驗證 final Authenticator authenticator;// 本地身份驗證 final ConnectionPool connectionPool;//連線池,複用連線 final Dns dns;//域名 final boolean followSslRedirects;//安全套接層重定向 final boolean followRedirects;//本地重定向 final boolean retryOnConnectionFailure; //重試連線失敗 final int connectTimeout;//連線超時 final int readTimeout; //read 超時 final int writeTimeout; //write 超時 final int pingInterval; //ping的間隔時間
接下來看它的構造方法
public OkHttpClient() { this(new Builder()); } OkHttpClient(Builder builder) { this.dispatcher = builder.dispatcher; this.proxy = builder.proxy; this.protocols = builder.protocols; this.connectionSpecs = builder.connectionSpecs; //.....、此處省略大量程式碼 }
可以看出來它使用了 建造者模式 。我們可以針對性選擇它在Builder裡定義的引數進行設定,如果不設定它的引數,就會使用它的預設值,像這樣
public Builder() { dispatcher = new Dispatcher(); protocols = DEFAULT_PROTOCOLS; connectionSpecs = DEFAULT_CONNECTION_SPECS; eventListenerFactory = EventListener.factory(EventListener.NONE); proxySelector = ProxySelector.getDefault(); //.....、此處省略大量程式碼 }
同樣Request物件也是通過建造者模式構建的
public final class Request { final HttpUrl url;//請求的url final String method; //請求方式 final Headers headers; //請求頭 final @Nullable RequestBody body; //請求體 final Map<Class<?>, Object> tags; Request(Builder builder) { this.url = builder.url; this.method = builder.method; this.headers = builder.headers.build(); this.body = builder.body; this.tags = Util.immutableMap(builder.tags); }
把控制類物件和請求物件都準備好了後,我們需要建立一個Call物件,進入原始碼我們可以看到它其實是一個介面,那我們就需要找它的實現類- RealCall ,它才是真正的請求執行者。
通過呼叫newCall方法,建立RealCall物件
public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */); } static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { RealCall call = new RealCall(client, originalRequest, forWebSocket); call.eventListener = client.eventListenerFactory().create(call); return call; }
到這裡,都算是為我們正式請求之前的準備工作。其實,真正的請求在背後做了什麼,才是Okhttp的優秀之處,精華所在。同時也是重難點所在,接下來,才是真正的大餐。
2.攔截器機制
在繼續分析原始碼之前,先介紹一個很重要的概念-攔截器機制。okhttp就是通過攔截器機制來獲取和處理網路請求響應的。
先從三個層看這個圖

OkhttpInterceptor.png
在圖中,可以看到在請求和獲取響應時,都會經過Okhttp的核心,並且都會經過一系列OkHttp自帶的攔截器(圖中的橙色區域)。當然在應用層和網路層也有應用攔截器和網路攔截器。我們主要分析Okhttp自帶的攔截器
那麼這些攔截器是負責做什麼的呢?
首先這些攔截器構成一條鏈(可以理解成鏈條),這些攔截器會在發起請求前對request進行處理,然後呼叫下一個攔截器,獲取response,最後對response進行處理,返回給上一個攔截器。
接下來先大致看一下它們的作用,在後面會詳細介紹這些攔截器的實現流程
1.RetryAndFollowUpInterceptor(重試重定向攔截器)
重試那些失敗或者重定向的請求。因為在整個網路請求的過程中,網路可能不穩定
2.BridgeInterceptor(橋接攔截器)
請求之前對響應頭做了一些檢查,並新增一些頭。負責將使用者構建好的Request請求轉換成可以進行網路訪問的請求
3.CacheInterceptor(快取攔截器)
做一些快取的工作。獲取響應時先從快取裡去獲取,如果快取沒有才會進行網路請求,並把獲取到響應後放入快取
4.ConnectInterceptor(連線攔截器)
負責建立和目標伺服器的連線
5.CallServerInterceptor(傳送服務請求攔截器)
負責向伺服器發起真正的網路請求
這些攔截器流程走向如下

interceptor.jpg
這些攔截器是什麼時候被建立的呢?
這個時候我們就可以進入同步的請求execute方法
public Response execute() throws IOException{ synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); timeout.enter(); eventListener.callStart(this); try { client.dispatcher().executed(this); //通過攔截器鏈獲取響應 Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { e = timeoutExit(e); eventListener.callFailed(this, e); throw e; } finally { client.dispatcher().finished(this); } }
通過一個ArrayList集合建立這些攔截器,形成一條鏈條,互相影響。也可以看成是一個棧,內部是遞迴的形式一層層返回響應結果。
Response getResponseWithInterceptorChain() throws IOException { //把所有攔截器放入list集合中 List<Interceptor> interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); interceptors.add(retryAndFollowUpInterceptor); interceptors.add(new BridgeInterceptor(client.cookieJar())); interceptors.add(new CacheInterceptor(client.internalCache())); interceptors.add(new ConnectInterceptor(client)); if (!forWebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); return chain.proceed(originalRequest); }
這裡我沒有分析非同步的過程,因為它比同步複雜多了,不信你看
public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); client.dispatcher().enqueue(new AsyncCall(responseCallback)); }
確定沒有逗我???我可是受過九年義務教育的。這程式碼量比enqueue簡短多了。這是因為它把職責交給了Dispatcher類了,而Dispatcher又牽涉到執行緒池等。
呼叫Dispatcher.enqueue時傳入了一個AsyncCall物件
final class AsyncCall extends NamedRunnable { private final Callback responseCallback; AsyncCall(Callback responseCallback) { super("OkHttp %s", redactedUrl()); this.responseCallback = responseCallback; } protected void execute() { boolean signalledCallback = false; timeout.enter(); try { Response response = getResponseWithInterceptorChain(); } //... } public abstract class NamedRunnable implements Runnable { //... public final void run() { try { execute();//執行子類的execute方法 } //... } protected abstract void execute(); }
可以看到,在非同步請求的過程中,它最終還是會呼叫 getResponseWithInterceptorChain方法,也就是通過攔截器鏈獲取返回的響應。
到這裡我們對攔截器有了一個淺層的認識了,下面開始詳細介紹每個攔截器的實現流程
2.1 RetryAndFollowUpInterceptor
public final class RetryAndFollowUpInterceptor implements Interceptor { private volatile StreamAllocation streamAllocation;//建立執行http請求的所有網路元件 //...... public Response intercept(Chain chain) throws IOException { Request request = chain.request(); RealInterceptorChain realChain = (RealInterceptorChain) chain; Call call = realChain.call(); EventListener eventListener = realChain.eventListener(); //1.建立StreamAllocation物件 StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()), call, eventListener, callStackTrace); this.streamAllocation = streamAllocation; //進行網路請求連線 while (true) { //... //2.呼叫RealInterceptorChain.proceed(),呼叫下一個攔截器進行網路請求連線,獲取response try { response = realChain.proceed(request, streamAllocation, null, null); releaseConnection = false; } catch (RouteException e) { } //... try { //重定向請求 followUp = followUpRequest(response, streamAllocation.route()); } catch (IOException e) { streamAllocation.release(); throw e; } //超過一定的重連次數,丟擲異常 if (++followUpCount > MAX_FOLLOW_UPS) { streamAllocation.release(); throw new ProtocolException("Too many follow-up requests: " + followUpCount); } } }
大致流程
- 建立StreamAllocation物件,用於建立執行http請求的所有網路元件
- 呼叫RealInterceptorChain.proceed()來呼叫下一個攔截器,獲取響應結果response
- 根據響應結果判斷是否進行重新請求
2.2 BridgeInterceptor
public final class BridgeInterceptor implements Interceptor { public Response intercept(Chain chain) throws IOException { Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); RequestBody body = userRequest.body(); //進行請求頭的包裝 if (body != null) { MediaType contentType = body.contentType(); if (contentType != null) { requestBuilder.header("Content-Type", contentType.toString()); } if (contentLength != -1) { requestBuilder.header("Content-Length", Long.toString(contentLength)); requestBuilder.removeHeader("Transfer-Encoding"); } //.... } responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody))); } return responseBuilder.build(); }
大致流程:
- 對請求頭做了一些檢查,並新增一些頭,目的是Request請求轉換成可以進行網路訪問的請求
- 將這個符合網路請求的request進行網路請求
- 然後將請求回來的響應結果Response轉換為使用者可見的Response
2.3 CacheInterceptor
CacheInterceptor內部實現了一個快取策略類CacheStrategy
public final class CacheInterceptor implements Interceptor {public Response intercept(Chain chain) throws IOException { Response cacheCandidate = cache != null? cache.get(chain.request()): null; CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse; //呼叫下一個攔截器 try { networkResponse = chain.proceed(networkRequest); } finally { //... } }
先判斷快取有沒有響應,如果有就從快取裡獲取;如果快取沒有就從網路中獲取響應
//如果快取有響應 if (cacheResponse != null) { if (networkResponse.code() == HTTP_NOT_MODIFIED) { Response response = cacheResponse.newBuilder() .headers(combine(cacheResponse.headers(), networkResponse.headers())) .sentRequestAtMillis(networkResponse.sentRequestAtMillis()) .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()) .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); networkResponse.body().close(); cache.trackConditionalCacheHit(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } }
如果快取沒有響應且網路請求結果為空,丟擲504碼錯誤
//如果快取沒有響應且網路請求結果為空 if (networkRequest == null && cacheResponse == null) { return new Response.Builder() .request(chain.request()) .protocol(Protocol.HTTP_1_1) .code(504) .message("Unsatisfiable Request (only-if-cached)") .body(Util.EMPTY_RESPONSE) .sentRequestAtMillis(-1L) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); }
大致流程如下:
- 獲取本地快取cacheCandidate,如果本地快取可用則打斷interceptor鏈,返回cacheCandidate,
- 呼叫下一個interceptor獲取networkResponse
- 用networkResponse、cacheResponse構造新的response
- 根據新的response裡的header通過快取策略存入快取中
2.4 ConnectInterceptor
okhttp的一大特點就是通過連線池來減小響應延遲。如果連線池中沒有可用的連線,則會與伺服器建立連線,並將socket的io封裝到HttpStream(傳送請求和接收response)中,這些都在ConnectInterceptor中完成。
public final class ConnectInterceptor implements Interceptor { public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; Request request = realChain.request(); //獲取之前的攔截器傳過來的 StreamAllocation StreamAllocation streamAllocation = realChain.streamAllocation(); // We need the network to satisfy this request. Possibly for validating a conditional GET. boolean doExtensiveHealthChecks = !request.method().equals("GET"); HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks); //通過streamAllocation獲取RealConnection物件 RealConnection connection = streamAllocation.connection(); return realChain.proceed(request, streamAllocation, httpCodec, connection); } }
大致流程:
- ConnectInterceptor獲取之前的攔截器傳過來的 StreamAllocation
- 通過streamAllocation獲取RealConnection物件,它是真正用於連線網路的物件
- 將RealConnection物件,以及對於與伺服器互動最為關鍵的HttpCodec等物件傳遞給後面的攔截器
HttpCodec的作用簡單的理解就是對request編碼,對response解碼
2.5 CallServerInterceptor
CallServerInterceptor是最後一個攔截器,它負責發起真正的網路請求並接收伺服器返回的響應資料
public final class CallServerInterceptor implements Interceptor { public Response intercept(Chain chain) throws IOException { RealInterceptorChain realChain = (RealInterceptorChain) chain; HttpCodec httpCodec = realChain.httpStream(); StreamAllocation streamAllocation = realChain.streamAllocation(); RealConnection connection = (RealConnection) realChain.connection(); Request request = realChain.request(); realChain.eventListener().requestHeadersStart(realChain.call()); //寫入請求頭資訊 httpCodec.writeRequestHeaders(request); realChain.eventListener().requestHeadersEnd(realChain.call(), request); //寫入請求體的資訊 request.body().writeTo(bufferedRequestBody); //完成網路請求工作 httpCodec.finishRequest(); if(responseBuilder == null) { realChain.eventListener().responseHeadersStart(realChain.call()); responseBuilder = httpCodec.readResponseHeaders(false); } //讀取網路響應的頭資訊 if (responseBuilder == null) { realChain.eventListener().responseHeadersStart(realChain.call()); responseBuilder = httpCodec.readResponseHeaders(false); } //讀取網路響應的body資訊 if (forWebSocket && code == 101) { // Connection is upgrading, but we need to ensure interceptors see a non-null response body. response = response.newBuilder() .body(Util.EMPTY_RESPONSE) .build(); } else { response = response.newBuilder() .body(httpCodec.openResponseBody(response)) .build(); } } return response; }
大致流程:
- 寫入請求頭和請求體資訊,發起正在的網路請求
- 獲取網路請求返回的響應,讀取響應頭和響應體的資訊
- 返回最終的響應結果
三、總結
一圖勝千言

同步和非同步請求.jpg
到這裡把OkHttp的執行流程都介紹了一遍,但是具體到每一個部分內部實現原理遠沒有我說的這麼簡單,牽涉到很多的知識點。這裡就不進行分析了,一篇文章也是不能講清楚的。相信你看完這篇文章之後,再去看它的Okhttp的原始碼,會更輕鬆的。