OkHttp源碼分析

那么我今天給大家簡單地講一下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的總體流程大致是:?

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • OkHttp源碼分析-同步篇 很早就想拿okhttp開刀了,這次就記一次使用OKhttp的網絡請求。首先需要說明的...
    埃賽爾閱讀 1,008評論 1 2
  • OkHttp作為時下最受歡迎的網絡請求框架之一,它有著自己的優點: 使用了眾多的設計模式(如:Builder模式、...
    永恒之眼V閱讀 308評論 1 1
  • 一、Okhttp的使用 1、創建一個OKHttpClient對象 2、創建一個request對象,通過內部類Bui...
    千涯秋瑟閱讀 385評論 0 0
  • 好教師不僅僅是上好課那么簡單,還應做一個超越者。 通過校本研修 引領教師做到三個超越 : 1、超越課堂界限,把教育...
    鄭國永閱讀 318評論 0 0
  • Reverse a String Factorialize a Number Check for Palindro...
    Rough33閱讀 290評論 0 0