溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

OkHttp解析

發(fā)布時(shí)間:2020-08-24 18:56:50 來(lái)源:網(wǎng)絡(luò) 閱讀:337 作者:Android_JIE 欄目:移動(dòng)開(kāi)發(fā)

一、整體思路

從使用方法出發(fā),首先是怎么使用,其次是我們使用的功能在內(nèi)部是如何實(shí)現(xiàn)的,實(shí)現(xiàn)方案上有什么技巧,有什么范式。全文基本上是對(duì) OkHttp 源碼的一個(gè)分析與導(dǎo)讀,非常建議大家下載 OkHttp 源碼之后,跟著本文,過(guò)一遍源碼。對(duì)于技巧和范式,由于目前我的功力還不到位,分析內(nèi)容沒(méi)多少,歡迎大家和我一起討論。

首先放一張完整流程圖(看不懂沒(méi)關(guān)系,慢慢往后看):

OkHttp解析

二、基本用例

來(lái)自O(shè)kHttp 官方網(wǎng)站。

2.1.創(chuàng)建 OkHttpClient 對(duì)象

OkHttpClient client = new OkHttpClient();

咦,怎么不見(jiàn) builder?莫急,且看其構(gòu)造函數(shù):

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

原來(lái)是方便我們使用,提供了一個(gè)“快捷操作”,全部使用了默認(rèn)的配置。OkHttpClient.Builder類(lèi)成員很多,后面我們?cè)俾治?,這里先暫時(shí)略過(guò):

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

2.2.發(fā)起 HTTP 請(qǐng)求

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實(shí)現(xiàn)了Call.Factory,負(fù)責(zé)根據(jù)請(qǐng)求創(chuàng)建新的Call。

那我們現(xiàn)在就來(lái)看看它是如何創(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);
}

如此看來(lái)功勞全在RealCall類(lèi)了,下面我們一邊分析同步網(wǎng)絡(luò)請(qǐng)求的過(guò)程,一邊了解RealCall的具體內(nèi)容。

2.2.1.同步網(wǎng)絡(luò)請(qǐng)求

我們首先看RealCall#execute

@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 = getResponseWithInterceptorChain();                // (3)
    if (result == null) throw new IOException("Canceled");
    return result;
  } finally {
    client.dispatcher().finished(this);                                 // (4)
  }
}

這里我們做了 4 件事:

  1. 檢查這個(gè) call 是否已經(jīng)被執(zhí)行了,每個(gè) call 只能被執(zhí)行一次,如果想要一個(gè)完全一樣的 call,可以利用call#clone方法進(jìn)行克隆。
  2. 利用client.dispatcher().executed(this)來(lái)進(jìn)行實(shí)際執(zhí)行dispatcher是剛才看到的OkHttpClient.Builder的成員之一,它的文檔說(shuō)自己是異步 HTTP 請(qǐng)求的執(zhí)行策略,現(xiàn)在看來(lái),同步請(qǐng)求它也有摻和。
  3. 調(diào)用getResponseWithInterceptorChain()函數(shù)獲取 HTTP 返回結(jié)果,從函數(shù)名可以看出,這一步還會(huì)進(jìn)行一系列“攔截”操作。
  4. 最后還要通知dispatcher自己已經(jīng)執(zhí)行完畢。

dispatcher 這里我們不過(guò)度關(guān)注,在同步執(zhí)行的流程中,涉及到 dispatcher 的內(nèi)容只不過(guò)是告知它我們的執(zhí)行狀態(tài),比如開(kāi)始執(zhí)行了(調(diào)用executed),比如執(zhí)行完畢了(調(diào)用finished),在異步執(zhí)行流程中它會(huì)有更多的參與。

真正發(fā)出網(wǎng)絡(luò)請(qǐng)求,解析返回結(jié)果的,還是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);
}

在OkHttp 開(kāi)發(fā)者之一介紹 OkHttp 的文章里面,作者講到:

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

可見(jiàn)Interceptor是 OkHttp 最核心的一個(gè)東西,不要誤以為它只負(fù)責(zé)攔截請(qǐng)求進(jìn)行一些額外的處理(例如 cookie),實(shí)際上它把實(shí)際的網(wǎng)絡(luò)請(qǐng)求、緩存、透明壓縮等功能都統(tǒng)一了起來(lái),每一個(gè)功能都只是一個(gè)Interceptor,它們?cè)龠B接成一個(gè)Interceptor.Chain,環(huán)環(huán)相扣,最終圓滿完成一次網(wǎng)絡(luò)請(qǐng)求。

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

