ok?->okhttp

首先声功,閱讀完本文希望你能回答:

ok(指的okhttp)它是怎么發(fā)起(創(chuàng)建和建立)http請求的呢?

ok它是怎么創(chuàng)建call的呢宠叼?

ok里面HttpCodec對象是啥先巴?

ok它是怎么進(jìn)行和服務(wù)器實際通信的?

ok它是怎么樣實現(xiàn)同步網(wǎng)絡(luò)和一部網(wǎng)絡(luò)請求的冒冬?代碼的結(jié)構(gòu)和設(shè)計模式方面你得到了什么么伸蚯?

廢話不多說,ok它提供okhttpclient()

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

方便我們使用简烤,提供快捷操作剂邮,全部使用默認(rèn)配置。OkHttpClient.Builder類成員很多横侦,這里略過:

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;
}

它是怎么發(fā)起http請求的呢挥萌?(涉及到創(chuàng)建和建立)

看里面的run()方法

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}

okhttpclient 實現(xiàn)了call.factory,負(fù)責(zé)根據(jù)請求創(chuàng)建新的call枉侧。
callFactory 負(fù)責(zé)創(chuàng)建 HTTP 請求引瀑,HTTP 請求被抽象為了 okhttp3.Call 類,它表示一個已經(jīng)準(zhǔn)備好榨馁,可以隨時執(zhí)行的 HTTP 請求

它是怎么創(chuàng)建call的呢憨栽?


/\*\* \* Prepares the {@code request} to be executed at some point in the future. \*/
 @Override public Call newCall(Request request) {
                  return new RealCall(this, request); 
}

A.發(fā)現(xiàn)跟源碼,出現(xiàn)了realcall這個類翼虫。接下來了解下它的同時屑柔,分析下同步網(wǎng)絡(luò)請求的過程

realcall里面的 excute方法:

@Override public Response execute() throws IOException { 
synchronized (this) { 
if (executed) throw new IllegalStateException("Already Executed"); // (1) 
executed = true; } try { client.dispatcher().executed(this); // (2) 
Response result = getResponseWithInterceptor();//(3) 
if (result == null) throw new IOException("Canceled"); return result; }
 finally {client.dispatcher().finished(this);// (4) } }

相信大家也看到了1,2蛙讥,3锯蛀,4了不賣關(guān)子灭衷,這里做的4件事情是在干什么呢次慢?

1.檢查:查call是否已經(jīng)執(zhí)行,每個call只能被執(zhí)行一次,如果想要一個一摸一樣的call迫像,也提供了clone的方法劈愚。

2.執(zhí)行:dispatcher是 上面說的OkHttpClient.Builder成員之一。

看源碼的時候這個類說自己是異步http請求的執(zhí)行策略闻妓,現(xiàn)在再看菌羽,同步tm也摻合進(jìn)來了

3.結(jié)果:調(diào)用這個函數(shù)獲取htto的返回結(jié)果,函數(shù)名稱上來看由缆,這一步還會進(jìn)行一系列的“攔截”操作(getResponseWithInterceptorChain())

4.over:通知dispatcher自己執(zhí)行完成

說了上面四點注祖,又發(fā)現(xiàn)什么么?

 Policy on when async requests are executed. Each dispatcher uses an {@link ExecutorService} 
to run calls internally. If you supply your  own executor,
 it should be able to run {@linkplain getMaxRequests the configured maximum} 
number  of calls concurrently. 

dispatcher注釋(ok的任務(wù)隊列的管理與調(diào)度其實就是Dispatcher一個類來完成均唉,雖然我們只是針對異步任務(wù)來講解是晨,但是它也負(fù)責(zé)同步任務(wù)的維護(hù),如 executed()方法的調(diào)用標(biāo)識任務(wù)的開始舔箭,finished()方法的調(diào)用標(biāo)識任務(wù)結(jié)束罩缴,具體代碼可以自行查閱。在任務(wù)被調(diào)度執(zhí)行以后层扶,任務(wù)就需要去執(zhí)行了箫章,也就是請求流程的執(zhí)行過程。本篇文章的最后一部分對此做介紹镜会,這一部分也是很多文章都會重點介紹的interceptor的調(diào)用流程檬寂,在okhttp中所有的功能幾乎都是通過定義interceptor, 對request和response做操作來實現(xiàn)的稚叹,其實就也就是請求流程的執(zhí)行過程焰薄。)

真正發(fā)出網(wǎng)絡(luò)請求的,解析返回結(jié)果的還是getResponseWithInterceptorChain:

先看圖:


你瞅啥扒袖?

一個接力傳遞塞茅,為了描述涉及到兩個類的遞歸過程

 A concrete interceptor chain that carries the entire interceptor chain:
 all application \* interceptors, the OkHttp core, all network interceptors, 
and finally the network caller. 

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); }

