Android小知識-剖析OkHttp中的同步請求
本平臺的文章更新會有延遲,大家可以關注微信公眾號-顧林海,包括年底前會更新kotlin由淺入深系列教程,目前計劃在微信公眾號進行首發,如果大家想獲取最新教程,請關注微信公眾號,謝謝
無論是同步請求還是非同步請求,都需要一個OkHttpClient。
private OkHttpClient mHttpClient = null; private void initHttpClient() { if (null == mHttpClient) { mHttpClient = new OkHttpClient.Builder() .readTimeout(5, TimeUnit.SECONDS)//設定讀超時 .writeTimeout(5,TimeUnit.SECONDS)////設定寫超時 .connectTimeout(15,TimeUnit.SECONDS)//設定連線超時 .retryOnConnectionFailure(true)//是否自動重連 .build(); } }
進入OkHttpClient的靜態內部類Builder:
public static final class Builder { Dispatcher dispatcher; ConnectionPool connectionPool; public Builder() { //分發器 dispatcher = new Dispatcher(); //連線池 connectionPool = new ConnectionPool(); ... } public OkHttpClient build() { return new OkHttpClient(this); } }
Builder構造器進行初始化操作,這裡先介紹幾個比較重要引數
-
Dispatcher分發器:它的作用是用於在非同步請求時是直接進行處理,還是進行快取等待,對於同步請求,Dispatcher會將請求放入佇列中。
-
ConnectionPool連線池:可以將客戶端與伺服器的連線理解為一個connection,每一個connection都會放在ConnectionPool這個連線池中,當請求的URL相同時,可以複用連線池中的connection,並且ConnectionPool實現了哪些網路連線可以保持開啟狀態以及哪些網路連線可以複用的相應策略的設定。
通過Builder進行了一些引數的初始化,最後通過build方法建立OkHttpClient物件。
建立OkHttpClient物件時使用到了設計模式中的Builder模式,將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。
private void synRequest() { Request request=new Request.Builder() .url("http://www.baidu.com") .get() .build(); Call call=mHttpClient.newCall(request); try { Response response=call.execute(); System.out.println(request.body().toString()); } catch (IOException e) { e.printStackTrace(); } }
建立完OkHttpClient物件後,接著建立Request物件,也就是我們的請求物件。Request的建立方式和OkHttpClient的建立方式一樣,都使用了Builder模式。
Request內部類Builder的構造方法:
public static class Builder { String method; Headers.Builder headers; public Builder() { this.method = "GET"; this.headers = new Headers.Builder(); } }
Request內部類的Builder構造方法非常的簡單,初始化了請求方式,預設是GET請求,接著初始化了頭部資訊。
接著看它的build方法:
public Request build() { if (url == null) throw new IllegalStateException("url == null"); return new Request(this); }
build方法中先校驗傳入的url是否為空,再將當前配置的請求引數傳給Request。
Request的構造方法:
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); }
將我們配置好的引數,比如請求地址url、請求方式、請求頭部資訊、請求體以及請求標誌傳給了Request。
總結同步請求的前兩步:
-
建立一個OkHttpClient物件。
-
構建了攜帶請求資訊的Request物件。
完成上面兩步後,就可以通過OkHttpClient的newCall方法將Request包裝成Call物件。
Call call=mHttpClient.newCall(request);
進入OkHttpClient的newCall方法:
@Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */); }
Call只是一個介面,因此它的所有操作都在RealCall中實現的。
看一下RealCall的newRealCall方法:
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forSocket/">WebSocket) { RealCall call = new RealCall(client, originalRequest, forWebSocket); call.eventListener = client.eventListenerFactory().create(call); return call; }
可以看到newRealCall方法只有三行程式碼,第一行建立RealCall物件,第二行設定RealCall的eventListener也就是監聽事件,最後返回RealCall物件,我們看下RealCall的構造方法。
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; //重定向攔截器 this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); }
RealCall的構造方法中,設定了前兩步建立的物件OkHttpClient和Request並設定了重定向攔截器(攔截器概念後面會進行詳解)。
到這裡Call物件也建立完畢了,最後通過Call的execute方法來完成同步請求,看一下execute方法到底做了哪些操作,由於Call是介面,execute方法的具體實現在RealCall中:
@Override public Response execute() throws IOException { //第一步:判斷同一Http是否請求過 synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } //捕捉Http請求的異常堆疊資訊 captureCallStackTrace(); //監聽請求開始 eventListener.callStart(this); try { //第二步:同步請求新增到同步佇列中 client.dispatcher().executed(this); //第三步: Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { eventListener.callFailed(this, e); throw e; } finally { //第四步:回收請求 client.dispatcher().finished(this); } }
第一步,在同步程式碼塊中判斷標誌位executed是否為ture,為true丟擲異常,也就是說同一個Http請求只能執行一次。
第二步,呼叫Dispatcher分發器的executed方法,我們進入Dispatcher的executed方法中。
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>(); synchronized void executed(RealCall call) { runningSyncCalls.add(call); }
Dispatcher的executed方法只是將我們的RealCall物件也就是請求新增到runningSyncCalls同步佇列中。
Dispatcher的作用就是維持Call請求發給它的一些狀態,同時維護一個執行緒池,用於執行網路請求,Call這個請求在執行任務時通過Dispatcher分發器,將它的任務新增到執行佇列中進行操作。
第三步,通過getResponseWithInterceptorChain方法獲取Response物件,getResponseWithInterceptorChain方法的作用是通過一系列攔截器進行操作,最終獲取請求結果。
第四步,在finally塊中,主動回收同步請求,進入Dispatcher的finished方法:
void finished(RealCall call) { finished(runningSyncCalls, call, false); } private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) { int runningCallsCount; Runnable idleCallback; synchronized (this) { //移除請求 if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); if (promoteCalls) promoteCalls(); //計算同步請求佇列+非同步請求佇列的總數 runningCallsCount = runningCallsCount(); idleCallback = this.idleCallback; } if (runningCallsCount == 0 && idleCallback != null) { idleCallback.run(); } }
將我們正在執行的同步請求RealCall物件通過finished方法傳了進去,接著從當前的同步佇列中移除本次同步請求,promoteCalls預設傳入是false,也就是promoteCalls方法不會執行到,但如果是非同步請求,這裡傳入的是ture,會執行promoteCalls方法,關於非同步請求後面進行講解。從同步佇列中清除當前請求RealCall後,重新計算當前請求總數,我們可以看下runningCallsCount方法的具體實現。
public synchronized int runningCallsCount() { return runningAsyncCalls.size() + runningSyncCalls.size(); }
方法非常簡單,就是計算正在執行的同步請求和非同步請求的總和。
在finished方法最後判斷runningCallsCount,如果正在執行的請求數為0並且idleaCallback不為null,就執行idleaCallback的回撥方法run。
到這裡同步請求已經介紹完了,在同步請求中Dispatcher分發器做的工作很簡單,就做了儲存同步請求和移除同步請求的操作。
總結流程圖如下:

bab.png

838794-506ddad529df4cd4.webp.jpg
搜尋微信“顧林海”公眾號,定期推送優質文章。