okhttp源碼筆記

2.okhttp3.0
整體流程:
1).創建okhttpclient客戶端對象,表示所有的http請求的客戶端的類,執行時只會創建一次,作為全局實例保存,只會使用一個單例對象;
2).創建request對象,封裝了請求報文信息,包括了url地址,請求方法,各種請求頭,內部通過build鏈式創建我們的對象;
3).通過request的newcall方法得到一個RealCall,代表我們一個實際的http請求,連接我們request和response的橋梁,有了這個realcall,才能實現我們下面的同步或者異步請求操作
4).決定同步還是異步通過Dispatcher,內部維護了一個線程池,用于執行我們的網絡請求,包括同步和異步;realcall在執行任務把請求添加到dispatcher中,在dispatcher三個隊列來維護我們的同步異步請求
5).不管是同步還是異步,都是通過攔截器練來進行真正服務器數據獲取,構建攔截器鏈通過依次執行攔截器鏈中的每個攔截器來將我們服務器獲取的數據返回
6).RetryAndFollow,負責兩部分邏輯:在網絡請求失敗后的重試;服務器返回當前請求需要重定向的時候會直接發起請求
7).Bridge:設置請求內容長度,內容編碼,gzip壓縮,也可以添加我們的cookie,為他設置其他的報頭等,主要是一些請求前的操作
8).cache:緩存管理,當我們網絡請求有符合要求的請求,直接返回,不需要網絡請求
9).Connect:為當前請求找到一個合適的鏈接;有可能會復用已有的鏈接,涉及到連接池
10).Callserver:向我們服務器發送真正的網絡請求,讀取后并返回,做得就是網絡鏈接

源碼關鍵點:
初始化OkhttpClient,初始化Request,用OkhttpClient.newCall(reqeust)獲取Call,在用call執行同步或者異步方法。
1).OkhttpClient:build模式初始化;Build()方法中初始化了dispatcher,Connectionpool(連接池,客戶端和服務器的鏈接抽象為一個connection,每一個connection都放在Connectionpool中統一管理,當你請求的URL是相同的可以復用;Connectionpool實現了哪些網絡鏈接保持打開狀態,哪些是可以復用的這些策略的設置);包含了newCall(reqeust)方法
2).Request:也是build模式初始化;默認指定請求方式GET。
3).RealCall構造方法中包含client,request;retryAndFollowUp攔截器初始化
4).同步請求:call.execute():內部調用
client.dispatcher.execute(this);-->runningSyncCalls.add(call);
getResponseWithInterceptroChain();
在finally中client.dispatcher().finished(this);-->runningSyncCalls.remove(call);
所以:dispatcher在同步請求中只做了加入隊列跟移除隊列操作
5).異步請求:call.enqueue():內部調用client.dispatcher().enqueue(new AsyncCall())->runningAsyncCalls.add(call)并executorService().execute(call)或者readyAsyncCalls.add(call);

AsyncCall是個Runable,run中執行getResponseWithInterceptroChain();
在finally中client.dispatcher().finished(this)-->runningAyncCalls.remove(call);
promoteCalls()-->runningAsyncCalls.add(call)并executorService().execute(call)

(6).dispatcher:維護請求的狀態,并且維護一個線程池用于執行請求,線程池初始化中coolpoolsize為0;隊列數為Interger.Max;時間為60s,也就是當所有線程空閑60s后會被全清除

(7).攔截器:應用程序攔截器,系統內部攔截器(5個),網絡攔截器。
發送一個http請求時會通過一個攔截鏈去實現;RetryAndFollowUpInterceptor是重試和失敗重定向攔截器,主要做一些初始化工作和初始化streamAllcation對象傳遞給后面的攔截器;BridgeInterceptor(橋接和適配器攔截器)和CacheInterceptor他們兩的職責主要用來補充用戶創建請求當中缺少的請求頭和處理緩存的功能;ConnectInterceptor和CallServerInterceptor是網絡請求中的關鍵,其中CallServerInterceptor主要是負責將我們的http請求寫入網絡的io流當中并且從網絡的io流中讀取服務器返給客戶端的數據;而我們的ConnectInterceptor這個連接攔截器主要用于建立可用的連接,是CallServerInterceptor的基礎。這就是攔截器的大體流程。

攔截器鏈介紹:
RealCall中的getResponseWithInterceptroChain()->List中加入攔截器,并傳入RealInterceptorChain構造函數中,然后調用chain.proceed(request)執行請求->創建新的RealInterceptorChain next,參數為index+1;執行當前攔截器intercepor(next)。
每一個攔截器又會調用傳入的攔截器鏈的chain.proceed()方法;從而達到遍歷攔截器鏈

攔截器總結:
a.在發起請求前對request進行處理
b.調用下一個攔截器,獲取response
c.對response進行處理,返回給上一個攔截器

(8).RetryAndFollowUpInterceptor:
a.創建streamAllocation:用于獲取連接服務端的connection和用于服務端傳輸的輸入輸出流;主要傳到之后的ConnectInterceptor攔截器所用。
b.調用RealInterceptorChain.proceed()進行網絡請求
c.根據異常結果或者相應結果判斷是否需要重連
d.對response進行處理返回給上一個攔截器

