Okhttp3 Interceptor(三)

interceptor.png

前言

OkHttp 中的 Interceptor 是通過(guò)責(zé)任鏈模式來(lái)設(shè)計(jì)的, 責(zé)任鏈模式參考: 責(zé)任鏈模式 , 至于為什么需要使用該模式, 我的理解是一次完整的請(qǐng)求需要以下步驟

  1. 構(gòu)建業(yè)務(wù)請(qǐng)求數(shù)據(jù)
  2. 自定義公共 Header 數(shù)據(jù)
  3. 建立 Socket 連接
  4. 發(fā)送請(qǐng)求
  5. 緩存請(qǐng)求數(shù)據(jù)

那么對(duì)每一個(gè)步驟來(lái)講舔亭, 它是按照處理邏輯進(jìn)行排序, 并且每一個(gè)處理步驟都代表相應(yīng)的職責(zé)勇边,通常來(lái)講,編寫(xiě)代碼時(shí)不會(huì)將所有的邏輯都放到一堆去寫(xiě)队他, 因?yàn)楫?dāng)需要增添其他功能時(shí)將是巨大的災(zāi)難,徒役,這時(shí)責(zé)任鏈模式將派上巨大用場(chǎng)。

Interceptor 介紹

Interceptors are a powerful mechanism that can monitor, rewrite, and retry calls

如官網(wǎng)所稱, Interceptors 是一個(gè)強(qiáng)大的機(jī)制, 可以用來(lái) 監(jiān)控于游、重寫(xiě) request 毁葱、 重試等 。

在 Okhttp 中贰剥,攔截器同樣起到非常重要的作用倾剿,通過(guò)提供的默認(rèn)攔截器來(lái)實(shí)現(xiàn)了:建立連接、發(fā)送請(qǐng)求蚌成、處理響應(yīng)等前痘,其中 Interceptor 又根據(jù)使用場(chǎng)景劃分為 Application Interceptor 和 Network Interceptor ,Application Interceptor 是用來(lái)在整個(gè) okhttp client 應(yīng)用處理請(qǐng)求期間起作用且只被執(zhí)行一次担忧,而 Network Interceptor 則是在 okhttp client 應(yīng)用處理請(qǐng)求期間中的每一次網(wǎng)絡(luò)交互都會(huì)執(zhí)行一次(因?yàn)橛兄卦嚥呗约识龋钥赡軙?huì)出現(xiàn)處理一次應(yīng)用的請(qǐng)求需要多次網(wǎng)絡(luò)重試)。那么根據(jù)其場(chǎng)景可以選擇不同類(lèi)型攔截器進(jìn)行增強(qiáng)涵妥。

  public class ApplicationLogInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
      Request request = chain.request();
      long start = System.currentTimeMillis();
      Response response = chain.proceed(request);
      long end = System.currentTimeMillis();
      if (end - start > 300) {
        System.out.println(String.format("request cost time exceed=%s ms", end - start));
      }
      return response;
    }
  }

    OkHttpClient httpClient = new OkHttpClient.Builder()
        .callTimeout(500, TimeUnit.MILLISECONDS)
        .connectTimeout(500, TimeUnit.MILLISECONDS)
        .addInterceptor(new ApplicationLogInterceptor())
        .build();

通過(guò)實(shí)現(xiàn) okhttp3.Interceptor 的 intercept 來(lái)達(dá)到對(duì)請(qǐng)求增強(qiáng)的目的乖菱,然后利用 OkHttpClient.Builder 的 addInterceptor(Interceptor interceptor)方法,將應(yīng)用攔截器的引用共享給 OkHttpClient 客戶端蓬网,這里與頂部圖片的 OkHttp Core 上半部分相同窒所。如果需要使用網(wǎng)絡(luò)攔截器,只需要將OkHttpClient.Builder 的 addInterceptor 方法換成 addNetworkInterceptor 即可帆锋。

注意這里的攔截器對(duì)所有請(qǐng)求都是屬于共享的吵取,因此所有類(lèi)變量使用不當(dāng)將會(huì)導(dǎo)致線程不安全,如果必須讓每個(gè)攔截器有“狀態(tài)”锯厢,那么可以通過(guò) ThreadLocal 來(lái)實(shí)現(xiàn)

Okhttp Core 中 默認(rèn)攔截器

Okhttp 將 http request 所經(jīng)過(guò)的每次請(qǐng)求鏈路的功能劃分給按職責(zé)劃分到不同攔截器中皮官,最終通過(guò)攔截器鏈 RealInterceptorChain 來(lái)組織,完成一次又一次的請(qǐng)求與響應(yīng)

  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 (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    Response response = chain.proceed(originalRequest);
    if (retryAndFollowUpInterceptor.isCanceled()) {
      closeQuietly(response);
      throw new IOException("Canceled");
    }
    return response;
  }

在 RealCall 的 getResponseWithInterceptorChain() 方法中可以看到实辑,在執(zhí)行請(qǐng)求前捺氢,需要默認(rèn)的攔截器和業(yè)務(wù)自定義的攔截器添加到 interceptors 之后,最終交給 RealInterceptorChain 來(lái)調(diào)度 (突然想到 國(guó)不可一日無(wú)君,家不可一日無(wú)主)

  1. RetryAndFollowUpInterceptor

負(fù)責(zé)對(duì)請(qǐng)求進(jìn)行重試處理剪撬,前提是沒(méi)有在 OkHttpClient.Builder 中設(shè)置 .retryOnConnectionFailure(false)摄乒,默認(rèn)是 true ,并且重試 21 次, google 瀏覽器也是這個(gè)策略馍佑,而 Http1.1 則是推薦 5 次斋否,沒(méi)辦法改重試的次數(shù),只有你把它 ban 了,然后自己寫(xiě)個(gè)應(yīng)用攔截器,具體看 RetryAndFollowUpInterceptor 源碼

     while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        ...... 省略
      } catch (IOException e) {
        ...... 省略
      } finally {
        ...... 省略
      }
....

這里可以看到 RouteException 和 IOException 這兩種異常的情況才有機(jī)會(huì)重試

  1. BridgeInterceptor

BridgeInterceptor負(fù)責(zé)在request階段對(duì)請(qǐng)求頭添加一些字段,在response階段對(duì)響應(yīng)進(jìn)行一些gzip解壓操作

  @Override 
  public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }
...... 省略
   // 處理響應(yīng)數(shù)據(jù)
    Response networkResponse = chain.proceed(requestBuilder.build());

    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
...... 省略
  1. CacheInterceptor

緩存服務(wù)的請(qǐng)求與響應(yīng)

  1. ConnectInterceptor

創(chuàng)建一個(gè)新的連接,或從連接池服用連接

  1. CallServerInterceptor

這是攔截器鏈中的最后的攔截器辛慰,使用 ConnectInterceptor 的連接來(lái)向目標(biāo)服務(wù)器發(fā)送網(wǎng)絡(luò)請(qǐng)求,并讀取解析 Response 數(shù)據(jù)流

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子社证,更是在濱河造成了極大的恐慌逼龟,老刑警劉巖评凝,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異腺律,居然都是意外死亡奕短,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)匀钧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)翎碑,“玉大人,你說(shuō)我怎么就攤上這事之斯∪砧荆” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵佑刷,是天一觀的道長(zhǎng)莉擒。 經(jīng)常有香客問(wèn)我,道長(zhǎng)瘫絮,這世上最難降的妖魔是什么涨冀? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮麦萤,結(jié)果婚禮上鹿鳖,老公的妹妹穿的比我還像新娘。我一直安慰自己壮莹,他們只是感情好翅帜,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著命满,像睡著了一般藕甩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,549評(píng)論 1 312
  • 那天狭莱,我揣著相機(jī)與錄音僵娃,去河邊找鬼。 笑死腋妙,一個(gè)胖子當(dāng)著我的面吹牛默怨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骤素,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼匙睹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了济竹?” 一聲冷哼從身側(cè)響起痕檬,我...
    開(kāi)封第一講書(shū)人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎送浊,沒(méi)想到半個(gè)月后梦谜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡袭景,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年唁桩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耸棒。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡荒澡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出与殃,到底是詐尸還是另有隱情单山,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布幅疼,位于F島的核電站米奸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏衣屏。R本人自食惡果不足惜躏升,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望狼忱。 院中可真熱鬧膨疏,春花似錦、人聲如沸钻弄。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)窘俺。三九已至饲帅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背灶泵。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工育八, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赦邻。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓髓棋,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親惶洲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子按声,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361