面試罐頭(一)——okhttp內部總結分析

本文意圖是針對面試敬锐,理清內部流程關系传轰,而非細摳代碼剩盒,匯總他人的文字而來,原地址 youyuge.cn

1.概述

okhttp3總體流程圖:

liucheng

先來回顧一下代碼的使用流程,然后跟著流程一步步來分析:

1.1 創(chuàng)建 OkHttpClient 對象

OkHttpClient client = new OkHttpClient();

其實okHttpClient用的也是builder構建者模式:

public OkHttpClient() {
    this(new Builder());
}

實際上慨蛙,OkHttpClient.Builder 類成員很多辽聊,用來設置連接的各種參數(shù),官方建議使用單例模式構建一個client期贫。 直接new只是使用了默認配置:

public Builder() {
  dispatcher = new Dispatcher();
  protocols = DEFAULT_PROTOCOLS;
  connectionSpecs = DEFAULT_CONNECTION_SPECS;
  proxySelector = ProxySelector.getDefault();
  cookieJar = CookieJar.NO_COOKIES;
  socketFactory = SocketFactory.getDefault();
  hostnameVerifier = OkHostnameVerifier.INSTANCE;
  certificatePinner = CertificatePinner.DEFAULT;
  proxyAuthenticator = Authenticator.NONE;
  authenticator = Authenticator.NONE;
  connectionPool = new ConnectionPool();
  dns = Dns.SYSTEM;
  followSslRedirects = true;
  followRedirects = true;
  retryOnConnectionFailure = true;
  connectTimeout = 10_000;
  readTimeout = 10_000;
  writeTimeout = 10_000;
}

1.2 創(chuàng)建Request請求對象

Request request = new Request.Builder()
  .url(url)
  .build();

1.3 創(chuàng)建Call對象

OkHttpClient 實現(xiàn)了Call.Factory跟匆,負責根據(jù)請求創(chuàng)建新的 Call。

callFactory 負責創(chuàng)建 HTTP 請求通砍,HTTP 請求被抽象為了 okhttp3.Call 類玛臂,它表示一個已經準備好,可以隨時執(zhí)行的 HTTP 請求

Call call = client.newCall(request);

1.4 同步阻塞執(zhí)行請求

Response response = call.execute();

1.5 異步回調執(zhí)行請求

call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
}

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        System.out.println(response.body().string());
}
});

2. 精妙的線程池

2.1 同步請求時