OkHttp解析

  1. 在配置OkHttpClient時(shí)設(shè)置的interceptors
  2. 負(fù)責(zé)失敗重試以及重定向的RetryAndFollowUpInterceptor;
  3. 負(fù)責(zé)把用戶(hù)構(gòu)造的請(qǐng)求轉(zhuǎn)換為發(fā)送到服務(wù)器的請(qǐng)求、把服務(wù)器返回的響應(yīng)轉(zhuǎn)換為用戶(hù)友好的響應(yīng)的BridgeInterceptor
  4. 負(fù)責(zé)讀取緩存直接返回、更新緩存的CacheInterceptor
  5. 負(fù)責(zé)和服務(wù)器建立連接的ConnectInterceptor;
  6. 配置OkHttpClient時(shí)設(shè)置的networkInterceptors;
  7. 負(fù)責(zé)向服務(wù)器發(fā)送請(qǐng)求數(shù)據(jù)、從服務(wù)器讀取響應(yīng)數(shù)據(jù)CallServerInterceptor。

在這里,位置決定了功能,最后一個(gè) Interceptor 一定是負(fù)責(zé)和服務(wù)器實(shí)際通訊的,重定向、緩存等一定是在實(shí)際通訊之前的。

責(zé)任鏈模式在這個(gè)Interceptor鏈條中得到了很好的實(shí)踐。

它包含了一些命令對(duì)象和一系列的處理對(duì)象,每一個(gè)處理對(duì)象決定它能處理哪些命令對(duì)象,它也知道如何將它不能處理的命令對(duì)象傳遞給該鏈中的下一個(gè)處理對(duì)象。該模式還描述了往該處理鏈的末尾添加新的處理對(duì)象的方法。

對(duì)于把Request變成Response這件事來(lái)說(shuō),每個(gè)Interceptor都可能完成這件事,所以我們循著鏈條讓每個(gè)Interceptor自行決定能否完成任務(wù)以及怎么完成任務(wù)(自力更生或者交給下一個(gè)Interceptor)。這樣一來(lái),完成網(wǎng)絡(luò)請(qǐng)求這件事就徹底從RealCall類(lèi)中剝離了出來(lái),簡(jiǎn)化了各自的責(zé)任和邏輯。兩個(gè)字:優(yōu)雅!

責(zé)任鏈模式在安卓系統(tǒng)中也有比較典型的實(shí)踐,例如 view 系統(tǒng)對(duì)點(diǎn)擊事件(TouchEvent)的處理。

回到 OkHttp,在這里我們先簡(jiǎn)單分析一下ConnectInterceptorCallServerInterceptor,看看 OkHttp 是怎么進(jìn)行和服務(wù)器的實(shí)際通信的。

2.2.1.1.建立連接:ConnectInterceptor
@Override public Response intercept(Chain chain) throws IOException {
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  Request request = realChain.request();
  StreamAllocation streamAllocation = realChain.streamAllocation();

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

實(shí)際上建立連接就是創(chuàng)建了一個(gè)HttpCodec對(duì)象,它將在后面的步驟中被使用,那它又是何方神圣呢?它是對(duì) HTTP 協(xié)議操作的抽象,有兩個(gè)實(shí)現(xiàn):Http1CodecHttp2Codec,顧名思義,它們分別對(duì)應(yīng) HTTP/1.1 和 HTTP/2 版本的實(shí)現(xiàn)。

Http1Codec中,它利用Okio對(duì)Socket的讀寫(xiě)操作進(jìn)行封裝,Okio 以后有機(jī)會(huì)再進(jìn)行分析,現(xiàn)在讓我們對(duì)它們保持一個(gè)簡(jiǎn)單地認(rèn)識(shí):它對(duì)java.iojava.nio進(jìn)行了封裝,讓我們更便捷高效的進(jìn)行 IO 操作。

而創(chuàng)建HttpCodec對(duì)象的過(guò)程涉及到StreamAllocation、RealConnection,代碼較長(zhǎng),這里就不展開(kāi),這個(gè)過(guò)程概括來(lái)說(shuō),就是找到一個(gè)可用的RealConnection,再利用RealConnection的輸入輸出(BufferedSourceBufferedSink)創(chuàng)建HttpCodec對(duì)象,供后續(xù)步驟使用。

2.2.1.2.發(fā)送和接收數(shù)據(jù):CallServerInterceptor
@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);

  if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
    request.body().writeTo(bufferedRequestBody);
    bufferedRequestBody.close();
  }

  httpCodec.finishRequest();

  Response response = httpCodec.readResponseHeaders()
      .request(request)
      .handshake(streamAllocation.connection().handshake())
      .sentRequestAtMillis(sentRequestMillis)
      .receivedResponseAtMillis(System.currentTimeMillis())
      .build();

  if (!forWebSocket || response.code() != 101) {
    response = response.newBuilder()
        .body(httpCodec.openResponseBody(response))
        .build();
  }

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

  // 省略部分檢查代碼

  return response;
}

