okHttp源碼閱讀

使用方法

okhttp基本使用方法:

// 1、創建OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//2、創建Request對象
Request request = new Request.Builder()
.url(url)
.build();
//3、通過okHttpClient的newCall方法獲得一個Call對象
Call call = okHttpClient.newCall(request);
//4、請求
//同步請求
Response response = call.execute();
//異步請求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
 //子線程
}

@Override
public void onResponse(Call call, Response response) throws IOException {
  //子線程
    }
});

源碼閱讀

按照okhttp的使用方法流程來讀源碼,首先是構建一個okhttpClient對象。來看源碼是怎樣構建的。

構造方法裏new了一個Builder來看這個Builder類。


構造函數裏主要是初始化了一堆屬性變量和一些需要的對象,主要留意Dispatcher請求分發器和ConnectionPool連接池這兩個對象,比較重要。這裏可以看出來okhttpClient採用了建造者模式,okhttpClient對象還可以通過okhttpClient.Builder().build()來創建,可以設置一些自定義的屬性比如超時時間等。

build方法裏實際上也是new了一個okhttpClient對象。接下來進行第二個步驟構建Request對象。


從Request源碼可以看出他也是基於建造者模式,他的默認構造方法裏就兩行設置默認請求方式是GET,另外添加了一些請求頭信息。通常我們會使用new Request.Builder().url().build()方法構建Request來傳入我們自己url等一些參數。

接下來第三步就是通過okhttpClient對象的newCall方法創建一個Call對象。來看okhttpClient的newCall方法:

在這個方法中實際上調用的RealCall這個類的newRealCall方法,並把request傳進去。於是在進入RealCall類查看newRealCall方法:

可以看到newRealCall方法裏只是創建了一個RealCall對象返回和一個eventListener。

RealCall的構造函數中可以看到除了傳入的三個參數外,還新初始化一個RetryAndFollowUpInterceptor這麼一個重試重定向攔截器,這裏涉及到Okhttp裏的攔截器機制,先不管還是先記下有這麼一個攔截器。至此爲止,第三步結束,進入最後一個步驟通過call.execute()或者call.enqueue方法發送同步或者異步請求。先來看同步的execute方法。

首先看到在同步代碼塊中判斷了executed是否爲true,executed是用來標識這個Call對象是否執行過的,所以每個Call只能執行一次,否則就會拋出異常。接着調用client.dispatcher().executed(this)這個方法,這個dispatcher就是之前在okhttpClient構造函數裏初始化的那個請求分發器,來看這個dispatcher。

首先看這個dispatcher類中有哪些成員變量。默認規定了最大併發線程數、每個主機最大請求數,一個線程池用來執行Call,一個準備運行的異步請求隊列,一個正在運行的異步請求隊列,一個正在運行的同步請求隊列。緊接着回看到之前調用的executed方法代碼:

executed方法很簡單,就是把Call對象添加到正在運行的同步請求隊列中。

再回到call.execute方法中,client.dispatcher().executed(this)結束後執行了getResponseWithInterceptorChain()這個方法,返回的是請求結果Response,這個方法裏面是調用執行okhttp的攔截器鏈從而通過一個個攔截器執行完成組裝請求所需參數、設置緩存策略等等最終完成請求返回結果數據,涉及到okhttp的攔截器機制,先暫時不管,簡單地看成通過這個方法得到了返回的Response。最後在finally代碼塊中執行了client.dispatcher().finished(this);方法:

可以看到finished又調用重載方法,首先從同步運行隊列中remove了這個call,然後因爲promoteCalls爲false,沒有執行promoteCalls這個重新整理排序隊列的方法,直接執行了runngCallsCount()方法,計算正在運行的請求總數。

至此同步請求流程執行結束。接下來看異步請求,調用call.enqueue方法:

這裏可以看到和同步方法一樣先判斷是否執行過,然後調用client.dispatcher().enqueue(new AsyncCall(responseCallback));方法把傳進來的callback傳入一個AsyncCall對象。進入dispatcher的enqueue方法中:

方法中首先判斷正在運行的異步請求隊列是否達到最大請求數和每個主機的最大請求數,達到了就把call加入到準備隊列中,否則加入運行隊列並且交給消費者線程池處理。所以我們很容易想到AsyncCall實際上是個Runnable。先來看看這個線程池是怎樣創建的:

在初始化Dispatcher時候可以傳入一個線程池,如果沒傳,默認會new一個核心線程數爲0,最大線程數爲
Integer.MAX_VALUE,線程超時時間爲60秒的一個線程池,也就是說沒任務時線程池中不會存在空閒線程,當一個線程空閒超過60秒後就會被銷燬,同時最大線程數雖然爲Integer.MAX_VALUE但是由於運行隊列限制了最大請求數默認爲64個,所以也就不會因爲一直創建新線程而導致內存緊張。

再來看AsyncCall的代碼:

AsyncCall是RealCall的一個內部類他果然繼承了一個叫NameRunnable的接口,在NameRunnable的run方法裏調用了execute方法,而這個方法的實現又是在AysncCall中。