實際上newCall方法會返回一個Realcall對象封孙,而它同步執(zhí)行execute()方法時:

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    try {
        //僅僅標記作用
      client.dispatcher().executed(this);
      //這才是真正的執(zhí)行下一步發(fā)請求迹冤,同步異步都會走到這個方法
      Response result = getResponseWithInterceptorChain(false);
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

雖然dispatcher也摻和了,其實沒什么用虎忌,涉及到 dispatcher 的內容只不過是在內部做了個標記泡徙,表明有個有個任務正在執(zhí)行:

 /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

2.2 異步請求

2.2.1 反向代理模型

服務器會添加Header并自動轉發(fā)請求給后端集群,接著返回數(shù)據(jù)結果給用戶呐籽》嫔祝可以提高服務的負載均衡能力,實現(xiàn)非阻塞狡蝶、高可用庶橱、高并發(fā)連接,避免資源全部放到一臺服務器而帶來的負載贪惹。


fxdl

而在OkHttp中苏章,非常類似于上述場景,它使用Dispatcher作為任務的派發(fā)器奏瞬,線程池對應多臺后置服務器枫绅,用AsyncCall對應Socket請求,用Deque<readyAsyncCalls>對應Nginx的內部緩存硼端。

xcc

具體成員如下

  • maxRequests = 64: 最大并發(fā)請求數(shù)為64
  • maxRequestsPerHost = 5: 每個主機最大請求數(shù)為5
  • Dispatcher: 分發(fā)者并淋,也就是生產者(默認在主線程)
  • AsyncCall: 隊列中需要處理的Runnable(包裝了異步回調接口)
  • ExecutorService:消費者池(也就是線程池)
  • Deque<readyAsyncCalls>:緩存(用數(shù)組實現(xiàn),可自動擴容珍昨,無大小限制)
  • Deque<runningAsyncCalls>:正在運行的任務县耽,僅僅是用來引用正在運行的任務以判斷并發(fā)量句喷,注意它并不是消費者緩存

通過將請求任務分發(fā)給多個線程,可以顯著的減少I/O等待時間兔毙。

2.2.2 異步執(zhí)行過程

具體過程如下:

  1. enqueue()方法調用后唾琼,判斷runningAsyncCalls隊列是否還有空位子。
  2. 若有空位澎剥,那就調用executorService().execute(call);交給線程池執(zhí)行請求锡溯,并且添加進運行隊列
  3. 啊,沒空位了哑姚,只能進readyAsyncCalls等待隊列了……

看一下call任務內部的執(zhí)行方法:

…………
//果然和同步方式一樣祭饭,最后通過這個方法去發(fā)送給下一步。
Response response = getResponseWithInterceptorChain(forWebSocket);

…………
finally {
  //最關鍵的代碼
client.dispatcher().finished(this);
}   

當任務執(zhí)行完成后蜻懦,無論是否有異常甜癞,finally代碼段總會被執(zhí)行,也就是會調用Dispatcher的finished函數(shù)宛乃,打開源碼悠咱,發(fā)現(xiàn)它將正在運行的任務Call從隊列runningAsyncCalls中移除后,接著執(zhí)行promoteCalls()函數(shù)

if (runningCallsForHost(call) < maxRequestsPerHost) {
        //將緩存等待區(qū)最后一個移動到運行區(qū)中征炼,并執(zhí)行
      i.remove();
      runningAsyncCalls.add(call);
      executorService().execute(call);
   }

實際上就是會在任務執(zhí)行完的最后析既,把自己從運行隊列移除,喚醒一個等待隊列的任務谆奥,讓他去執(zhí)行眼坏。
這樣,就主動的把緩存隊列向前走了一步酸些,而沒有使用互斥鎖等復雜編碼宰译。

2.2.3 異步總結

  • OkHttp采用Dispatcher技術,反向代理魄懂,優(yōu)化了單生產者多消費者模式沿侈,與線程池配合實現(xiàn)了高并發(fā),低阻塞的運行
  • Okhttp采用Deque作為緩存市栗,按照入隊的順序先進先出
  • OkHttp最出彩的地方就是在try/finally中調用了finished函數(shù)缀拭,可以在任務結束時候喚醒等待的任務,主動控制等待隊列的移動填帽,而不是采用鎖或者wait/notify蛛淋,極大減少了編碼復雜性

3. 工作的核心----攔截器

3.1 攔截器的介紹與種類

不管同步異步發(fā)送任務請求,最后都會執(zhí)行getResponseWithInterceptorChain:

private Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> 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(
      retryAndFollowUpInterceptor.isForWebSocket()));

  Interceptor.Chain chain = new RealInterceptorChain(
      interceptors, null, null, null, 0, originalRequest);
  return chain.proceed(originalRequest);
}

攔截器是個啥篡腌?官方介紹和圖片:

the whole thing is just a stack of built-in interceptors.


ljq2

可見 Interceptor 是 OkHttp 最核心的一個東西褐荷,不要誤以為它只負責攔截請求進行一些額外的處理(例如 cookie),實際上它把實際的網絡請求嘹悼、緩存诚卸、透明壓縮等功能都統(tǒng)一了起來葵第,每一個功能都只是一個 Interceptor绘迁,它們再連接成一個 Interceptor.Chain合溺,環(huán)環(huán)相扣,最終圓滿完成一次網絡請求缀台。

getResponseWithInterceptorChain 函數(shù)我們可以看到棠赛,Interceptor.Chain 的分布依次是:

ljq

  1. 在配置 OkHttpClient 時設置的 interceptors
  2. 負責失敗重試以及重定向的 RetryAndFollowUpInterceptor膛腐;
  3. 負責把用戶構造的請求轉換為發(fā)送到服務器的請求睛约、把服務器返回的響應轉換為用戶友好的響應的 BridgeInterceptor
  4. 負責讀取緩存直接返回哲身、更新緩存的 CacheInterceptor辩涝;
  5. 負責和服務器建立連接的 ConnectInterceptor
  6. 配置 OkHttpClient 時設置的 networkInterceptors勘天;
  7. 負責向服務器發(fā)送請求數(shù)據(jù)怔揩、從服務器讀取響應數(shù)據(jù)的CallServerInterceptor

3.2 攔截器的責任鏈模式

實際上脯丝,是責任鏈模式的最佳應用(如同事件分發(fā)機制)商膊,每個攔截器可以自己攔截處理,或者交給下一個攔截器介杆,讓每個 Interceptor 自行決定能否完成任務以及怎么完成任務捞慌。

其實 Interceptor 的設計也是一種分層的思想胸哥,每個 Interceptor 就是一層。為什么要套這么多層呢实幕?分層的思想在 TCP/IP 協(xié)議中就體現(xiàn)得淋漓盡致,分層簡化了每一層的邏輯堤器,每層只需要關注自己的責任(單一原則思想也在此體現(xiàn))昆庇,而各層之間通過約定的接口/協(xié)議進行合作(面向接口編程思想),共同完成復雜的任務吼旧。

4 總結

  1. OkHttpClient 實現(xiàn) Call.Factory凰锡,負責為 Request 創(chuàng)建 Call;
  2. RealCall 為具體的 Call 實現(xiàn)圈暗,其 enqueue() 異步接口通過 Dispatcher 利用 ExecutorService 實現(xiàn)掂为,而最終進行網絡請求時和同步 execute() 接口一致,都是通過 getResponseWithInterceptorChain() 函數(shù)實現(xiàn)员串;
  3. getResponseWithInterceptorChain() 中利用 Interceptor 鏈條勇哗,分層實現(xiàn)緩存、透明壓縮寸齐、網絡 IO 等功能欲诺;

引用

OkHttp3源碼分析
拆輪子系列:拆 OkHttp

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末抄谐,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子扰法,更是在濱河造成了極大的恐慌蛹含,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件塞颁,死亡現(xiàn)場離奇詭異浦箱,居然都是意外死亡,警方通過查閱死者的電腦和手機祠锣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門酷窥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人伴网,你說我怎么就攤上這事蓬推。” “怎么了澡腾?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵沸伏,是天一觀的道長。 經常有香客問我蛋铆,道長馋评,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任刺啦,我火速辦了婚禮留特,結果婚禮上,老公的妹妹穿的比我還像新娘玛瘸。我一直安慰自己蜕青,他們只是感情好,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布糊渊。 她就那樣靜靜地躺著右核,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渺绒。 梳的紋絲不亂的頭發(fā)上贺喝,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機與錄音宗兼,去河邊找鬼躏鱼。 笑死,一個胖子當著我的面吹牛殷绍,可吹牛的內容都是我干的染苛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼主到,長吁一口氣:“原來是場噩夢啊……” “哼茶行!你這毒婦竟也來了躯概?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤畔师,失蹤者是張志新(化名)和其女友劉穎娶靡,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茉唉,經...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡固蛾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了度陆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡献幔,死狀恐怖懂傀,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情蜡感,我是刑警寧澤蹬蚁,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站郑兴,受9級特大地震影響犀斋,放射性物質發(fā)生泄漏。R本人自食惡果不足惜情连,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一叽粹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧却舀,春花似錦虫几、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至螃诅,卻和暖如春啡氢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背术裸。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工倘是, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人穗椅。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓辨绊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親匹表。 傳聞我的和親對象是個殘疾皇子门坷,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內容