Okhttp3-网络请求流程解析
前言
已經(jīng)大火2年的Retrofit,必然會提到另外兩個庫,OKhttp3和Rxjava,尤其前者,作為Retrofit網(wǎng)絡(luò)請求的底層庫,我們有必要了解OKhttp3的網(wǎng)絡(luò)請求是如何運作的,就會理解為什么OKhttp3比其它網(wǎng)絡(luò)請求庫更高效,為什么Volley,Glide,Picasso在后續(xù)版本紛紛改用或支持OKhttp3作為自身網(wǎng)絡(luò)傳輸層的底層庫。
基本構(gòu)成
OKhttp3作為Square公司(貢獻了很多優(yōu)秀的開源庫,比如Retrofit,OKhttp,Okio,Picasso等)開發(fā),旨于替換Java的HttpUrlConnection和Apache的HttpClient的輕量級網(wǎng)絡(luò)框架,已經(jīng)被運用到很多開源庫以及Android的源碼中(Android Studio 在6.0之后,移除了HttpClient,并且用OKHttp代替了HttpUrlConnection)。
和其它網(wǎng)絡(luò)框架類似,OKhttp3也主要由以下6個概念一起運作:
1.OkHttpClient:客戶端;
2.Dispatcher:線程池;
3.Interceptor:攔截器(OKhttp的特色);
4.Request:請求;
5.Response:響應(yīng);
6.CallBack:回調(diào)。
Get請求流程
1.創(chuàng)建OkHttpClient客戶端.
2.創(chuàng)建Request請求,設(shè)置url.
3.通過OkHttpClient的newCall()方法,將Request包裝成一個Call接口.
4.最后調(diào)用execute()方法得到同步的響應(yīng)Response.
5.或者調(diào)用execute()方法,以CallBack回調(diào)接口作為參數(shù),得到異步的響應(yīng)Response.
POST請求流程
流程和GET請求類似.
不同的地方在于:
1.需要RequestBody請求體封裝各種類型的請求參數(shù).
2.調(diào)用Request.Builder的post()方法,傳入RequestBody,設(shè)置為POST請求.
接下來,我們對其內(nèi)部的流程進行分析
Get請求內(nèi)部流程分析
第一步:OkHttpClient client = new OkHttpClient();
1.通過OkHttpClient的Builder的默認構(gòu)造方法來初始化網(wǎng)絡(luò)所需的各種成員:
這些成員依次為:
Dispatcher:線程池
Proxy:代理
Protocol:協(xié)議
ConnectionSpec:連接規(guī)則
Interceptor:攔截器
ProxySelector:代理選擇器
CookieJar:Cookie緩存
Cache:緩存
SocketFactory:Socket工廠
HostnameVerifier:主機校隊
SSLSocketFactory/CertificatePinner:SSL證書相關(guān)
ConnectionPool:連接池
等等
2.然后再由Builder將這些成員設(shè)置給OkHttpClient對象:
第二步:Request request = new Request.Builder().url("http://xxxxxx").build();
1.通過Request的Builder的默認構(gòu)造方法,初始化請求部分所需的請求方式method和默認的請求頭headers,
2.再由Builder的url()方法設(shè)置請求的鏈接地址,最后調(diào)用build()方法返回Request對象。
3.在Builder的build()方法中,調(diào)用了Request的構(gòu)造參數(shù),將method,headers等成員設(shè)置給Request對象。
第三步:Call call =client.newCall(request);
1.通過OkhttpClient的newCall()方法,構(gòu)建一個RealCall對象,且其持有第一,二步創(chuàng)建的OkhttpClient和Request對象的引用。
2.同時RealCall對象還會創(chuàng)建一個攔截器RetryAndFollowUpInterceptor。
第四步:
(一)同步請求:Response response =call.execute();
1.調(diào)用第三步的RealCall對象的execute()方法,該方法先檢查該RealCall對象是否已經(jīng)執(zhí)行過該方法了,重復執(zhí)行會拋出異常。
2.調(diào)用第一步的OkhttpClient的dispatcher()方法,獲取到OkhttpClient的線程池Dispatcher,然后執(zhí)行其executed()方法;
runningSyncCalls是Dispatcher中維護的一個正在執(zhí)行的同步請求隊列,RealCall對象會被加入到該隊列的末尾。
3.然后執(zhí)行RealCall對象的getResponseWithInterceptorChain()方法;
按固定順序?qū)r截器(依次為初始化OkhttpClient的攔截器,重試和重定向的攔截器,橋接轉(zhuǎn)換攔截器,緩存攔截器,連接攔截器,初始化OkhttpClient的網(wǎng)絡(luò)攔截器,服務(wù)器回調(diào)攔截器)添加到Interceptors集合中,與第二步的Request一起作為構(gòu)造參數(shù),創(chuàng)建了一個RealInterceptorChain對象。
這里注意構(gòu)造參數(shù)中的(this.index)0,就是RealInterceptorChain的成員變量index,用來標記執(zhí)行到了Interceptors集合中哪一個攔截器的intercept()方法。
4.緊接著執(zhí)行RealInterceptorChain對象的proceed()方法;
首先會作判斷:if(this.index >= this.interceptors.size()) {
throw new AssertionError();
} else { … },這個判斷有什么用呢,先看下面。
因為首次進入httpStream為null,所以不會執(zhí)行同一請求檢查this.sameConnection(request.url())和請求次數(shù)的檢查this.calls >1.
5.接著看以下三句代碼:
RealInterceptorChain next = new RealInterceptorChain(this.interceptors, streamAllocation, httpStream, connection, this.index +1, request);Interceptor interceptor = (Interceptor)this.interceptors.get(this.index);Response response = interceptor.intercept(next);(1)以Interceptors集合和Request對象,以及index+1作為構(gòu)造參數(shù),創(chuàng)建一個新的RealInterceptorChain對象;
(2)執(zhí)行index位置的攔截器的intercept()方法,同時將新創(chuàng)建的RealInterceptorChain對象傳遞進去,很明顯,這個RealInterceptorChain對象的proceed()方法又會被執(zhí)行,因此,結(jié)合第4點的代碼判斷,通過迭代,Interceptors集合中的所有攔截器都會執(zhí)行intercept()方法:
a.OkhttpClient的interceptors集合,默認是空集;
b.RetryAndFollowUpInterceptor攔截器:負責請求的重試和重定向,最多20次。
c.BridgeInterceptor橋接攔截器:負責請求構(gòu)建和響應(yīng)
d.CacheInterceptor緩存攔截器:負責網(wǎng)絡(luò)緩存操作
e.ConnectInterceptor連接攔截器:負責socket的IO操作,這里使用了Okio提供的封裝
socket操作就是在這個攔截器里執(zhí)行的。
f.OkhttpClient的networkInterceptors攔截器,默認是空集.
g.CallServerInterceptor攔截器:向服務(wù)器發(fā)送請求,將請求header和body寫入socket中,然后讀取響應(yīng)header和body,返回最后需要的響應(yīng)數(shù)據(jù).
下圖是CallServerInterceptor的intercept()方法的實現(xiàn)
(4)最后的CallServerInterceptor攔截器執(zhí)行完intercept()方法后,返回請求的響應(yīng)數(shù)據(jù):Response對象.
服務(wù)器響應(yīng)的數(shù)據(jù)主要通過其中的ResponseBody對象獲取。
5.最后不管請求是否成功,最后都會執(zhí)行Dispatcher的finished()方法,結(jié)束整個請求;
同步請求,不會執(zhí)行Dispatcher的promoteCalls()方法(這個方法在后面的異步請求再分析),通過runningSyncCalls隊列的remove()方法將RealCall從運行隊列中移除.
(二)異步請求:
call.enqueue(new Callback() {
@Override
public void onFailure(Call call,IOException e) {}
@Override
public void onResponse(Call call,Response response) throwsIOException {}
});
與同步請求不同的地方在于
1.執(zhí)行RealCall對象的的enqueue()方法,需要一個CallBack接口實現(xiàn)作為參數(shù),執(zhí)行最后請求的成功和失敗回調(diào);
該方法內(nèi)部是調(diào)用OkhttpClient的Dispatcher的enqueue()方法,同時傳入一個AsyncCall對象作為參數(shù),每個RealCall對象只能執(zhí)行一次。
2.該AsyncCall對象持有第1點中創(chuàng)建的CallBack對象.
3.如果正在執(zhí)行的異步隊列runningAsyncCalls沒有超過最大請求數(shù)(最大為64)并且該請求的主機的最大請求數(shù)沒有超過最大限制(最大為5)時,AsyncCall對象會被加入到runningAsyncCalls中;否則,AsyncCall會被加入到準備執(zhí)行的異步隊列readyAsyncCalls中。
4.如果AsyncCall加入了運行隊列,會通過Dispatcher的executorService()方法,創(chuàng)建一個單例線程池ThreadPoolExecutor。
使用到的ThreadPoolExecutor的構(gòu)造參數(shù):
corePoolSize:并發(fā)數(shù),maximumPoolSize:最小線程數(shù)為0,最大線程數(shù)為Integer.MAX_VALUE;
keepAliveTime:空閑線程的存活時間;
workQueue:先進先出的工作隊列;
threadFactory:單個線程的線程工廠。
緊接著調(diào)用ThreadPoolExecutor的execute()方法,
command就是傳入的AsyncCall,然后執(zhí)行addWorker()方法
在addWorker()方法中,可以發(fā)現(xiàn)firstTask(即上述的AsyncCall),被包裝成Worker后,再由其內(nèi)部的Thread執(zhí)行了start()方法。
5.AsyncCall 繼承自NamedRunnable 而NamedRunnable是Runnable接口的抽象實現(xiàn)。
ThreadPoolExecutor的execute()方法執(zhí)行了工作線程,觸發(fā)了線程內(nèi)部的Runnable(即AsyncCall )的run()方法,run()方法內(nèi)部執(zhí)行AsyncCall的execute()方法。
6.最后執(zhí)行的AsyncCall的execute()方法
調(diào)用RealCall的getResponseWithInterceptorChain()方法獲取最后響應(yīng)的數(shù)據(jù)Response(這一步的內(nèi)部流程和同步請求一樣,不再累述)。
如果中途通過retryAndFollowUpInterceptor攔截器取消了請求,或者拋出IO異常,則請求失敗,回調(diào)responseCallBack(即第1點傳入的CallBack接口實現(xiàn))的onFailure()方法;否則,請求成功,回調(diào)responseCallBack的onResponse()方法。
不管請求失敗還是成功,都會調(diào)用線程池Dispatcher的finished()方法。
異步請求在結(jié)束請求時,傳入的promoteCalls為true,將改請求從正在執(zhí)行的異步隊列中移除后,會額外執(zhí)行promoteCalls()方法,檢查是否有待執(zhí)行的請求。
promoteCalls()方法中,如果正在執(zhí)行的異步隊列的請求數(shù)小于最大請求數(shù),就會繼續(xù)檢查準備執(zhí)行的隊列中是否有還沒有執(zhí)行的請求,如果有,則取出最早存入準備執(zhí)行的隊列的AsyncCall,只要單個主機的請求數(shù)也小于最大請求數(shù),就會重復上述第3點的方法,將這個沒執(zhí)行的請求執(zhí)行下去。
從上述可以總結(jié)這個Dispatcher的特點:
1.Okhttp3的Dispatcher線程池,同步請求為一個工作對列,異步請求時通過一個工作隊列和一個準備隊列來互相配合,支持最大64個的并發(fā)請求,通過Deque隊列先進先出的特點控制請求執(zhí)行的順序,而不是通過鎖機制;
2.整個Dispatcher內(nèi)部只創(chuàng)建一個ThreadPoolExecutor,不保存最小的存活線程數(shù),最大線程數(shù)為Integer.MAX_VALUE,為每個正在執(zhí)行的請求創(chuàng)建一個線程,當線程空閑60s后,結(jié)束線程;
3.設(shè)置有主機數(shù)限制,最大每個主機支持5個請求。
在異步請求中,Dispatcher作為第一個接收請求的對象,根據(jù)當前正在執(zhí)行的請求的狀況,將新的請求指派到工作隊列中并發(fā)處理,或者添加到準備隊列中緩存起來;不限制單例線程池的最大線程數(shù),減少高并發(fā)時額外線程創(chuàng)建的時間耗費,同時不保留最小存活線程數(shù),設(shè)置線程空閑60s后銷毀,避免資源的長期占用;通過try catch finally 塊控制請求隊列的執(zhí)行順序,而沒有使用鎖機制,這幾個地方的設(shè)計都很巧妙。
而Volley的Diapatcher則是由1個緩存線程和默認4個線程的網(wǎng)絡(luò)線程池組成,線程池采用輪詢的機制,這在應(yīng)對高并發(fā)和大數(shù)據(jù)的請求時并不算高效。
POST請求
內(nèi)部流程和GET請求基本一樣,除了在上述第二步生成的Request對象時,需要額外的RequestBody封裝不同類型的請求參數(shù)外。
結(jié)語
Okhttp3目前已經(jīng)有很多通用的第三方封裝框架,但是如果配合Rxjava使用,建議使用Okhttp3的同步請求,自己封裝一層,可以滿足一般項目開發(fā)的基本需求。從上面可以看到,其實源碼并不是那么難懂,尤其是同步請求,只要多看幾遍,即可看到不少Okhttp3的內(nèi)部運作流程和巧妙之處,這也是建議在配合Rxjava使用時,自己封裝一層的原因。當然,如果是配合Retrofit和Rxjava使用,那么就不需要對其過度封裝了,因為Retrofit本身就是對Okhttp3的封裝庫。這次分析流程比較長,還是建議自己寫幾個例子后,逐步去分析,會對其內(nèi)部的流程有一個很明了的認知。
原文發(fā)布時間為:2018-07-09
本文作者:lzt橘子
本文來自云棲社區(qū)合作伙伴“ 安卓巴士Android開發(fā)者門戶”,了解相關(guān)信息可以關(guān)注“ 安卓巴士Android開發(fā)者門戶”。
總結(jié)
以上是生活随笔為你收集整理的Okhttp3-网络请求流程解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 22. 括号生成
- 下一篇: react 按照一级路由 分包加载