這裏同樣執行了getResponseWithInterceptorChain()這個方法獲得Response,然後判斷請求是否取消,取消回調onFailure,沒取消回調onResponse方法。最後在finally代碼塊中依舊調用了dispatcher().finished(this)方法

看清楚這次我們傳入的是AysncCall所以會走上面這個finished方法最終同樣的從運行隊列中移除當前call對象,這回因爲promoteCalls是true會執行promoteCalls這個方法了。

promoteCalls方法裏判斷了異步準備隊列無請求和異步運行隊列請求滿的情況,如果都不滿足,就從準備隊列中取出請求放入運行隊列中交給線程池處理並從準備隊列中移除。promoteCalls執行完後又重新計正在運行的請求總數。至此異步請求流程結束。
這是OkHttp的執行流程圖:

自己畫的一個OkHttp原理的簡化結構圖:

簡單的來說就是Call通過Dispatcher調度加入對應隊列,異步請求由消費者線程池從隊列中取出執行,調用攔截器鏈獲得響應結果返回,這麼一個過程。

接下來來看之前一直忽略的getResponseWithInterceptorChain()方法,來看看Response究竟是怎麼獲得的,這個攔截器鏈到底是什麼。

getResponseWithInterceptorChain方法裏的代碼也不是很多,創建了一個存放interceptor攔截器的List集
合,並往裏添加了許多攔截器,包括在RealCall構造函數中創建retryAndFollowUpInterceptor攔截器,之後創建了一個RealInterceptorChain真正的攔截器鏈對象,把剛纔的list傳入,並且調用了proceed方法傳入request。

可以看到proceed(request)又調用了一個四個參數的重載方法,撇開拋異常的代碼主要看143行,這裏同樣再次創建了一個next攔截器鏈對象,不同的是傳入的第五個參數爲index+1,這個index是幹什麼的,我們看到在getResponseWithInterceptorChain方法中創建攔截器鏈傳入的index是0,再看146行147行從傳入的攔截器list中取出下表爲index的攔截器並調用了他的intercept方法傳入了新的攔截器鏈next返回了response,進入
retryAndFollowUpInterceptor的intercept方法:

其他的代碼先不看,直接看126行,這裏又調用了傳進來攔截器鏈的proceed方法,至此就搞清楚了okhttp的攔截器機制的執行流程,在getResponseWithInterceptorChain方法中初始化初始的攔截器鏈,調用攔截器鏈的proceed方法,在proceed方法中創建新的攔截器鏈並把index+1傳入,取出下標爲index的攔截器執行其intercept方法傳入新的攔截器鏈,而在intercept方法中又會再次調用新攔截器鏈的proceed方法,如此循環往復,直至所有攔截器執行完成,返回最終的response。

這張圖介紹了okhttp中的攔截器分爲應用攔截器和網絡連接器,會看getResponseWithInterceptorChain方法:

可以看到攔截器List中除了添加client裏的應用攔截器和網絡攔截器之外,還有紅框這五個攔截器,接着來看看這些個攔截器是做什麼的?

RetryAndFollowUpInterceptor(重試重定向攔截器)

看名字就能猜到這個攔截器的功能,就是用來重連和重定向的,來看源碼:

主要看intercept方法,111行創建了一個StreamAllocation對象,這個對象是用來建立執行Http請求的一些組件的,可以看到傳參有連接池,根據request的url創建地址等。接下來就是一系列的判斷,在一個while(true)一直執行的循環中判斷請求是否被取消等如果一旦出現異常就release掉StreamAllocation對象。在153號拿到重定向的請求,主要看168行這裏判斷了最大的重連次數超出最大次數同樣releaseStreamAllocation對象。這就是這個攔截器的主要執行邏輯。

BridgeInterceptor(橋接攔截器)

還是看intercept方法,這裏先是拿到傳入的Request,然後爲這個Request添加一系列請求頭信息,例如看73行,connection,默認設置是keep-Alive,所以okhttp默認是長連接的。接下來93行,熟悉的調用攔截器鏈的proceed方法通過下面的攔截器獲得Response,100行開始判斷這個請求是否支持gzip壓縮,支持就轉換成GzipSource這個類型。

CacheInterceptor(緩存攔截器)

53行先判斷cache是否爲空,這個cache是在創建CacheInterceptor對象是傳進來的cache,也就是創建OkHttpClient是設置的Cache,我們可以在創建OkHttpClient時候設置具體的緩存路徑。59行創建了一個緩存策略,後面根據這個緩存策略來決定緩存是否有效,67行如果緩存Response不爲空,但緩存策略設置爲無效,則close這個Response;72行根據緩存策略既禁用網絡並且緩存無效,就返回一個code爲504,body空的Response;接下來85行,如果禁用網絡但是有緩存就直接返回緩存的Response,93行如果之前都無返回,繼續調用下一個攔截器訪問網絡返回Response;之後再看102行如果策略允許使用緩存並且網絡返回的ResponseCode等於304,說明服務器無變化,就直接使用緩存Response。123行調用網絡返回的Response.newBuilder()方法構建一個可以返回的Response對象,之後129行判斷httpHeaders裏有沒有響應體,並且緩存策略是可以被緩存的,就把這個新的Response寫入緩存;135行又判斷了這個Request的方法是否是有效的方法,否則就從緩存中移除這個Request。