在while(true)循環中獲取response,并對response解析,重連,次數不大于20,當大于20次,streamAllocation.release();并拋出異常退出循環

(9).BridgeInterceptor:
a.設置內容長度,編碼方式,gzip等處理請求頭部信息
其中有個Connection:keep-alive
b.調用攔截器鏈chain.proceed()獲取response
c.網絡請求從服務器返回給我們的response轉化為用戶可以使用的response,例如如果支持gzip壓縮,服務器返回的response在這里進行解壓過程

視頻中ppt給的三個步驟:
a.是負責將用戶構建的一個request請求轉化為能夠進行網絡訪問的請求
b.將這個符合網絡請求的Request進行網絡請求
c.講網絡請求回來的響應response轉化為用戶可用的response

(10).CacheInterceptor:
使用:在初始化okhttpclient去配置cache類

Cache類
初始化:this.cache=DiskLruCache.create();
有個內部類internalCache->回調方法get,put,remove,update,都是調用Cache的get,put,remove,update;

put方法:最核心還是通過DiskLruCache算法
CacheRequest put(Response response){
a.獲取response中requestMethod
b.不緩存非“GET”方法
c.創建Entry實例:url,頭部(請求頭部和響應頭部),方法,協議,code,message,請求時間,響應時間,等所有屬性
d.創建DiskLruCache.Editor來寫入,key值為url的md5值,value為entry;調用entry.writeTo(editor)來寫入
e.return new CacheRequestImpl(editor);寫入body
}

get方法:從緩存中讀取我們的惡響應體response
Response get(Request request){
a.獲取url的md5值作為key
b.DiskLruCache.Snapshot snapshot = cache.get(key);
c.如果snapshot不為空,則以snapshot為值新建Entry,通過entry獲取Response
d.再請求 響應是否匹配
}

okhttp內部維護著一個清理線程池,來實現對緩存文件的清理和管理功能

CacheInterceptor:
Response intercept(Chain chain){
1).通過cache.get獲取緩存response,可能為空
/**
CacheStrategy(包含networkRequest和cacheResponse:networkRequest為空表示不走網絡.cacheResponse為空表示不緩存);
factory()方法中有一項如果request中不要求緩存或者是可選擇的get則返回new CacheStrategy(request,null);通過緩存策略能知道是否直接返回還是繼續往下走攔截器鏈
**/
2).CacheStrategy strategy = new CacheStrategy.factory(request,cacheResponse);

3).在需要走攔截器則調用
Response networkResponse = chain.proceed(networkRequest);
如果cacheResponse不為空并且networkResponse.code=304,則繼續從緩存中讀取

4).如果response中含有響應體body,并且緩存策略是可以被緩存,則可以cache.put(response)
}

(11).ConnectInterceptor:
streamAllocation在這里使用;
Response intercept(Chain chain){
/**
HttpCodec主要用來編碼request以及解碼response
/
1).HttpCodec httpCodec = streamAllocation.newStream();
/

RealConnection 進行實際的網絡io操作的
**/
2).RealConnection connection = streamAllocation.connection();
3).return chain.proceed(httpCodec , connection );
}

streamAllocation.newStream()分析:
public HttpCodec newStream(){
RealConnection resultConnection = findHealthyConnection();
HttpCodec resultCodec = resultConnection.newStream();
}

private RealConnection findHealthyConnection(){
看當前connection能否復用
不能復用從連接池中獲取新的connection ,
執行connection.connect();操作
并put到連接池中
}

PPT的總結:
1).弄一個RealConnection(實際的網絡請求)
2).選擇不同的鏈接方式(是否需要隧道連接和原始的socket連接)
3).調用下一個攔截器

connectionPool介紹:在時間限制內復用connection,同時還要對它進行有效的清理回收工作。
不管是http1.0或者http2.0的keep-alive機制或者多路復用機制,在實現上都需要引入一個連接池的概念,來維護我們整個http的網絡連接;okhttp把客戶端跟服務端的鏈接抽象成一個connection類,而realConnection是它的實現類;connectionPool用來管理這些connection,這些連接的復用;當共享相同的地址的時候就可以復用鏈接,同時connectionpool還實現了哪些鏈接可以保持打開狀態以備后面使用的策略。

1).如何對連接池進行簡單的get put操作:
總結:
a.每次一個http請求都會產生一個StreamAllocation對象(在重試重定向攔截器中初始化的)
b.將StreamAllocation對象的弱引用添加到RealConnection對象的allocations集合中(是ArrayList,能判斷集合的大小,根據大小來判斷該鏈接是否超過了最大的連接數)
c.從鏈接池中獲取鏈接,復用

2).如何對connection回收:
利用了我們的GC回收算法,鏈接中StreamAllocation的數量會漸漸變為0,當它變為0的時候會被線程池監測到然后回收,這樣就能保持多個健康的keep-alive連接;在connectionPool中有個獨立的線程會開啟cleanRunnable來清理我們的連接池

