前言
節(jié)前想著豐富下假期购对,在慕**上花了幾百大洋買了個(gè)課程啡浊,結(jié)果三觀都掉沒(méi)了觅够,雖然現(xiàn)在知識(shí)付費(fèi)了,但這tama也叫高級(jí)課程巷嚣!為了省錢喘先,最直接的辦法就是自己干,當(dāng)然我是指學(xué)習(xí)方面廷粒。
一般而言懂得如何使用窘拯,此人可以認(rèn)定為初級(jí)红且;懂得內(nèi)部邏輯,熟悉流程間銜接涤姊,能夠在框架提供的框框內(nèi)玩耍API暇番,世俗認(rèn)定為此人中高級(jí)、已經(jīng)很不錯(cuò)了思喊;但我認(rèn)為還不夠壁酬。
本著不懂就查,不懂就深追的精神:蘅巍舆乔!此文不同于其他講解(介紹下如何使用、基本的源碼流程剂公、責(zé)任鏈和幾大攔截器希俩,這也是現(xiàn)狀),我劍走偏鋒纲辽,不是流程上的講解颜武,而是深入研讀每一步的源碼了解作者為何及意圖,這也意味著你必需要知道OkHttp淺層的知識(shí)文兑,否則看起來(lái)會(huì)很累盒刚,文章采用CPU響應(yīng)中斷保護(hù)現(xiàn)場(chǎng)的模式書寫,以免思路混亂绿贞,同時(shí)文章也會(huì)異常的長(zhǎng)長(zhǎng)長(zhǎng)因块。。籍铁。涡上。
雖然與現(xiàn)在大環(huán)境相逆,但我深信:知其然拒名,要知其所以然吩愧,這才是真正的工程師,而不是碼農(nóng)增显。
干貨總結(jié)
由于文章太長(zhǎng)雁佳、內(nèi)容太多,相信大多數(shù)人不可能全部讀完同云,因此總結(jié)放在前面糖权,希望能夠提起興趣讀完全文。
- 接口適配器模式
- StrictMode之CloseGuard
-
雙端隊(duì)列的概念及環(huán)形隊(duì)列的操作
/* 4XX: client error */
/**
* HTTP Status-Code 400: Bad Request.
*/
public static final int HTTP_BAD_REQUEST = 400;
/**
* HTTP Status-Code 401: Unauthorized.
*/
public static final int HTTP_UNAUTHORIZED = 401;
/**
* HTTP Status-Code 402: Payment Required.
*/
public static final int HTTP_PAYMENT_REQUIRED = 402;
/**
* HTTP Status-Code 403: Forbidden.
*/
public static final int HTTP_FORBIDDEN = 403;
/**
* HTTP Status-Code 404: Not Found.
*/
public static final int HTTP_NOT_FOUND = 404;
/**
* HTTP Status-Code 405: Method Not Allowed.
*/
public static final int HTTP_BAD_METHOD = 405;
/**
* HTTP Status-Code 406: Not Acceptable.
*/
public static final int HTTP_NOT_ACCEPTABLE = 406;
/**
* HTTP Status-Code 407: Proxy Authentication Required.
*/
public static final int HTTP_PROXY_AUTH = 407;
/**
* HTTP Status-Code 408: Request Time-Out.
*/
public static final int HTTP_CLIENT_TIMEOUT = 408;
/**
* HTTP Status-Code 409: Conflict.
*/
public static final int HTTP_CONFLICT = 409;
/**
* HTTP Status-Code 410: Gone.
*/
public static final int HTTP_GONE = 410;
/**
* HTTP Status-Code 411: Length Required.
*/
public static final int HTTP_LENGTH_REQUIRED = 411;
/**
* HTTP Status-Code 412: Precondition Failed.
*/
public static final int HTTP_PRECON_FAILED = 412;
/**
* HTTP Status-Code 413: Request Entity Too Large.
*/
public static final int HTTP_ENTITY_TOO_LARGE = 413;
/**
* HTTP Status-Code 414: Request-URI Too Large.
*/
public static final int HTTP_REQ_TOO_LONG = 414;
/**
* HTTP Status-Code 415: Unsupported Media Type.
*/
public static final int HTTP_UNSUPPORTED_TYPE = 415;
正文
以下內(nèi)容基于OkHttp3.11.0版本
OkHttp的所有操作炸站,都在當(dāng)前線程星澳,包括結(jié)果回調(diào),這也意味著使用時(shí)必須為其開(kāi)單獨(dú)的子線程旱易,否則OkHttp內(nèi)部檢查線程時(shí)就會(huì)報(bào)錯(cuò)禁偎。
OkHttpClient通過(guò)構(gòu)建者模式創(chuàng)建實(shí)例腿堤,初始化參數(shù),項(xiàng)目中可以使用單例如暖,如果是多后臺(tái)項(xiàng)目需要清楚內(nèi)部參數(shù)設(shè)置笆檀,看完全文后會(huì)清楚原因。
Request同理創(chuàng)建實(shí)例盒至,初始化參數(shù)误债。并作為參數(shù)初始化接口Call
這里可見(jiàn),Call的實(shí)現(xiàn)類為RealCall妄迁,且持有OkHttpClient實(shí)例對(duì)象。
經(jīng)由靜態(tài)方法李命,進(jìn)入構(gòu)造方法給變量賦值登淘。
分支開(kāi)始EventListener,保存RealCall位置
為什么非要經(jīng)過(guò)靜態(tài)方法呢封字?給每一個(gè)Call對(duì)象的eventListener賦值為
經(jīng)okHttpClient實(shí)例對(duì)象eventListenerFactory創(chuàng)建的EventListener黔州。
EventListener.Factory來(lái)自Builder
如果Builder沒(méi)有主動(dòng)設(shè)置過(guò),則默認(rèn)生成阔籽。
EventListener.Factory接口根據(jù)傳入的Call生成EventListener
EventListener是什么有什么用流妻?
通過(guò)類結(jié)構(gòu),可以看出EventListener是一個(gè)抽象類笆制,采用適配器模式(若定義為接口绅这,則必須實(shí)現(xiàn)全部方法,又稱接口適配器模式)默認(rèn)每個(gè)方法都是空實(shí)現(xiàn)在辆,繼承者根據(jù)需要實(shí)現(xiàn)相應(yīng)方法证薇。方法名也很好理解,結(jié)果表明:這就是是請(qǐng)求生命周期的回調(diào)匆篓,在請(qǐng)求的不同時(shí)期會(huì)回調(diào)相應(yīng)的方法浑度。
那Call中的EventListener對(duì)象是哪里實(shí)現(xiàn)的?
一步步鸦概,通過(guò)Builder初始化默認(rèn)eventListenerFactory箩张,再傳遞到okHttpClient的eventListenerFactory,最終由eventListenerFactory創(chuàng)建EventListener窗市。
哪里跋瓤丁?沒(méi)看見(jiàn)谨设!這里確實(shí)繞了幾個(gè)彎熟掂,第三、四張圖是關(guān)鍵扎拣。
第四張圖靜態(tài)方法生成EventListenerFactory赴肚,但非常簡(jiǎn)單素跺,傳入的參數(shù)是EventListener,什么都沒(méi)做就返回出去了誉券,表明實(shí)際的eventListener對(duì)象是外部初始化后傳入的指厌。
回來(lái)看第三張圖,傳入的eventListener對(duì)象是什么踊跟?EventListener.NONE踩验,又回去
eventListener對(duì)象為默認(rèn)的EventListener類內(nèi)部靜態(tài)變量,且各個(gè)生命周期的回調(diào)并沒(méi)有重寫商玫,意味著生命周期回調(diào)還是空實(shí)現(xiàn)箕憾。
這里就需要我們手動(dòng)繼承EventListener實(shí)現(xiàn)自己的生命周期監(jiān)聽(tīng)類,并實(shí)現(xiàn)
EventListener.Factory接口拳昌,重寫create返回自己的生命周期監(jiān)聽(tīng)類袭异,賦值給OkHttpClient.Builder
這樣下來(lái),經(jīng)由此okHttpClient對(duì)象的Call炬藤,在相應(yīng)的生命周期就會(huì)回調(diào)到自己的生命周期監(jiān)聽(tīng)類內(nèi)御铃。
雖然代碼繞來(lái)繞去,但更能夠理解作者的意圖沈矿,每個(gè)okHttpClient對(duì)象內(nèi)不直接賦值或生成EventListener上真,而是存儲(chǔ)eventListenerFactory工廠,讓具體的EventListener由具體的eventListenerFactory生成羹膳,降低了okHttpClient對(duì)象與eventListener對(duì)象的耦合睡互。
分支結(jié)束EventListener,讀取RealCall位置
RealCall的構(gòu)造方法內(nèi)還賦值了retryAndFollowUpInterceptor變量溜徙,這是攔截器的起點(diǎn)(下面講)湃缎。
Call對(duì)象初始化完成,其實(shí)就是基本的變量初始化蠢壹。
執(zhí)行execute方法嗓违,在其實(shí)現(xiàn)類RealCall內(nèi)
每個(gè)Call對(duì)象只能被執(zhí)行一次,有變量executed來(lái)記錄自己是否被執(zhí)行過(guò)图贸,這里加鎖為了防止多線程間線程安全蹂季,因?yàn)槊總€(gè)請(qǐng)求都會(huì)放到池子pool內(nèi)(后面講)。
分支開(kāi)始StackTrace疏日,保存RealCall位置
execute內(nèi)執(zhí)行captureCallStackTrace偿洁,譯為"開(kāi)啟Call的棧跟蹤"。
Platform類如其意"平臺(tái)"沟优,注意有Android的Logger打印日志類變量涕滋,返回的PLATFORM來(lái)自findPlatform。
注釋也寫了挠阁,通過(guò)運(yùn)行環(huán)境匹配合適的Platform宾肺,這里列舉了幾個(gè)平臺(tái)溯饵,除了android其他的都不認(rèn)識(shí)。
如果運(yùn)行環(huán)境平臺(tái)都不符合锨用,則new Platform();
方法getStackTraceForCloseable返回的是Throwable
若符合android平臺(tái)丰刊,通過(guò)反射獲取一些證書、Session等網(wǎng)絡(luò)相關(guān)的類增拥。當(dāng)Platform支持此功能時(shí)啄巧,便會(huì)反射invoke相關(guān)功能。
返回AndroidPlatform掌栅,其繼承自Platform
android平臺(tái)下getStackTraceForCloseable被重寫
這里有個(gè)內(nèi)部類CloseGuard秩仆,其實(shí)這個(gè)類在android源碼dalvik.system.CloseGuard包下,注釋中也有提到猾封,這里只是利用反射逗概,封裝了原CloseGuard的方法。
分支開(kāi)始CloseGuard忘衍,保存StackTrace位置
先看看源碼中的CloseGuard類
想多學(xué)習(xí)的也可以看看 http://duanqz.github.io/2015-11-04-StrictMode-Analysis#All
源碼取自android7.0路徑看圖
注釋中解釋了此類的作用及如何使用,我再?gòu)?fù)述一遍卿城。
CloseGuard類用來(lái)記錄對(duì)象是否被關(guān)閉枚钓,某些情況下如果對(duì)象不關(guān)閉的話就會(huì)造成內(nèi)存泄露或其他問(wèn)題,由于CloseGuard類是源碼級(jí)別的類瑟押,很多源碼中判斷對(duì)象是否關(guān)閉都有用到它如InputStream搀捷,可以看看引用的文章。
CloseGuard類使用也很簡(jiǎn)單多望,如例子嫩舟,在使用對(duì)象Foo類中增加變量guard,實(shí)例化Foo對(duì)象時(shí)調(diào)用guard的open方法怀偷,表示對(duì)象開(kāi)啟在被使用家厌,對(duì)象關(guān)閉調(diào)用cleanup,此時(shí)guard調(diào)用close椎工,表明對(duì)象關(guān)閉不再使用饭于。此時(shí)JVM便會(huì)GC此對(duì)象調(diào)用其finalize方法,如果對(duì)象不被使用且沒(méi)有關(guān)閉調(diào)用cleanup(即內(nèi)存泄露)维蒙,GC時(shí)便會(huì)發(fā)現(xiàn)guard掰吕,并警告調(diào)用warnIfOpen。
整體思路就是這樣颅痊,看看源碼的實(shí)現(xiàn)殖熟。
注釋寫的很好,很明白斑响。
CloseGuard類為final菱属,在JVM方法區(qū)運(yùn)行時(shí)常量池中存在NOOP對(duì)象钳榨,當(dāng)CloseGuard功能不使用時(shí),可避免浪費(fèi)內(nèi)存生成對(duì)象照皆。
CloseGuard功能默認(rèn)是打開(kāi)的重绷,android在啟動(dòng)的時(shí)候關(guān)閉了此功能,注釋中也有提到膜毁。
何為關(guān)閉昭卓?功能開(kāi)啟情況下一個(gè)對(duì)象對(duì)應(yīng)一個(gè)CloseGuard對(duì)象,關(guān)閉情況下瘟滨,所有對(duì)象對(duì)應(yīng)一個(gè)CloseGuard對(duì)象NOOP候醒,即起不到監(jiān)控對(duì)象是開(kāi)啟還是關(guān)閉的狀態(tài)。
get方法直接返回自己或?qū)嵗约?/p>
open方法則初始化了Throwable的allocationSite對(duì)象杂瘸,針對(duì)不同的對(duì)象傳入不同的closer信息倒淫,可以加速問(wèn)題的排查
close方法置空了allocationSite對(duì)象
warnIfOpen方法在發(fā)現(xiàn)功能開(kāi)啟且對(duì)象未關(guān)閉時(shí),便會(huì)報(bào)異常信息
而報(bào)異常信息的對(duì)象是系統(tǒng)級(jí)別
分支結(jié)束CloseGuard败玉,讀取StackTrace位置
CloseGuard通過(guò)反射取得源類的Method作為自己的變量敌土,對(duì)象調(diào)用方法時(shí),再利用反射invoke源類中相應(yīng)的方法运翼,達(dá)到對(duì)應(yīng)的功能
getStackTraceForCloseable方法調(diào)用返回的是源CloseGuard對(duì)象
源CloseGuard對(duì)象
又被賦值給責(zé)任鏈的第一環(huán)RetryAndFollowUpInterceptor攔截器中返干,此時(shí)的對(duì)象關(guān)系為:RealCall對(duì)象持有RetryAndFollowUpInterceptor對(duì)象,RetryAndFollowUpInterceptor對(duì)象持有CloseGuard對(duì)象血淌。簡(jiǎn)言之:一個(gè)Call對(duì)象對(duì)應(yīng)一個(gè)CloseGuard對(duì)象矩欠,當(dāng)Call對(duì)象沒(méi)有被close時(shí)便會(huì)報(bào)異常。
那這個(gè)CloseGuard對(duì)象何時(shí)被close呢悠夯?答案是沒(méi)有癌淮,因?yàn)橛谐刈拥年P(guān)系所有的Call都在池子內(nèi),當(dāng)Call真正被回收清除的時(shí)候就會(huì)通過(guò)CloseGuard對(duì)象log下沦补。
CloseGuard對(duì)象賦值給RetryAndFollowUpInterceptor對(duì)象
RetryAndFollowUpInterceptor對(duì)象轉(zhuǎn)手有將它賦值給StreamAllocation對(duì)象(后面講)
StreamAllocation對(duì)象轉(zhuǎn)手有將它賦值給StreamAllocationReference它自己的弱引用
當(dāng)池子需要清理了
便會(huì)取出之前的CloseGuard對(duì)象
log一下通知此連接已關(guān)閉乳蓄,但并非真關(guān)閉,只是此次的連接請(qǐng)求關(guān)閉了夕膀,連接本身還留著執(zhí)行下次的請(qǐng)求栓袖,這是pool的功能。
如果CloseGuard對(duì)象沒(méi)有成功log店诗,便會(huì)調(diào)用android中的Log類裹刮。
到此就是整個(gè)Call連接的路徑追蹤,從創(chuàng)建Call到連接流關(guān)閉庞瘸,Platformge根據(jù)平臺(tái)的不同捧弃,以不同的方式打印追蹤C(jī)all連接流的關(guān)閉事件。
分支結(jié)束StackTrace,讀取RealCall位置
eventListener生命周期回調(diào)callStart
緊接著httpClient對(duì)象調(diào)用dispatcher執(zhí)行call對(duì)象
dispatcher對(duì)象在httpClient通過(guò)builder創(chuàng)建時(shí)默認(rèn)生成
Dispatcher為異步請(qǐng)求執(zhí)行策略违霞,且為final類不允許繼承
有可能官方后面會(huì)有擴(kuò)充嘴办,通過(guò)開(kāi)放繼承或?qū)崿F(xiàn)接口以用來(lái)個(gè)性化定制。目前版本买鸽,此類主要針對(duì)異步請(qǐng)求涧郊,對(duì)同步請(qǐng)求沒(méi)影響僅做計(jì)數(shù)功能
Dispatcher中幾個(gè)重要的變量,從上至下依次(其實(shí)變量名起的很明白)
- 最大請(qǐng)求數(shù)
- 每臺(tái)主機(jī)最大請(qǐng)求數(shù)
- Dispatcher空閑時(shí)的回調(diào)
- 線程池
- 異步等待隊(duì)列
- 異步請(qǐng)求隊(duì)列
- 同步請(qǐng)求隊(duì)列
分支開(kāi)始Deque眼五,保存RealCall位置
什么是Deque妆艘?與Queue有什么關(guān)系?Queue又是什么看幼?
Queue是數(shù)據(jù)結(jié)構(gòu)中的隊(duì)列批旺,Queue提供了隊(duì)列的基本操作,不同的實(shí)現(xiàn)類有具體的實(shí)現(xiàn)
通過(guò)類圖看出诵姜,Deque接口繼承自Queue汽煮,Dispatcher中的對(duì)象為ArrayDeque,它實(shí)現(xiàn)了Deque接口并繼承自AbstractCollection棚唆。
那不是實(shí)現(xiàn)了兩次Collection接口嗎暇赤?
并不是,首先看一下實(shí)現(xiàn)與繼承的區(qū)別:
java是單繼承多實(shí)現(xiàn)宵凌,接口表示具體功能翎卓,實(shí)現(xiàn)接口意味著類具有某功能。繼承為子承父業(yè)摆寄,父類具有的功能,子類同樣具有坯门。實(shí)現(xiàn)為具有某功能微饥,繼承為具有某功能的實(shí)現(xiàn)。
AbstractCollection為抽象類古戴,實(shí)現(xiàn)了Collection聲明的方法欠橘,又將功能合并生成抽象方法;ArrayDeque實(shí)現(xiàn)了Collection接口现恼,表示ArrayDeque類具有Collection接口聲明的功能肃续,功能的具體實(shí)現(xiàn)在父類AbstractCollection中,ArrayDeque只需實(shí)現(xiàn)AbstractCollection類合并功能后的抽象方法叉袍。
簡(jiǎn)言之:ArrayDeque實(shí)現(xiàn)了Collection接口始锚,但具體的實(shí)現(xiàn)在AbstractCollection中。這種設(shè)計(jì)思路值得學(xué)習(xí)
Queue為隊(duì)列提供了隊(duì)尾插入add喳逛、offer功能瞧捌,隊(duì)首移除remove、poll功能,還有偷窺隊(duì)首元素element姐呐、peek功能殿怜,屬于常規(guī)的隊(duì)列操作接口
Deque繼承自Queue,但其增加了隊(duì)列首尾的功能曙砂,使隊(duì)列成為雙端隊(duì)列头谜,即首尾都可插入、移除鸠澈。其還有棧功能柱告,但不在此次介紹范圍內(nèi)。
ArrayDeque為雙端隊(duì)列實(shí)現(xiàn)的其中一種款侵,簡(jiǎn)單看下源碼:
與大部分集合工具類一樣末荐,具體的數(shù)據(jù)存儲(chǔ)在數(shù)組中,可以看到transient關(guān)鍵字新锈,表明不能夠被序列化甲脏,但其內(nèi)部有writeObject、readObject自己的序列化方式妹笆。
ArrayDeque是一種環(huán)形隊(duì)列(源碼中的head块请、tail指針與圖中的算法不同,此圖僅做參考拳缠,ArrayDeque中tail指針指null)
通過(guò)head墩新、tail指針完成整個(gè)隊(duì)列的操作
初始化存儲(chǔ)空間,numElements二進(jìn)制每位或1操作窟坐,使initialCapacity為奇數(shù)海渊,再自加為偶數(shù),保證initialCapacity一定為2的n次方(英文翻譯)哲鸳,正確應(yīng)是:保證initialCapacity一定為偶數(shù)臣疑,只有這樣在后面的求mask時(shí)二進(jìn)制的各位才能都被與操作到。
如果initialCapacity超出最大長(zhǎng)度則分配2^30 elements
雙端隊(duì)列插入操作
頭部指針指向位置插入元素
紅框標(biāo)示的位置及為什么ArrayDeque的容量必須為偶數(shù)徙菠,只有偶數(shù)減1讯沈,必為奇數(shù),而奇數(shù)二進(jìn)制位的最后一位必定位1婿奔,這樣與操作才不會(huì)漏位缺狠。
offer的內(nèi)部邏輯與add是一致的,只不過(guò)offer成功會(huì)return true萍摊,不成功就NullPointerException
接著就是擴(kuò)容邏輯
斷言判斷隊(duì)列是否已滿挤茄,保存臨時(shí)變量記錄位置,容量擴(kuò)為原來(lái)的2倍冰木,
將隊(duì)列中head指針右側(cè)的數(shù)組蝇闭,拷貝到新數(shù)組的左端,緊接著拷貝剩余位置起點(diǎn)到tail指針的數(shù)組缴守,最終新數(shù)組左側(cè)填滿,右側(cè)空挖腰。
poll操作不會(huì)拋出異常,而remove不同,會(huì)有NoSuchElementException
為什么說(shuō)tail指null呢练湿?看pollFirst直接取head指針取數(shù)據(jù)猴仑,而pollLast是先計(jì)算tail指針再去取
注意get操作,并沒(méi)有刪除數(shù)據(jù)肥哎,但會(huì)NoSuchElementException
反倒peek不會(huì)
移除第一個(gè)匹配項(xiàng)辽俗,分為從head循環(huán)和tail循環(huán),首次遇到equal項(xiàng)即刪除
所有的數(shù)組集合工具類一樣篡诽,刪除意味著移動(dòng)
check了隊(duì)列的正確性后崖飘,移動(dòng)數(shù)組,分了兩種情況:
- head在tail前
- head在tail后
隊(duì)列操作的實(shí)質(zhì)
驗(yàn)證了我之前對(duì)head和tail的解析
分支結(jié)束Deque杈女,讀取RealCall位置
Dispatcher使用隊(duì)列朱浴,正式因?yàn)殛?duì)列的FIFO性質(zhì),符合網(wǎng)絡(luò)請(qǐng)求的場(chǎng)景
dispatcher對(duì)象將call對(duì)象放入同步請(qǐng)求隊(duì)列runningSyncCalls即結(jié)束
然后在請(qǐng)求返回后达椰,調(diào)用dispatcher對(duì)象將call結(jié)束
簡(jiǎn)單的操作翰蠢,將call從隊(duì)列中移除,由于是同步操作所以沒(méi)有觸發(fā)promoteCalls啰劲,并重新計(jì)算此dispatcher(即okHttpclient)中正在運(yùn)行的call總數(shù)
也很easy就是求和梁沧,如果沒(méi)有正在執(zhí)行的請(qǐng)求就執(zhí)行Dispatcher空閑時(shí)的回調(diào)。
現(xiàn)在可能對(duì)Dispatcher的作用產(chǎn)生疑問(wèn)蝇裤,它到底有什么用廷支?提前劇透,OkHttp中有池子的概念即復(fù)用栓辜,這會(huì)導(dǎo)致不同的請(qǐng)求使用同一個(gè)流恋拍,如何正確的識(shí)別請(qǐng)求而對(duì)外屏蔽池子,即是Dispatcher的功能啃憎。我們只管向Dispatcher中加請(qǐng)求,完成了就移除似炎,判斷請(qǐng)求存在與否時(shí)直接使用Dispatcher辛萍,即使請(qǐng)求被復(fù)用到了其他請(qǐng)求,Dispatcher也會(huì)將之前的請(qǐng)求移除羡藐,認(rèn)為是不同的操作贩毕。
除了以上Dispatcher還做了請(qǐng)求量的控制和一些基本Call對(duì)象操作,接下來(lái)異步請(qǐng)求時(shí)會(huì)涉及
Dispatcher的在同步請(qǐng)求時(shí)沒(méi)做多少事情仆嗦,到此就休息了辉阶。那請(qǐng)求的結(jié)果是如何得到的?