ConnectInterceptor(網絡連接攔截器)

ConnectInterceptor中只做了兩件事,通過在重試重定向攔截器初始化的StreamAllocation對象調用它的newStream方法,獲得了一個HttpCodec對象,調用connection方法獲得一個真正的連接。然後就是調用proceed方法傳入下一個攔截器執行。首先來看newStream方法:

在114行調用findHealthyConnection方法獲得一個健康的連接,再通過這個Connection連接newCodec創建了HttpCodec對象並返回。進入findHealthyConnection方法:


看到在while循環中又調用了findConnection方法返回一個candidate連接,在140行判斷successCount是否爲0,正確就return返回,147行判斷這個candidate是否是健康的不是健康的就調用noNewStream方法然後繼續循環。

noNewStream方法裏將這個Connection置爲null並且關閉銷燬socket資源。接下來看findConnection方法:

主要看第174行,如果可以複用connection就複用這個connection,186行如果不能複用就從連接池中獲取一個新的connection,獲取connection之後在257行調用connect方法進行網絡連接,然後267行將連接put入連接池。

CallServerInterceptor(向服務器發送獲取響應的攔截器)

CallServerInterceptor獲得四個對象:攔截器鏈RealInterceptorChain、HttpCodec封裝的流對象、之前用到的StreamAllocation對象和Request。在第50行代碼,調用HttpCodec.writeRequestHeaders方法向socket裏寫入請求頭信息,在72行接着向socket寫入請求body信息,接下來84行調用finishRequest方法說明請求到此寫入結束。接着88行通過httpCodec.readResponseHeaders方法獲的響應的頭信息,在117行通過對websocket和code進行構建一個response,接下來就是對返回碼進行一些判斷拋出異常,無異常就返回response。

至此OkHttp的攔截器的機制的調用流程就閱讀完了。總結一下攔截器鏈的順序和功能流程圖:

Cache

在緩存攔截器中用到了Cache這個類,用來讀寫緩存。先來看一下在OkHttp怎樣設置緩存:

OkHttpClient client = new OkHttpClient.Builder()  
            .cache(new Cache(new File(this.getExternalCacheDir(), "okhttpcache"), 10 * 1024 * 1024))  
            .build();

在創建OkHttpClient可以是new一個Cache類設置緩存的路徑和大小。接下來就來看看這緩存Cache類:

Cache類裏有一個internalCache接口的實現,而這個實現裏的方法都是調用Cache裏的對應方法。除此之外,在169行有一個DiskLruCache對象,說明OkHttp的緩存全是基於DiskLruCache這個類實現的,接下來來看他的put方法:

在put方法的232行看到對請求的方法做了個判斷,對所有非GET請求方式的請求返回null,不予緩存;243行創建了一個Entry對象這個對象就是要緩存內容。

可以看到Entry裏主要是請求的url、請求頭、請求方法、響應碼、響應頭等。接着在246行,創建了DiskLruCache.Editor傳入key,key是以請求url進行MD5加密後轉十六進制存儲。然後250行調用Entry.wrireTo方法寫入緩存

writeTo方法裏寫入url、請求頭、請求方法、響應碼、響應頭等。接下來看get方法:

get方法裏首先同樣是根據url調用key方法獲得一個key,然後調用cache的get方法去緩存裏獲取一個snapshot,如果snapshot爲null就返回null;不爲空就用snapshot創建一個Entry對象再從Entry裏獲取到Response返回。

ConnectionPool

連接池(ConnectionPool)是用來管理維護整個okhttp裏的網絡連接Connection。它裏面主要有這麼幾個成員變量和方法:

第一個是個線程池,看註釋這個線程池是用於在後臺線程清除過期閒置的連接。一個cleanupRunnable用來清理連接。一個connections用於connection的緩存隊列。接下來看put方法:

put方法裏先判斷清理了一下閒置過期連接,然後就只是將connection加入緩存隊列。cleanupRunnable的run方法裏首先獲得下一次清理的時間然後調用wait方法等待下一次的清理。

get方法中循環了connections隊列,判斷連接是否是一個合格的連接,如果是就又調用StreamAllocation的acquire方法:

acquire方法裏首先是把傳過來的這個connection賦給了StreamAllocation裏的成員變量,然後又把這StreamAllocation的弱引用添加到這個connection中的allocations裏。

allocations就是一個存放引用的List集合,這裏就是爲了根據connection裏的allocations的長度判斷當前這個連接對象持有的StreamAllocation的數量,是否超過設置的最大值。包括cleanup連接時候也是根據allocations中的引用是否爲空,如果爲空就說明沒有地方引用這個對象了就該刪除它。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章