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