(12).CallServerInterceptor:
向服務器發送真正的網絡請求,能讀取服務端給我們的響應;
Response intercept(Chain chain){
httpCodec.writeRequestHeaders(request);
//向socket中寫入我們的body信息
request.body().writeTo(bufferedRequestBody);

//表示我們整個網絡工作的寫入工作已完成
 httpCodec.finishRequest();
 //讀取網絡相應的頭部信息
responseBuilder = httpCodec.readResponseHeaders();
httpCodec.openResponseBody(response);

}

(13).okhttp面試

1).socket:
基于應用層跟傳輸層中抽象出的一層;是一個對TCP/IP協議封裝的API,不是協議,成對出現,一對套接字;TCP采取字節流方式來提供可靠的字節流服務的;UDP采用數據報文方式來提供數據,打包發送服務。

2).websocket
短輪詢:每隔一段時間向服務端發送請求獲取數據;缺點:某個時間段server沒有更新數據,短輪詢都是無效的,浪費資源
長輪詢:向服務端發送請求,如果服務端數據沒有變化會一直保持這個request直到數據更新,返回response。

http:請求響應協議
websocket:與HTTP同等的,基于tcp的雙向通信協議;向服務端發送一個http請求/"Upgrade WebSocket";服務端解析這些附加的頭信息產生response
Socket:不是一個協議,接口
okhttp支持websocket

3).http緩存
強制緩存
Expires:服務端返回的到期時間,http1.0使用,目前作用暫時失效;缺點:Expires是服務器時間,跟客戶端時間有誤差。
Cache-Control:服務器返回的response頭部信息,告訴客戶端是從本地還是服務器獲取消息;有如下五個取值;private(客戶端可以緩存),public(客戶端和代理服務端都可以緩存),max-age(表示我們的緩存內容在多少秒后失效),no-cache(通過對比緩存來驗證我們的數據,主要是表示強勢緩存的標識無法處理我們的緩存),no-store(所有內容都不需要緩存)

對比緩存
首先需要進行比較判斷是否可以使用緩存;服務端會將緩存標識與數據一起返給客戶端。

ETag/If-None-Match
ETag:服務器響應請求時,告訴客戶端當前資源在服務器的唯一標識
If-None-Match:再次請求服務器時,通過此字段通知服務器客戶端緩存數據的唯一標識
示例:客戶端發起請求->有緩存但過期->有Etag->向服務器請求中加If-None-Match->服務器決策->返回200或者304

Last-Modified/If-Modified-Since
Last-Modified:服務器在響應請求時,告訴客戶端資源的最后修改時間。
If-Modified-Since:再次請求服務器時,通過此字段通知服務器上次請求時,服務器返回的資源最后修改時間。
Etag優先級高于Last-Modified。
在Etag不能判斷的前提下,示例:客戶端發起請求->有緩存但過期->有Last-Modified->向服務器請求中加If-Modified-Since->服務器決策->返回200或者304

4).斷點續傳原理
從文件已經下載完的地方繼續下載
在request頭部添加RANGE標識;例如RANGE:bytes=200080-;表示從字節20080開始下載

5).多線程下載
每個線程只負責下載文件的一部分
RandomAccessFile.setLength();//設置跟下載文件一樣大小的文件。
開啟多個線程,設置下載的起始跟結束位置,每個線程建立連接設置range:bytes=起始位置-結束位置;網絡返回數據為206代表部分資源請求成功

6).文件上傳
Content-Type屬性指定請求和響應的HTTP內容類型
boundary:分隔符,用來分隔數據

7)https
https是一種基于SSL/TLS的Http協議
http缺點:所有傳輸內容都是明文
https:所有傳輸的內容都是經過加密
對稱加密:是指加密和解密使用的密鑰是一個key
不對稱加密:加解密不是同一個key,有一個是公開的
對稱加密所使用的密鑰可以通過非對稱加密發送過去
過程:請求https連接;返回證書(公鑰);產生隨機(對稱)密鑰,使用公鑰對對稱密鑰加密,發送加密后的對稱密鑰,之后通過對稱密鑰加密的密文通信

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,836評論 18 139
  • 用OkHttp很久了,也看了很多人寫的源碼分析,在這里結合自己的感悟,記錄一下對OkHttp源碼理解的幾點心得。 ...
    藍灰_q閱讀 4,337評論 4 34
  • 關于okhttp是一款優秀的網絡請求框架,關于它的源碼分析文章有很多,這里分享我在學習過程中讀到的感覺比較好的文章...
    蕉下孤客閱讀 3,615評論 2 38
  • 我喜歡 2017/09/11 我喜歡塵世間所有美好的事物。 大自然是一種美妙而又神奇的事物,它塑造一幅又一副和諧而...
    擷一朵鏡中花閱讀 271評論 0 0
  • 真誠 不知道什么時候開始的,厚黑學開始大行其道,許多人也將之奉為人生哲學 認為只有這樣,才能在這個社會里走的更順暢...
    哇啦啦啦啦啦啦閱讀 178評論 2 1