Android--OkHttp理解系列(一)
本篇文章主要對OkHttp進(jìn)行分析,主要內(nèi)容如下:
- OkHttp初識(shí)
- OkHttp基本執(zhí)行流程(Dispatcher)
- 攔截器(Interceptor)
- 責(zé)任鏈設(shè)計(jì)模式
1. OkHttp 初識(shí)
OkHttp 在開發(fā)中經(jīng)常使用到,常見的用法是:
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} }); 復(fù)制代碼上述代碼是基于異步的網(wǎng)絡(luò)請求,同步的網(wǎng)絡(luò)請求不同的地方是:
try {response execute = call.execute(); } catch (ioexception e) {//nop } 復(fù)制代碼不相同的地方點(diǎn)在于call對象調(diào)用的方法,那么,同步調(diào)用與異步調(diào)用主要的差別是什么? call.execute() 方法將直接進(jìn)行網(wǎng)絡(luò)請求,阻塞當(dāng)前線程直到獲得網(wǎng)絡(luò)請求的響應(yīng)。異步執(zhí)行會(huì)將call放入一個(gè)異步執(zhí)行隊(duì)列中,由executorservice在后臺(tái)進(jìn)行執(zhí)行。
2. 基本執(zhí)行流程
基本執(zhí)行流程主要談的是call的執(zhí)行流程,在call的執(zhí)行流程中分為同步執(zhí)行流程與異步執(zhí)行流程。call其實(shí)是一個(gè)繼承了cloneable的接口,我們調(diào)用call call = okhttpclient.newcall(request); 獲取的其實(shí)是realcall這個(gè)對象,因此下文提到的call其實(shí)就是realcall。
2.1 Call的同步執(zhí)行流程
通過realcall.excute()方法執(zhí)行的流程為:
2.2 Call的異步執(zhí)行流程
通過realcall.enqueue(callback)方法執(zhí)行的流程首先是,client.dispatcher().enqueue(new asynccall(responsecallback)); 創(chuàng)建一個(gè)asynccall,將enqueue的callback傳遞過去。asynccall其實(shí)是一個(gè)runnable,當(dāng)調(diào)用enqueue()方法的時(shí)候就是講asynccall傳遞給dispatcher。dispatcher里面封裝了一個(gè)線程池去執(zhí)行這個(gè)call,executorservice().execute(call); ,這個(gè)call就是一個(gè)runnable,我們跟進(jìn)這個(gè)runnable的run()方法,我們可以看到里面執(zhí)行了一個(gè)execute() ;所以邏輯最終回到了asynccall的execute()方法。realcall.asynccall.execute()與同步執(zhí)行的流程有些類似:
2.3 Dispatcher干了些啥
通過上面call的執(zhí)行流程,我們可以看出其實(shí)okhttp的核心其實(shí)是dispatcher這個(gè)分發(fā)器,接下來我們詳細(xì)分析okhttp中dispatcher在網(wǎng)絡(luò)請求的時(shí)候做了些什么事情。
2.3.1 同步Dispatcher
首先看下代碼:
/** used by {@code call#execute} to signal it is in-flight. */ synchronized void executed(realcall call) {runningsynccalls.add(call); } 復(fù)制代碼從上面的代碼可以看出,大體的執(zhí)行邏輯就是將傳遞過來的realcall添加進(jìn)了一個(gè)隊(duì)列中,那么這個(gè)runningsynccalls到底是什么?看下源碼:
/** running synchronous calls. includes canceled calls that haven't finished yet. */ private final deque<realcall> runningsynccalls = new arraydeque<>(); 復(fù)制代碼在call同步執(zhí)行的過程中,dispatcher僅僅將call放進(jìn)了runningsynccalls這個(gè)隊(duì)列,其他的什么都沒有做,這個(gè)隊(duì)列包含了正在執(zhí)行的call,而將call注冊該隊(duì)列主要的作用是方便全局管理運(yùn)行的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);} } 復(fù)制代碼在異步調(diào)度中,傳遞過來的asynccall是被放在線程池中進(jìn)行處理的。這個(gè)線程池是什么?來看看代碼:
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; } 復(fù)制代碼默認(rèn)該線程池是一個(gè)不限制大小的線程池。從前面代碼我們可以看到,dispatcher會(huì)限制每一個(gè)host請求的最大限制,private int maxrequestsperhost = 5; 同時(shí)也會(huì)限制同時(shí)執(zhí)行的最大請求數(shù)量,private int maxrequests = 64;。在runningasynccalls隊(duì)列中,保留全部滿足限制條件而正在被executorservice執(zhí)行的所有asynccall,而不滿足限制條件的則由readyasynccalls進(jìn)行保存。
在異步調(diào)度中如果當(dāng)前的call不能立即入隊(duì)執(zhí)行的話,那么會(huì)執(zhí)行readyasynccalls.add(call);這個(gè)方法不會(huì)立即執(zhí)行而是需要進(jìn)行等待 ,進(jìn)入等待則是說明當(dāng)前的情況是要么有64條線程正在并發(fā),要么同一個(gè)地址有5 個(gè)請求,那么等待的call什么時(shí)候會(huì)被再次執(zhí)行呢? 他的觸發(fā)條件為:
1. Dispatcher 的setMaxRequestPerHost() 方法被調(diào)用時(shí)候 ; 2. Dispatcher 的setMaxRequests() 被調(diào)用時(shí)候; 3. 當(dāng)有一條請求結(jié)束了 執(zhí)行了finish()的出隊(duì)操作, 這個(gè)時(shí)候會(huì)觸promoteCalls()方法執(zhí)行 ,從而進(jìn)行調(diào)整。 復(fù)制代碼2.3.3 dispatcher的finished()
在finished()中我們看看其實(shí)現(xiàn)原理:
/** 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();} } 復(fù)制代碼同步或者異步dispatcher的finished()方法最后都會(huì)執(zhí)行到finished()方法,在該方法中除了會(huì)從runningsyncalls隊(duì)列中移除當(dāng)前正在被執(zhí)行的call,異步方法還會(huì)檢查由于限制條件(這里的限制條件是指最大請求數(shù)與單個(gè)host最大的請求數(shù)量)而保存在readyasyncalls隊(duì)列中的asynccall從而進(jìn)行移除。在異步方法中關(guān)鍵代碼:finished(runningasyncalls, call, true);第三個(gè)參數(shù)就是判斷是否需要移除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.} } 復(fù)制代碼在promotecalls()中,主要的邏輯就是如果當(dāng)前線程大于maxRequest則不進(jìn)行操作,如果小于maxRequest則遍歷整個(gè)readyAsyncCalls,取出一個(gè)call,并把這個(gè)call放入runningAsyncCalls,然后執(zhí)行execute。如果在遍歷過程中runningAsyncCalls超過maxRequest則不再添加,否則一直添加。總結(jié)一下,promoteCalls()負(fù)責(zé)ready的Call到running的call的轉(zhuǎn)換,在finished()方法中,所有的call,不管是realcall或者asynccall都會(huì)在執(zhí)行完畢之后檢測是否存在正在進(jìn)行的http請求,檢測的方法為:
public synchronized int runningcallscount() {return runningasynccalls.size() + runningsynccalls.size(); } 復(fù)制代碼當(dāng)判斷runningcallscount為0的時(shí)候,且該idlecallback存在的時(shí)候,回調(diào)idlecallback的run()方法。那么idlecallback什么時(shí)候存在?或者說調(diào)度器什么時(shí)候處于空閑狀態(tài)?繼續(xù)分析源碼:
/*** 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; } 復(fù)制代碼從上面方法中追溯到,調(diào)度器的空閑狀態(tài)在異步方法調(diào)度時(shí),在callback的onResponse()方法或者onfailed()方法被回調(diào)的時(shí)候,調(diào)度器處于空閑狀態(tài)。同步方法中只有在excute()方法返回的時(shí)候才會(huì)處于空閑狀態(tài)。
3. 攔截器
在okhttp中真正核心的除了上面提到的dispatcher還有就是攔截器interceptor。在同步請求方法或者異步請求方法中,都會(huì)執(zhí)行一條很重要的指令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); } 復(fù)制代碼從上面源碼中可以看到,在一次網(wǎng)絡(luò)請求中其實(shí)是經(jīng)歷了多次攔截器的調(diào)用,首先先用一個(gè)流程圖來大體的分析各個(gè)攔截的調(diào)用過程:
從上面的流程可以看出,okhttp調(diào)用了多個(gè)攔截器來對一個(gè)請求做攔截操作,那么這樣的操作是怎么完成的呢?其實(shí)在每一個(gè)攔截器后面都會(huì)調(diào)用RealInterceptorChain.proceed()來進(jìn)行處理,回到上面的源碼,okhttp將所有的攔截器添加進(jìn)入了一個(gè)集合之中,利用自增遞增來調(diào)用RealInterceptorChain.proceed() 來依次處理添加進(jìn)入的攔截器。
跟進(jìn)下RealInterceptorChain的源碼:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection) throws IOException {//...異常判斷省略// 獲取下一個(gè)攔截器.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;} 復(fù)制代碼RealInterceptorChain 構(gòu)造函數(shù)中的index與interceptor進(jìn)行對應(yīng),通過interceptor.intercept(next)方法在各個(gè)攔截器里面又能通過next參數(shù)繼續(xù)調(diào)用proceed()方法,完成遞歸調(diào)用。至于各個(gè)攔截器具體的功能,相信大家都大概清楚,這里就不多闡述了。
4. 責(zé)任鏈設(shè)計(jì)模式
在okHttp中攔截器部分設(shè)計(jì)到的核心就是責(zé)任鏈設(shè)計(jì)模式,首先回顧下什么是責(zé)任鏈模式:使多個(gè)對象都有機(jī)會(huì)處理 同一個(gè)請求,從而避免請求的發(fā)送者與接收者之間的耦合關(guān)系。將這些對象連接成一條鏈,并沿著這條鏈傳遞 請求,直到有一個(gè)對象處理它為止。 現(xiàn)在我們來剖析OkHttp中的調(diào)用鏈,回顧下在OkHttp中的調(diào)用鏈結(jié)構(gòu):
上面的調(diào)用模式核心是通過Interceptor來進(jìn)行完成的,該接口主要的作用為添加、移除、轉(zhuǎn)換請求或者回應(yīng)頭部信息。瞟一眼該接口的源碼:
public interface Interceptor { Response intercept(Chain chain) throws IOException;interface Chain {Request request();Response proceed(Request request) throws IOException;//省略} } 復(fù)制代碼Interceptor 中intercept(Chain chain)方法負(fù)責(zé)具體的過濾,而它子類中又調(diào)用了Chain, 該Chain其實(shí)是指RealInterceptorChain ,在RealInterceptorChain中持有了一個(gè)interceptor 的集合 ,通過遞歸調(diào)用該List中的攔截器, 對發(fā)起的請求進(jìn)行層層處理,最后遞歸結(jié)束返回請求結(jié)果。
4.1 具體分析責(zé)任鏈模式
上面大體的說明了在OkHttp中責(zé)任鏈的調(diào)用,現(xiàn)在詳細(xì)分析該責(zé)任鏈怎么完成調(diào)用。責(zé)任鏈模式其實(shí)分為完全責(zé)任鏈模式與不完全責(zé)任鏈模式。完全責(zé)任鏈模式是指: 當(dāng)請求到達(dá)某個(gè)處理類的時(shí)候,要么處理,要么傳遞給下個(gè)處理類。不完全責(zé)任鏈模式: 當(dāng)請求到達(dá)時(shí)候, 處理一部分( 可以配置一些參數(shù)或者 增加請求頭等),然后扔給下一個(gè)處理類進(jìn)行處理。在Okhttp中,調(diào)用的責(zé)任鏈就是不完全的責(zé)任鏈模式。責(zé)任鏈模式的特點(diǎn):
1. 抽象一個(gè)處理任務(wù)類; 2. 任務(wù)處理類持有下一個(gè)任務(wù)對象; 3. 完全責(zé)任鏈模式處理請求任務(wù)時(shí)類似于if-else,不完全責(zé)任鏈模式則對一個(gè)請求進(jìn)行分步處理。 復(fù)制代碼在OkHttp中,發(fā)起的請求經(jīng)過攔截器的層層處理最終返回結(jié)果,它具體是怎么操作的? 回溯到Okhttp的 getResponseWithInterceptorChain()該方法在okHttp中同步或者異步最終調(diào)用的方法,也是責(zé)任鏈調(diào)用的起始方法。
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); } 復(fù)制代碼接下來介紹核心類RealInterceptorChain ,該類是整個(gè)責(zé)任鏈的調(diào)度中心,該類繼承了Interceptor的內(nèi)部接口Chain。
interface Chain { Request request(); Response proceed(Request request) throws IOException; //省略...復(fù)制代碼request() 獲取的是當(dāng)前的請求,而proceed() 就是負(fù)責(zé)具體的轉(zhuǎn)發(fā)任務(wù), 看看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; } 復(fù)制代碼該方法核心的就這么幾步:
1. 根據(jù)傳入進(jìn)來的責(zé)任鏈集合和索引獲取下一個(gè)責(zé)任鏈(next) ,即每一個(gè)攔截器會(huì)持有下一個(gè)鏈對象; 2. 從攔截器集合中獲取當(dāng)前的攔截器; 3. 攔截處理下一個(gè)責(zé)任鏈(該鏈中傳入當(dāng)前的請求,只是索引+1 ,那么執(zhí)行的邏輯就是當(dāng)前的攔截器如果處理了就直接返回,如果沒有執(zhí)行或者執(zhí)行了部分,那么就封裝一個(gè)新的請求對象,由下一個(gè)鏈進(jìn)行處理,下一個(gè)鏈又對應(yīng)一個(gè)攔截器,同理,如果處理了就直接返回,沒有處理了話,下一個(gè)攔截器處理一部分再次扔給攔截器集合中的下一個(gè)攔截器,這樣進(jìn)行遞歸調(diào)用,直到返回結(jié)果)。 復(fù)制代碼具體調(diào)用如下:
整體流程可以看下圖:
上述圖片描述的就是責(zé)任鏈的調(diào)度過程,關(guān)于OkHttp的后續(xù)內(nèi)容,后面文章繼續(xù)。
如果文章中有什么疏漏或者錯(cuò)誤的地方,還望各位指正,你們的監(jiān)督是我最大的動(dòng)力,謝謝!
總結(jié)
以上是生活随笔為你收集整理的Android--OkHttp理解系列(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端项目部署
- 下一篇: Android 6.0 超级简单的权限申