那么我今天給大家簡單地講一下Okhttp這款網(wǎng)絡(luò)框架及其原理。它是如何請求數(shù)據(jù),如何響應(yīng)數(shù)據(jù)的 ?有什么優(yōu)點?它的應(yīng)用場景是什么?6z
說到Okhtpp的原理他是基于原生的Http,他的優(yōu)點主要是如下幾點:
1.支持同步、異步。
2.支持GZIP減少數(shù)據(jù)流量
3.緩存響應(yīng)數(shù)據(jù)(從而減少重復(fù)的網(wǎng)絡(luò)請求)
4.自動重連(處理了代理服務(wù)問題和SSL握手失敗的問題)
5.支持SPDY(共享同一個Socket來處理同一個服務(wù)器的請求,如果SPDY不可用,則通過連接池來減少請求延時)
它的應(yīng)用場景呢是在數(shù)據(jù)量特別大重量級的網(wǎng)絡(luò)請求
上面說了一下他的優(yōu)點,下面說一下他是如何發(fā)起網(wǎng)絡(luò)請求如何響應(yīng)數(shù)據(jù)的
OkHttp中的重要類:
1,OkHttpClient:OkHttp請求客戶端,Builder模式實現(xiàn)
2,Dispatcher:本質(zhì)是異步請求的調(diào)度器,負(fù)責(zé)調(diào)度異步請求的執(zhí)行,控制最大請求并發(fā)數(shù)和單個主機(jī)的最大并發(fā)數(shù),并持有有一個線程池負(fù)責(zé)執(zhí)行異步請求,對同步請求只是作統(tǒng)計操作。
3,Request:封裝網(wǎng)絡(luò)請求,就是構(gòu)建請求參數(shù)(如url,header,請求方式,請求參數(shù)),Builder模式實現(xiàn)
4,Response:網(wǎng)絡(luò)請求對應(yīng)的響應(yīng),Builder模式實現(xiàn),真正的Response是通過RealCall.getResponseWithInterceptorChain()方法獲取的。
5,Call:是根據(jù)Request生成的一個具體的請求實例,且一個Call只能被執(zhí)行一次。
6,ConnectionPool:Socket連接池
7,Interceptor:Interceptor可以說是OkHttp的核心功能,它就是通過Interceptor來完成監(jiān)控管理,重寫和重試請求的。
8,Cache:可以自定義是否采用緩存,緩存形式是磁盤緩存,DiskLruCache。
不管是同步請求還是異步請求,都是通過RealCall.getResponseWithInterceptorChain()方法獲取請求結(jié)
果的,只不過在前者在主線程中執(zhí)行,而后者在線程池中的線程中執(zhí)行的。
一個典型的請求過程是這樣的,用一個構(gòu)造好的OkHttpClient和Request獲取到一個Call,然后執(zhí)行call的異步或者同步方法取得Response或者處理異常,如下所示:
OkHttpClient ?okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
????????.url("")
????????.build();
okHttpClient.newCall(request).enqueue(new Callback() {
????@Override
????public void onFailure(Call call, IOException e) {
????}
????@Override
????public void onResponse(Call call, Response response) throws IOException {
????}
});
OkHttp3,網(wǎng)絡(luò)請求庫,同步請求RealCall.execute()和異步請求RealCall.enqueue(),請求任務(wù)都是交給Dispatcher調(diào)度請求任務(wù)的處理,請求通過一條攔截鏈,每一個攔截器處理一部分工作,最后一個攔截器,完成獲取請求任務(wù)的響應(yīng),會將響應(yīng)沿著攔截鏈向上傳遞。
這里實際上,Call的實現(xiàn)是一個RealCall的類,execute的代碼如下:
final class RealCall implements Call {
@Override public Response execute() throws IOException {
??synchronized (this) {
????if (executed) throw new IllegalStateException("Already Executed");
????executed = true;
??}
??try {
????client.dispatcher().executed(this);
????Response result = getResponseWithInterceptorChain(false);
????if (result == null) throw new IOException("Canceled");
????return result;
??} finally {
????client.dispatcher().finished(this);
??}
}
}
檢查這個call是否已經(jīng)被執(zhí)行了,每個call?只能被執(zhí)行一次
而enqueue實際上是RealCall的將內(nèi)部類AsyncCall扔進(jìn)了dispatcher中:client.dispatcher().enqueue(new AsyncCall(responseCallback));
public final class Dispatcher {
synchronized void enqueue(AsyncCall call) {
??if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
????runningAsyncCalls.add(call);
????executorService().execute(call);
??} else {
????readyAsyncCalls.add(call);
??}
}
。AsyncCall實際上是一個Runnable,我們看一下進(jìn)入線程池后真正執(zhí)行的代碼:
final class AsyncCall extends NamedRunnable
public abstract class NamedRunnable implements Runnable
AsyncCall 實現(xiàn)NameRunnable接口,丹NameRunnable又實現(xiàn)了Runnable接口
?@Override protected void execute() {
????boolean signalledCallback = false;
????try {
??????Response response = getResponseWithInterceptorChain(forWebSocket);
??????if (canceled) {
????????signalledCallback = true;
????????responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
??????} else {
????????signalledCallback = true;
????????responseCallback.onResponse(RealCall.this, response);
??????}
????} catch (IOException e) {
??????if (signalledCallback) {
????????// Do not signal the callback twice!
????????logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
??????} else {
????????responseCallback.onFailure(RealCall.this, e);
??????}
????} finally {
??????client.dispatcher().finished(this);
????}
??}
}
?client.dispatcher().finished(this);這里邊this代表著AsycCall
于是這里需要介紹一個Dispatcher的概念。Dispatcher的本質(zhì)是異步請求的管理器,控制最大請求并發(fā)數(shù)和單個主機(jī)的最大并發(fā)數(shù),并持有一個線程池負(fù)責(zé)執(zhí)行異步請求。
對同步的請求只是用作統(tǒng)計。
他是如何做到控制并發(fā)呢,
其實原理就在上面的execute代碼里面,
真正網(wǎng)絡(luò)請求執(zhí)行前后會調(diào)用executed和finished方法,執(zhí)行和完成
而對于AsyncCall的finished方法后,
會根據(jù)當(dāng)前并發(fā)數(shù)目選擇是否執(zhí)行隊列中等待的AsyncCall。
并且如果修改Dispatcher的maxRequests或者maxRequestsPerHost也會觸發(fā)這個過程。最大請求主機(jī)
好的,
在回到RealCall中,我們看到無論是execute還是enqueue,真正的Response是通過這個函數(shù)getResponseWithInterceptorChain獲取的,
其他的代碼都是用作控制與回調(diào)。而這里就是真正請求的入口,也是到了OkHttp的一個很精彩的設(shè)計:Interceptor與Chain
上面分析到了,網(wǎng)絡(luò)請求的入口實質(zhì)上是在這里getResponseWithInterceptorChain
private?Response getResponseWithInterceptorChain() throws?IOException {
?// Build a full stack of interceptors. ???????????????????????????????????????????????????????????????????????????????????????????????????//構(gòu)建一個完整的攔截器堆棧 ?List 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?(!retryAndFollowUpInterceptor.isForWebSocket()) {
??interceptors.addAll(client.networkInterceptors());
?}
?interceptors.add(new?CallServerInterceptor(
????5.isForWebSocket()));
?Interceptor.Chain chain = new?RealInterceptorChain(
????interceptors, null, null, null, 0, originalRequest);
?return?chain.proceed(originalRequest);
}
下面這是OkHttp內(nèi)置攔截器的思維導(dǎo)圖↓
RetryAndFollowUpInterceptor ???后繼攔截器
@Override public Response intercept(Chain chain) throws IOException {
??Request request = chain.request();
??streamAllocation = new StreamAllocation(
??????client.connectionPool(), createAddress(request.url()), callStackTrace);
后繼攔截器 攔截
在OkHttp的RetryAndFollowUpInterceptor請求重定向的Interceptor中是根據(jù)當(dāng)前請求獲取的Response,來決定是否需要進(jìn)行重定向操作。在followUpRequest方法中,將會根據(jù)響應(yīng)userResponse,獲取到響應(yīng)碼,并從連接池StreamAllocation中獲取連接,然后根據(jù)當(dāng)前連接,得到路由配置參數(shù)Route。觀察以下代碼可以看出,這里通過userResponse得到了響應(yīng)碼responseCode。
接下來該方法會通過switch…case…來進(jìn)行不同的響應(yīng)碼處理操作。
從這段代碼開始,都是3xx的響應(yīng)碼處理,這里就開始進(jìn)行請求重定向的處理操作。
重試與重定向攔截器,用來實現(xiàn)重試和重定向功能,內(nèi)部通過while(true)死循環(huán)來進(jìn)行重試獲取Response(有重試上限,超過會拋出異常)。followUpRequest主要用來根據(jù)響應(yīng)碼來判斷屬于哪種行為觸發(fā)的重試和重定向(比如未授權(quán),超時,重定向等),然后構(gòu)建響應(yīng)的Request進(jìn)行下一次請求。當(dāng)然,如果沒有觸發(fā)重新請求就會直接返回Response。
當(dāng)網(wǎng)絡(luò)連接失敗時重試
當(dāng)服務(wù)器返回當(dāng)前請求需要進(jìn)行重定向是直接發(fā)起新的去請求,并在條件允許的情況下復(fù)用此鏈接。
主要做了三件事StreamAllocation:流量分配
[if !supportLists]?[endif]創(chuàng)建了StreamAllocation,用于Socket管理
[if !supportLists]?[endif]處理重定向
[if !supportLists]?[endif]失敗重連
BridgeInterceptor ????橋接攔截器
在請求階段自動補(bǔ)全請求頭,在響應(yīng)階段對GZIP進(jìn)行解壓縮
就是告訴服務(wù)器客戶端能夠接受的數(shù)據(jù)編碼類型,OKHTTP默認(rèn)就是 GZIP 類型
GZIP:
GZIP是網(wǎng)站壓縮加速的一種技術(shù),對于開啟后可以加快我們網(wǎng)站的打開速度,原理是經(jīng)過服務(wù)器壓縮,客戶端瀏覽器快速解壓的原理,可以大大減少了網(wǎng)站的流量。
Gzip開啟以后會將輸出到用戶瀏覽器的數(shù)據(jù)進(jìn)行壓縮的處理,這樣就會減小通過網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)量,提高瀏覽的速度。
是一種流行的文件壓縮算法,現(xiàn)在的應(yīng)用十分廣泛,尤其是在Linux平臺。當(dāng)應(yīng)用Gzip壓縮到一個純文本文件時,效果是非常明顯的,大約可以減少70%以上的文件大小。這取決于文件中的內(nèi)容。
BridgeInterceptor 功能主要有以下三點:
是負(fù)責(zé)將用戶構(gòu)建的一個Request請求轉(zhuǎn)化為能夠進(jìn)行網(wǎng)絡(luò)訪問的請求。
將這個符合網(wǎng)絡(luò)請求的Request進(jìn)行網(wǎng)絡(luò)請求。
Response networkResponse = chain.proceed(requestBuilder.build());
將網(wǎng)絡(luò)請求回來的響應(yīng)Response轉(zhuǎn)化為用戶可用的 Response
Transfer-Encoding值為 chunked 表示請求體的內(nèi)容大小是未知的。
Host請求的 url 的主機(jī)
Connection默認(rèn)就是 "Keep-Alive",就是一個 TCP 連接之后不會關(guān)閉,保持連接狀態(tài)。
Accept-Encoding默認(rèn)是 "gzip" 告訴服務(wù)器客戶端支持 gzip 編碼的響應(yīng)。
Cookie當(dāng)請求設(shè)置了 Cookie 那么就是添加 Cookie 這個請求頭。
User-Agent "okhttp/3.4.1"這個值根據(jù) OKHTTP 的版本不一樣而不一樣,它表示客戶端 的信息。
上面就是將一個普通的Request添加很多頭信息,讓其成為可以發(fā)送網(wǎng)絡(luò)請求的 Request?
CacheInterceptor緩存攔截器
CacheInterceptor負(fù)責(zé)在request階段判斷是否有緩存,是否需要重新請求。在response階段負(fù)責(zé)把response緩存起來
緩存其實是一個非常復(fù)雜的邏輯,單獨的功能模塊,它其實不屬于OkHttp上的功能,只是通過Http協(xié)議和DiskLruCache做了處理而已。
DiskLruCache是Android提供的一個管理磁盤緩存的類。該類可用于在程序中把從網(wǎng)絡(luò)加載的數(shù)據(jù)
保存到磁盤上作為緩存數(shù)據(jù),例如一個顯示網(wǎng)絡(luò)圖片的gridView,可對從網(wǎng)絡(luò)加載的圖片進(jìn)行緩存,
提高程序的可用性。
剛才也提到了OkHttp的緩存攔截器不是屬于OkHttp上的功能,他是通過Http協(xié)議和DiskLruCache做的處理 ?一張圖來看一下緩存策略↓
ETag響應(yīng)頭部字段值是一個實體標(biāo)記,它提供一個“不透明”的緩存驗證器
兩種緩存的區(qū)別:
對于強(qiáng)制緩存,服務(wù)器通知瀏覽器一個緩存時間,在緩存時間內(nèi),下次請求,直接用緩存,不在時間內(nèi),執(zhí)行對比緩存策略。
對于對比緩存,將緩存信息中的Etag和Last-Modified通過請求發(fā)送給服務(wù)器,由服務(wù)器校驗,返回304狀態(tài)碼時,瀏覽器直接使用緩存。
HTTP的緩存規(guī)則是優(yōu)先考慮強(qiáng)制緩存,然后考慮對比緩存。
[if !supportLists]?[endif]首先判斷強(qiáng)制緩存中的數(shù)據(jù)的是否在有效期內(nèi)。如果在有效期,則直接使用緩存。如果過了有效期,則進(jìn)入對比緩存。
[if !supportLists]?[endif]在對比緩存過程中,判斷ETag是否有變動,如果服務(wù)端返回沒有變動,說明資源未改變,使用緩存。如果有變動,判斷Last-Modified。
[if !supportLists]?[endif]判斷Last-Modified,如果服務(wù)端對比資源的上次修改時間沒有變化,則使用緩存,否則重新請求服務(wù)端的數(shù)據(jù),并作緩存工作。
CacheStrategy緩存策略
直接看CacheStrategy的get方法。緩存策略是由請求和緩存響應(yīng)共同決定的。
[if !supportLists]?[endif]如果緩存響應(yīng)為空,則緩存策略為不使用緩存。
[if !supportLists]?[endif]如果請求是https但是緩存響應(yīng)沒有握手信息,同上不使用緩存。
[if !supportLists]?[endif]如果請求和緩存響應(yīng)都是不可緩存的,同上不使用緩存。
[if !supportLists]?[endif]如果請求是noCache,并且又包含If-Modified-Since或If-None-Match,同上不使用緩存。
[if !supportLists]?[endif]然后計算請求有效時間是否符合響應(yīng)的過期時間,如果響應(yīng)在有效范圍內(nèi),則緩存策略使用緩存。
[if !supportLists]?[endif]否則創(chuàng)建一個新的有條件的請求,返回有條件的緩存策略。
[if !supportLists]?[endif]如果判定的緩存策略的網(wǎng)絡(luò)請求不為空,但是只使用緩存,則返回兩者都為空的緩存策略。
CacheInterceptor的總體流程大致是:?