我們抓住主干部分:

  1. 向服務(wù)器發(fā)送 request header;
  2. 如果有 request body,就向服務(wù)器發(fā)送;
  3. 讀取 response header,先構(gòu)造一個(gè)Response對(duì)象;
  4. 如果有 response body,就在 3 的基礎(chǔ)上加上 body 構(gòu)造一個(gè)新的Response對(duì)象;

這里我們可以看到,核心工作都由HttpCodec對(duì)象完成,而HttpCodec實(shí)際上利用的是 Okio,而 Okio 實(shí)際上還是用的Socket,所以沒(méi)什么神秘的,只不過(guò)一層套一層,層數(shù)有點(diǎn)多。

其實(shí)Interceptor的設(shè)計(jì)也是一種分層的思想,每個(gè)Interceptor就是一層。為什么要套這么多層呢?分層的思想在 TCP/IP 協(xié)議中就體現(xiàn)得淋漓盡致,分層簡(jiǎn)化了每一層的邏輯,每層只需要關(guān)注自己的責(zé)任(單一原則思想也在此體現(xiàn)),而各層之間通過(guò)約定的接口/協(xié)議進(jìn)行合作(面向接口編程思想),共同完成復(fù)雜的任務(wù)。

簡(jiǎn)單應(yīng)該是我們的終極追求之一,盡管有時(shí)為了達(dá)成目標(biāo)不得不復(fù)雜,但如果有另一種更簡(jiǎn)單的方式,我想應(yīng)該沒(méi)有人不愿意替換。

2.2.2.發(fā)起異步網(wǎng)絡(luò)請(qǐng)求
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());
    }
});

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

這里我們就能看到 dispatcher 在異步執(zhí)行時(shí)發(fā)揮的作用了,如果當(dāng)前還能執(zhí)行一個(gè)并發(fā)請(qǐng)求,那就立即執(zhí)行,否則加入readyAsyncCalls隊(duì)列,而正在執(zhí)行的請(qǐng)求執(zhí)行完畢之后,會(huì)調(diào)用promoteCalls()函數(shù),來(lái)把readyAsyncCalls隊(duì)列中的AsyncCall“提升”為runningAsyncCalls,并開(kāi)始執(zhí)行。

這里的AsyncCallRealCall的一個(gè)內(nèi)部類(lèi),它實(shí)現(xiàn)了Runnable,所以可以被提交到ExecutorService上執(zhí)行,而它在執(zhí)行時(shí)會(huì)調(diào)用getResponseWithInterceptorChain()函數(shù),并把結(jié)果通過(guò)responseCallback傳遞給上層使用者。

這樣看來(lái),同步請(qǐng)求和異步請(qǐng)求的原理是一樣的,都是在getResponseWithInterceptorChain()函數(shù)中通過(guò)Interceptor鏈條來(lái)實(shí)現(xiàn)的網(wǎng)絡(luò)請(qǐng)求邏輯,而異步則是通過(guò)ExecutorService實(shí)現(xiàn)。

2.3返回?cái)?shù)據(jù)的獲取

