Android--OkHttp理解系列(一)
本篇文章主要對OkHttp進行分析,主要內容如下:
- OkHttp初識
- OkHttp基本執行流程(Dispatcher)
- 攔截器(Interceptor)
- 責任鏈設計模式
1. OkHttp 初識
OkHttp 在開發中經常使用到,常見的用法是:
okhttpclient okhttpclient = new okhttpclient(); request request = new request.builder().url("www.baidu.com").get()//get 請求.build(); call call = okhttpclient.newcall(request); call.enqueue(new callback() {@overridepublic void onfailure(call call, ioexception e) {//nop}@overridepublic void onresponse(call call, response response) throws ioexception {//nop} }); 復制代碼上述代碼是基于異步的網絡請求,同步的網絡請求不同的地方是:
try {response execute = call.execute(); } catch (ioexception e) {//nop } 復制代碼不相同的地方點在于call對象調用的方法,那么,同步調用與異步調用主要的差別是什么? call.execute() 方法將直接進行網絡請求,阻塞當前線程直到獲得網絡請求的響應。異步執行會將call放入一個異步執行隊列中,由executorservice在后臺進行執行。
2. 基本執行流程
基本執行流程主要談的是call的執行流程,在call的執行流程中分為同步執行流程與異步執行流程。call其實是一個繼承了cloneable的接口,我們調用call call = okhttpclient.newcall(request); 獲取的其實是realcall這個對象,因此下文提到的call其實就是realcall。
2.1 Call的同步執行流程
通過realcall.excute()方法執行的流程為:
2.2 Call的異步執行流程
通過realcall.enqueue(callback)方法執行的流程首先是,client.dispatcher().enqueue(new asynccall(responsecallback)); 創建一個asynccall,將enqueue的callback傳遞過去。asynccall其實是一個runnable,當調用enqueue()方法的時候就是講asynccall傳遞給dispatcher。dispatcher里面封裝了一個線程池去執行這個call,executorservice().execute(call); ,這個call就是一個runnable,我們跟進這個runnable的run()方法,我們可以看到里面執行了一個execute() ;所以邏輯最終回到了asynccall的execute()方法。realcall.asynccall.execute()與同步執行的流程有些類似:
2.3 Dispatcher干了些啥
通過上面call的執行流程,我們可以看出其實okhttp的核心其實是dispatcher這個分發器,接下來我們詳細分析okhttp中dispatcher在網絡請求的時候做了些什么事情。
2.3.1 同步Dispatcher
首先看下代碼:
/** used by {@code call#execute} to signal it is in-flight. */ synchronized void executed(realcall call) {runningsynccalls.add(call); } 復制代碼從上面的代碼可以看出,大體的執行邏輯就是將傳遞過來的realcall添加進了一個隊列中,那么這個runningsynccalls到底是什么?看下源碼:
/** running synchronous calls. includes canceled calls that haven't finished yet. */ private final deque<realcall> runningsynccalls = new arraydeque<>(); 復制代碼在call同步執行的過程中,dispatcher僅僅將call放進了runningsynccalls這個隊列,其他的什么都沒有做,這個隊列包含了正在執行的call,而將call注冊該隊列主要的作用是方便全局管理運行的call。
2.3.2 異步Dispatcher
首先我們看看代碼:
synchronized void enqueue(asynccall call) {if (runningasynccalls.size() < maxrequests && runningcallsforhost(call) < maxrequestsperhost) {runningasynccalls.add(call);executorservice().execute(call);} else {readyasynccalls.add(call);} } 復制代碼在異步調度中,傳遞過來的asynccall是被放在線程池中進行處理的。這個線程池是什么?來看看代碼:
public synchronized executorservice executorservice() {if (executorservice == null) {executorservice = new threadpoolexecutor(0, integer.max_value, 60, timeunit.seconds,new synchronousqueue<runnable>(), util.threadfactory("okhttp dispatcher", false));}return executorservice; } 復制代碼默認該線程池是一個不限制大小的線程池。從前面代碼我們可以看到,dispatcher會限制每一個host請求的最大限制,private int maxrequestsperhost = 5; 同時也會限制同時執行的最大請求數量,private int maxrequests = 64;。在runningasynccalls隊列中,保留全部滿足限制條件而正在被executorservice執行的所有asynccall,而不滿足限制條件的則由readyasynccalls進行保存。
在異步調度中如果當前的call不能立即入隊執行的話,那么會執行readyasynccalls.add(call);這個方法不會立即執行而是需要進行等待 ,進入等待則是說明當前的情況是要么有64條線程正在并發,要么同一個地址有5 個請求,那么等待的call什么時候會被再次執行呢? 他的觸發條件為:
1. Dispatcher 的setMaxRequestPerHost() 方法被調用時候 ; 2. Dispatcher 的setMaxRequests() 被調用時候; 3. 當有一條請求結束了 執行了finish()的出隊操作, 這個時候會觸promoteCalls()方法執行 ,從而進行調整。 復制代碼2.3.3 dispatcher的finished()
在finished()中我們看看其實現原理:
/** used by {@code asynccall#run} to signal completion. */ void finished(asynccall call) {finished(runningasynccalls, call, true); }/** used by {@code call#execute} to signal completion. */ 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();} } 復制代碼同步或者異步dispatcher的finished()方法最后都會執行到finished()方法,在該方法中除了會從runningsyncalls隊列中移除當前正在被執行的call,異步方法還會檢查由于限制條件(這里的限制條件是指最大請求數與單個host最大的請求數量)而保存在readyasyncalls隊列中的asynccall從而進行移除。在異步方法中關鍵代碼:finished(runningasyncalls, call, true);第三個參數就是判斷是否需要移除readyasyncalls中的call。
private void promotecalls() {if (runningasynccalls.size() >= maxrequests) return; // already running max capacity.if (readyasynccalls.isempty()) return; // no ready calls to promote.for (iterator<asynccall> i = readyasynccalls.iterator(); i.hasnext(); ) {asynccall call = i.next();if (runningcallsforhost(call) < maxrequestsperhost) {i.remove();runningasynccalls.add(call);executorservice().execute(call);}if (runningasynccalls.size() >= maxrequests) return; // reached max capacity.} } 復制代碼在promotecalls()中,主要的邏輯就是如果當前線程大于maxRequest則不進行操作,如果小于maxRequest則遍歷整個readyAsyncCalls,取出一個call,并把這個call放入runningAsyncCalls,然后執行execute。如果在遍歷過程中runningAsyncCalls超過maxRequest則不再添加,否則一直添加。總結一下,promoteCalls()負責ready的Call到running的call的轉換,在finished()方法中,所有的call,不管是realcall或者asynccall都會在執行完畢之后檢測是否存在正在進行的http請求,檢測的方法為:
public synchronized int runningcallscount() {return runningasynccalls.size() + runningsynccalls.size(); } 復制代碼當判斷runningcallscount為0的時候,且該idlecallback存在的時候,回調idlecallback的run()方法。那么idlecallback什么時候存在?或者說調度器什么時候處于空閑狀態?繼續分析源碼:
/*** set a callback to be invoked each time the dispatcher becomes idle (when the number of running* calls returns to zero).** <p>note: the time at which a {@linkplain call call} is considered idle is different depending* on whether it was run {@linkplain call#enqueue(callback) asynchronously} or* {@linkplain call#execute() synchronously}. asynchronous calls become idle after the* {@link callback#onresponse onresponse} or {@link callback#onfailure onfailure} callback has* returned. synchronous calls become idle once {@link call#execute() execute()} returns. this* means that if you are doing synchronous calls the network layer will not truly be idle until* every returned {@link response} has been closed.*/ public synchronized void setidlecallback(@nullable runnable idlecallback) {this.idlecallback = idlecallback; } 復制代碼從上面方法中追溯到,調度器的空閑狀態在異步方法調度時,在callback的onResponse()方法或者onfailed()方法被回調的時候,調度器處于空閑狀態。同步方法中只有在excute()方法返回的時候才會處于空閑狀態。
3. 攔截器
在okhttp中真正核心的除了上面提到的dispatcher還有就是攔截器interceptor。在同步請求方法或者異步請求方法中,都會執行一條很重要的指令getresponsewithinterceptorchain()該方法就是攔截器的入口方法,接下來源碼分析該方法:
response getresponsewithinterceptorchain() throws ioexception {// build a full stack of interceptors.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); } 復制代碼從上面源碼中可以看到,在一次網絡請求中其實是經歷了多次攔截器的調用,首先先用一個流程圖來大體的分析各個攔截的調用過程:
從上面的流程可以看出,okhttp調用了多個攔截器來對一個請求做攔截操作,那么這樣的操作是怎么完成的呢?其實在每一個攔截器后面都會調用RealInterceptorChain.proceed()來進行處理,回到上面的源碼,okhttp將所有的攔截器添加進入了一個集合之中,利用自增遞增來調用RealInterceptorChain.proceed() 來依次處理添加進入的攔截器。
跟進下RealInterceptorChain的源碼:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection) throws IOException {//...異常判斷省略// 獲取下一個攔截器.RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout);Interceptor interceptor = interceptors.get(index);Response response = interceptor.intercept(next);// Confirm that the next interceptor made its required call to chain.proceed().if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {throw new IllegalStateException("network interceptor " + interceptor+ " must call proceed() exactly once");}// Confirm that the intercepted response isn't null.if (response == null) {throw new NullPointerException("interceptor " + interceptor + " returned null");}if (response.body() == null) {throw new IllegalStateException("interceptor " + interceptor + " returned a response with no body");}return response;} 復制代碼RealInterceptorChain 構造函數中的index與interceptor進行對應,通過interceptor.intercept(next)方法在各個攔截器里面又能通過next參數繼續調用proceed()方法,完成遞歸調用。至于各個攔截器具體的功能,相信大家都大概清楚,這里就不多闡述了。
4. 責任鏈設計模式
在okHttp中攔截器部分設計到的核心就是責任鏈設計模式,首先回顧下什么是責任鏈模式:使多個對象都有機會處理 同一個請求,從而避免請求的發送者與接收者之間的耦合關系。將這些對象連接成一條鏈,并沿著這條鏈傳遞 請求,直到有一個對象處理它為止。 現在我們來剖析OkHttp中的調用鏈,回顧下在OkHttp中的調用鏈結構:
上面的調用模式核心是通過Interceptor來進行完成的,該接口主要的作用為添加、移除、轉換請求或者回應頭部信息。瞟一眼該接口的源碼:
public interface Interceptor { Response intercept(Chain chain) throws IOException;interface Chain {Request request();Response proceed(Request request) throws IOException;//省略} } 復制代碼Interceptor 中intercept(Chain chain)方法負責具體的過濾,而它子類中又調用了Chain, 該Chain其實是指RealInterceptorChain ,在RealInterceptorChain中持有了一個interceptor 的集合 ,通過遞歸調用該List中的攔截器, 對發起的請求進行層層處理,最后遞歸結束返回請求結果。
4.1 具體分析責任鏈模式
上面大體的說明了在OkHttp中責任鏈的調用,現在詳細分析該責任鏈怎么完成調用。責任鏈模式其實分為完全責任鏈模式與不完全責任鏈模式。完全責任鏈模式是指: 當請求到達某個處理類的時候,要么處理,要么傳遞給下個處理類。不完全責任鏈模式: 當請求到達時候, 處理一部分( 可以配置一些參數或者 增加請求頭等),然后扔給下一個處理類進行處理。在Okhttp中,調用的責任鏈就是不完全的責任鏈模式。責任鏈模式的特點:
1. 抽象一個處理任務類; 2. 任務處理類持有下一個任務對象; 3. 完全責任鏈模式處理請求任務時類似于if-else,不完全責任鏈模式則對一個請求進行分步處理。 復制代碼在OkHttp中,發起的請求經過攔截器的層層處理最終返回結果,它具體是怎么操作的? 回溯到Okhttp的 getResponseWithInterceptorChain()該方法在okHttp中同步或者異步最終調用的方法,也是責任鏈調用的起始方法。
Response getResponseWithInterceptorChain() throws IOException {// Build a full stack of interceptors.List<Interceptor> interceptors = new ArrayList<>();// 添加各種攔截器Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,originalRequest, this, eventListener, client.connectTimeoutMillis(),client.readTimeoutMillis(), client.writeTimeoutMillis());return chain.proceed(originalRequest); } 復制代碼接下來介紹核心類RealInterceptorChain ,該類是整個責任鏈的調度中心,該類繼承了Interceptor的內部接口Chain。
interface Chain { Request request(); Response proceed(Request request) throws IOException; //省略...復制代碼request() 獲取的是當前的請求,而proceed() 就是負責具體的轉發任務, 看看RealInterceptorChain中的proceed() 方法:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection) throws IOException {// 省略..// Call the next interceptor in the chain.RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,writeTimeout);Interceptor interceptor = interceptors.get(index);Response response = interceptor.intercept(next);//省略..return response; } 復制代碼該方法核心的就這么幾步:
1. 根據傳入進來的責任鏈集合和索引獲取下一個責任鏈(next) ,即每一個攔截器會持有下一個鏈對象; 2. 從攔截器集合中獲取當前的攔截器; 3. 攔截處理下一個責任鏈(該鏈中傳入當前的請求,只是索引+1 ,那么執行的邏輯就是當前的攔截器如果處理了就直接返回,如果沒有執行或者執行了部分,那么就封裝一個新的請求對象,由下一個鏈進行處理,下一個鏈又對應一個攔截器,同理,如果處理了就直接返回,沒有處理了話,下一個攔截器處理一部分再次扔給攔截器集合中的下一個攔截器,這樣進行遞歸調用,直到返回結果)。 復制代碼具體調用如下:
整體流程可以看下圖:
上述圖片描述的就是責任鏈的調度過程,關于OkHttp的后續內容,后面文章繼續。
如果文章中有什么疏漏或者錯誤的地方,還望各位指正,你們的監督是我最大的動力,謝謝!
總結
以上是生活随笔為你收集整理的Android--OkHttp理解系列(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端项目部署
- 下一篇: JavaScript闭包函数的理解与使用