之前看到okhttp開發(fā)者之一的作者有寫到:the whole thing is just a stack of built-in interceptors.

可見攔截器是okhttp最核心的一個,不要誤以為它只負(fù)責(zé)攔截請求進(jìn)行了一些額外的處理(如cookie)季率,實質(zhì)上它吧網(wǎng)絡(luò)請求野瘦,緩存,透明壓縮等功能都同意了起來飒泻。每一個功能都是一個攔截器Interceptor鞭光,它們再連接一個chain。最后才完成一次網(wǎng)絡(luò)請求

從getResponseWithInterceptorChain函數(shù)可以看到泞遗,Interceptor.Chain 的分布依次是:

你瞅啥惰许?

這里用網(wǎng)上的一個截圖來表示流程。

在配置okhttpclient時設(shè)置的interceptors

負(fù)責(zé)失敗重回 i以及重定向的retryandfollowupinterceptor

負(fù)責(zé)吧用戶構(gòu)造請求轉(zhuǎn)發(fā)送到服務(wù)器請求史辙,吧度武器返回的響應(yīng)轉(zhuǎn)換成用戶友好的響應(yīng)的bridgerintecptor

負(fù)責(zé)讀取緩存直接返回汹买,更新緩存的cache佩伤;和服務(wù)器建立連接的connect

配置okhttpclient時設(shè)置的network;負(fù)責(zé)向服務(wù)器發(fā)送請求數(shù)據(jù)晦毙,從服務(wù)器讀取響應(yīng)數(shù)據(jù)的callserver

這個讓我看到了一個責(zé)任鏈模式生巡!不得不說okhttp寫出來真的花費了很多構(gòu)思和心血!

它包含了一些命令對象和一系列的處理對象见妒,每一個處理對象決定它能處理哪些命令對象孤荣,它也知道如何將它不能處理的命令對象傳遞給該鏈中的下一個處理對象。該模式還描述了往該處理鏈的末尾添加新的處理對象的方法须揣。--stay

責(zé)任鏈模式在這個 Interceptor 鏈條中得到了很好的實踐Q喂伞!耻卡!學(xué)習(xí)了

接下來遂庄,還有更好的!這個忍不住要說“優(yōu)雅”劲赠,“華麗”涛目!我要說的就是對于request變成response這件事,每個攔截器都有可能完成這件事的流程框架設(shè)計凛澎。(這個鏈條讓讓每個攔截器自己hi還是傳遞給下一個攔截器去hi很靈活芭巍!)

這樣一來塑煎,完成網(wǎng)絡(luò)的請求徹底從realcall這個類中隔離開了沫换,簡化了它們各自的職能。

其實最铁,view的事件分發(fā)中對touchevent事件的處理就是比較典型的責(zé)任鏈讯赏!

好了接下來帶著問題:OkHttp 是怎么進(jìn)行和服務(wù)器實際通信的?

簡單的看下建立連接的connectinterceptor和callserverinteceptor

Connectinterceptor
@Override public Response intercept(Chain chain) throws IOException {
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  Request request = realChain.request();
  StreamAllocation streamAllocation = realChain.streamAllocation();//連接與流的橋梁冷尉, 它負(fù)責(zé)為一次請求尋找連接并建立流漱挎,從而完成遠(yuǎn)程通信,所以StreamAllocation與請求雀哨,連接磕谅,流都相關(guān)
  // We need the network to satisfy this request. Possibly for validating a conditional GET.
  boolean doExtensiveHealthChecks = !request.method().equals("GET");//請求
  HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);//新建流
  RealConnection connection = streamAllocation.connection();//建立連接

  return realChain.proceed(request, streamAllocation, httpCodec, connection);
}

建立連接其實就是創(chuàng)建HttpCodec 對象,它將在后面被拿出來使用雾棺,那它又是啥呢膊夹?是

/** Encodes HTTP requests and decodes HTTP responses. */

有兩個實現(xiàn):Http1Codec 和 Http2Codec,它們分別對應(yīng) HTTP/1.1 和 HTTP/2 版本的實現(xiàn)捌浩。

在 Http1Codec 中放刨,它利用 Okio 對 Socket 的讀寫操作進(jìn)行封裝,(Okio它對 java.io 和 java.nio 進(jìn)行了封裝尸饺,更便捷高效的進(jìn)行 IO 操作进统。)

而創(chuàng)建 HttpCodec 對象的過程涉及到 StreamAllocation拓诸、RealConnection,代碼較長麻昼,這里就不詳述,這個過程概括來說馋辈,就是:找到一個可用的 RealConnection抚芦,再利用 RealConnection 的輸入輸出(BufferedSource 和 BufferedSink)創(chuàng)建 HttpCodec 對象,供后續(xù)步驟使用迈螟。

Callserverinteceptor

@Override public Response intercept(Chain chain) throws IOException {
  HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
  StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
  Request request = chain.request();

  long sentRequestMillis = System.currentTimeMillis();
  httpCodec.writeRequestHeaders(request);    //1. 向socket中寫入請求header信息

  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
        //2. 向socket中寫入請求body信息
    Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
    request.body().writeTo(bufferedRequestBody);
    bufferedRequestBody.close();
  }
  httpCodec.finishRequest();    //3. 完成網(wǎng)絡(luò)請求的寫入
    //4. 讀取網(wǎng)絡(luò)響應(yīng)header信息
  Response response = httpCodec.readResponseHeaders()
      .request(request)
      .handshake(streamAllocation.connection().handshake())
      .sentRequestAtMillis(sentRequestMillis)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();

  if (!forWebSocket || response.code() != 101) {
    //5由 HttpCodec#openResponseBody 提供具體 HTTP 協(xié)議版本的響應(yīng) body叉抡,而 HttpCodec 則是利用 Okio實現(xiàn)具體的數(shù)據(jù) IO 操作。
    response = response.newBuilder()
        .body(httpCodec.openResponseBody(response))
        .build();
  }

  if ("close".equalsIgnoreCase(response.request().header("Connection"))
      || "close".equalsIgnoreCase(response.header("Connection"))) {
    streamAllocation.noNewStreams();
  }

  // 省略部分檢查代碼

  return response;
}

可以看出核基本上都時由 HttpCodec 對象完成答毫,而 HttpCodec 實際上利用的是 Okio褥民,而 Okio (https://github.com/square/okio)實際上還是用的 Socket。

B.接下來發(fā)起異步請求:

client.newCall(request).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());
  //響應(yīng) body 被封裝到 ResponseBody 類中洗搂,該類主要有兩點需要注意:每個 body 只能被消費一次消返,多次消費會拋出異常;body 必須被關(guān)閉耘拇,否則會發(fā)生資源泄漏撵颊;

  }
});

// RealCall#enqueue
@Override
public void enqueue(Callback responseCallback) {
  synchronized (this) {
  if (executed) throw new IllegalStateException("Already Executed");
  executed = true;
  }
  client.dispatcher().enqueue(new AsyncCall(responseCallback));
}

// Dispatcher#enqueue
synchronized void enqueue(AsyncCall call) {
  if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
  runningAsyncCalls.add(call);
  executorService().execute(call);
  } else {
  readyAsyncCalls.add(call);
  }
}

可以發(fā)現(xiàn)dispatcher異步的時候,如果還能執(zhí)行一個并發(fā)請求就執(zhí)行惫叛,否則加入到對列倡勇,而正在執(zhí)行的請求執(zhí)行完就回調(diào)用promotecalls函數(shù),來吧對列中的asynccall變?yōu)閞unningasynccall嘉涌,并且開始執(zhí)行F扌堋(這里的 AsyncCall 是 RealCall 的一個內(nèi)部類,它實現(xiàn)了 Runnable仑最,所以可以在 ExecutorService 上執(zhí)行扔役,而它在執(zhí)行時會調(diào)用 getResponseWithInterceptorChain() 函數(shù),并把結(jié)果通過 responseCallback 傳遞給上層使用者警医。)

無論同步請求和異步請求的原理是一樣的厅目,都是在 getResponseWithInterceptorChain() 函數(shù)中通過 Interceptor 鏈條來實現(xiàn)的網(wǎng)絡(luò)請求邏輯,只不過異步則是通過 ExecutorService 實現(xiàn)法严。

C.緩存策略方面這里也不過多介紹了损敷,

(主要涉及 HTTP 協(xié)議緩存細(xì)節(jié)的實現(xiàn),而具體的緩存邏輯 OkHttp 內(nèi)置封裝了一個 Cache 類深啤,它利用 DiskLruCache拗馒,用磁盤上的有限大小空間進(jìn)行緩存,按照 LRU 算法進(jìn)行緩存淘汰)

我們可以在構(gòu)造 OkHttpClient 時設(shè)置 Cache 對象溯街,在其構(gòu)造函數(shù)中我們可以指定目錄和緩存大杏展稹:

public Cache(File directory, long maxSize);

自定義的緩存策略:也可以自行實現(xiàn) InternalCache 接口洋丐,在構(gòu)造 OkHttpClient 時進(jìn)行設(shè)置。

D.對整體有了清晰認(rèn)識之后挥等,細(xì)節(jié)部分如有需要友绝,再單獨深入將更加容易。(這里借鑒piasy大神的圖和總結(jié))

瞅你咋地肝劲?
  • OkHttpClient 實現(xiàn) Call.Factory迁客,負(fù)責(zé)為 Request 創(chuàng)建 Call;
  • RealCall 為具體的 Call 實現(xiàn)辞槐,其 enqueue() 異步接口通過 Dispatcher 利用 ExecutorService 實現(xiàn)掷漱,而最終進(jìn)行網(wǎng)絡(luò)請求時和同步 execute() 接口一致,都是通過 getResponseWithInterceptorChain() 函數(shù)實現(xiàn)榄檬;
  • getResponseWithInterceptorChain() 中利用 Interceptor 鏈條卜范,分層實現(xiàn)緩存、透明壓縮鹿榜、網(wǎng)絡(luò) IO 等功能海雪;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市舱殿,隨后出現(xiàn)的幾起案子喳魏,更是在濱河造成了極大的恐慌,老刑警劉巖怀薛,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刺彩,死亡現(xiàn)場離奇詭異,居然都是意外死亡枝恋,警方通過查閱死者的電腦和手機(jī)创倔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來焚碌,“玉大人畦攘,你說我怎么就攤上這事∈纾” “怎么了知押?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鹃骂。 經(jīng)常有香客問我台盯,道長,這世上最難降的妖魔是什么畏线? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任静盅,我火速辦了婚禮,結(jié)果婚禮上寝殴,老公的妹妹穿的比我還像新娘蒿叠。我一直安慰自己明垢,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布市咽。 她就那樣靜靜地躺著痊银,像睡著了一般。 火紅的嫁衣襯著肌膚如雪施绎。 梳的紋絲不亂的頭發(fā)上溯革,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天,我揣著相機(jī)與錄音粘姜,去河邊找鬼。 笑死熔酷,一個胖子當(dāng)著我的面吹牛孤紧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拒秘,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼号显,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了躺酒?” 一聲冷哼從身側(cè)響起押蚤,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎羹应,沒想到半個月后揽碘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡园匹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年雳刺,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片裸违。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡掖桦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出供汛,到底是詐尸還是另有隱情枪汪,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布怔昨,位于F島的核電站雀久,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏趁舀。R本人自食惡果不足惜岸啡,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赫编。 院中可真熱鬧巡蘸,春花似錦奋隶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至搬味,卻和暖如春境氢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碰纬。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工萍聊, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人悦析。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓寿桨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親强戴。 傳聞我的和親對象是個殘疾皇子亭螟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容