在上述同步(Call#execute()執(zhí)行之后)或者異步(Callback#onResponse()回調(diào)中)請(qǐng)求完成之后,我們就可以從Response對(duì)象中獲取到響應(yīng)數(shù)據(jù)了,包括 HTTP status code,status message,response header,response body 等。這里 body 部分最為特殊,因?yàn)榉?wù)器返回的數(shù)據(jù)可能非常大,所以必須通過(guò)數(shù)據(jù)流的方式來(lái)進(jìn)行訪問(wèn)(當(dāng)然也提供了諸如string()bytes()這樣的方法將流內(nèi)的數(shù)據(jù)一次性讀取完畢),而響應(yīng)中其他部分則可以隨意獲取。

響應(yīng) body 被封裝到ResponseBody類(lèi)中,該類(lèi)主要有兩點(diǎn)需要注意:

  1. 每個(gè) body 只能被消費(fèi)一次,多次消費(fèi)會(huì)拋出異常;
  2. body 必須被關(guān)閉,否則會(huì)發(fā)生資源泄漏;

在2.2.1.2.發(fā)送和接收數(shù)據(jù):CallServerInterceptor小節(jié)中,我們就看過(guò)了 body 相關(guān)的代碼:

if (!forWebSocket || response.code() != 101) {
  response = response.newBuilder()
      .body(httpCodec.openResponseBody(response))
      .build();
}

HttpCodec#openResponseBody提供具體 HTTP 協(xié)議版本的響應(yīng) body,而HttpCodec則是利用 Okio 實(shí)現(xiàn)具體的數(shù)據(jù) IO 操作。

這里有一點(diǎn)值得一提,OkHttp 對(duì)響應(yīng)的校驗(yàn)非常嚴(yán)格,HTTP status line 不能有任何雜亂的數(shù)據(jù),否則就會(huì)拋出異常,在我們公司項(xiàng)目的實(shí)踐中,由于服務(wù)器的問(wèn)題,偶爾 status line 會(huì)有額外數(shù)據(jù),而服務(wù)端的問(wèn)題也毫無(wú)頭緒,導(dǎo)致我們不得不忍痛繼續(xù)使用 HttpUrlConnection,而后者在一些系統(tǒng)上又存在各種其他的問(wèn)題,例如魅族系統(tǒng)發(fā)送 multi-part form 的時(shí)候就會(huì)出現(xiàn)沒(méi)有響應(yīng)的問(wèn)題。

2.4.HTTP 緩存

在2.2.1.同步網(wǎng)絡(luò)請(qǐng)求小節(jié)中,我們已經(jīng)看到了Interceptor的布局,在建立連接、和服務(wù)器通訊之前,就是CacheInterceptor,在建立連接之前,我們檢查響應(yīng)是否已經(jīng)被緩存、緩存是否可用,如果是則直接返回緩存的數(shù)據(jù),否則就進(jìn)行后面的流程,并在返回之前,把網(wǎng)絡(luò)的數(shù)據(jù)寫(xiě)入緩存。

這塊代碼比較多,但也很直觀,主要涉及 HTTP 協(xié)議緩存細(xì)節(jié)的實(shí)現(xiàn),而具體的緩存邏輯 OkHttp 內(nèi)置封裝了一個(gè)Cache類(lèi),它利用DiskLruCache,用磁盤(pán)上的有限大小空間進(jìn)行緩存,按照 LRU 算法進(jìn)行緩存淘汰,這里也不再展開(kāi)。

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

public Cache(File directory, long maxSize);

而如果我們對(duì) OkHttp 內(nèi)置的Cache類(lèi)不滿意,我們可以自行實(shí)現(xiàn)InternalCache接口,在構(gòu)造OkHttpClient時(shí)進(jìn)行設(shè)置,這樣就可以使用我們自定義的緩存策略了。

三、總結(jié)

OkHttp 還有很多細(xì)節(jié)部分沒(méi)有在本文展開(kāi),例如 HTTP2/HTTPS 的支持等,但建立一個(gè)清晰的概覽非常重要。對(duì)整體有了清晰認(rèn)識(shí)之后,細(xì)節(jié)部分如有需要,再單獨(dú)深入將更加容易。

在文章最后我們?cè)賮?lái)回顧一下完整的流程圖:

OkHttp解析

  • OkHttpClient實(shí)現(xiàn)Call.Factory,負(fù)責(zé)為Request創(chuàng)建Call
  • RealCall為具體的Call實(shí)現(xiàn),其enqueue()異步接口通過(guò)Dispatcher利用ExecutorService實(shí)現(xiàn),而最終進(jìn)行網(wǎng)絡(luò)請(qǐng)求時(shí)和同步execute()接口一致,都是通過(guò)getResponseWithInterceptorChain()函數(shù)實(shí)現(xiàn);
  • getResponseWithInterceptorChain()中利用Interceptor鏈條,分層實(shí)現(xiàn)緩存、透明壓縮、網(wǎng)絡(luò) IO 等功能;
向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI