Android-OKHttp底層原理淺析(二)

上一篇Android-OKHTTP底層原理淺析(一)講到

getResponseWithInterceptorChain()

這個(gè)方法過,今天接著這部分繼續(xù)诈闺,開始之前我們先預(yù)習(xí)一下——責(zé)任鏈模式菜枷。

ok,這個(gè)責(zé)任鏈模式是什么意思呢浊洞,首先“鏈”牵敷,就如我們生活中常見的鎖鏈,一環(huán)扣一環(huán)法希,首尾相應(yīng),你想長點(diǎn)靶瘸,就多接幾圈苫亦,短點(diǎn)就少接幾圈,對的怨咪,很靈活是吧屋剑,所以責(zé)任鏈模式的一大特征是靈活性。在咱們的編程世界里诗眨,每一個(gè)環(huán)就等于一個(gè)節(jié)點(diǎn)唉匾、一個(gè)對象,有各自負(fù)責(zé)的邏輯匠楚。當(dāng)一個(gè)請求從鏈的首端發(fā)出巍膘,沿著鏈的路徑依次傳遞給每一個(gè)節(jié)點(diǎn),直到有節(jié)點(diǎn)處理這個(gè)請求為止芋簿,我們將這種模式稱之為責(zé)任鏈模式峡懈。這種模式在篩選攔截方面的需求用處較多(比如咱們今天的okhttp)。第二個(gè)特征叫解耦与斤,怎么理解這個(gè)概念呢肪康,舉個(gè)栗子:
比如你們部門要出去嗨了,準(zhǔn)備跟公司申請一筆經(jīng)費(fèi)撩穿,OK磷支,這個(gè)時(shí)候一般都會去找人事部對吧,人事部職員一看食寡,幫你核算一下人數(shù)雾狈,登記一下時(shí)間,ok冻河,轉(zhuǎn)達(dá)給人事部大佬箍邮,大佬接到申請單茉帅,掃了一眼,大概沒問題了锭弊,但是批不了呀他堪澎,為啥,因?yàn)樨?cái)政大權(quán)不在他手上味滞,他立馬轉(zhuǎn)交給了財(cái)務(wù)部樱蛤,財(cái)務(wù)部妹妹接過手,嗯都填的差不多了剑鞍,就是人數(shù)跟經(jīng)費(fèi)有點(diǎn)超昨凡,他這邊不好批,所以轉(zhuǎn)給了財(cái)務(wù)部大佬蚁署,財(cái)務(wù)部大佬大手一揮便脊,同意,然后層層返回光戈,最后人事跟你說哪痰,OK 去嗨吧 記得開發(fā)票。
仔細(xì)想想久妆,如果不是上帝視角晌杰,你覺得你接觸的有多少角色? 沒錯(cuò)筷弦,可能只有第一次的人事妹妹肋演,最后告知你OK的也是她。后面的事你不須理會烂琴,哪天后面的流程有變動也不關(guān)你事(這就是靈活性)爹殊,這就是責(zé)任鏈第二大特征。
在Android的源碼中监右,比較經(jīng)典的責(zé)任鏈場景就是事件分發(fā)边灭,有興趣的筒子可以去了解一下ViewGroup是如何將事件派發(fā)到子view的。
好健盒,啰嗦完了绒瘦,有了這個(gè)概念之后咱們繼續(xù)講okhttp。

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);
    return chain.proceed(originalRequest);
  }

首先這里定義了一個(gè)攔截器集合扣癣,里面分別有retryAndFollowUpInterceptor 惰帽, BridgeInterceptor , CacheInterceptor 父虑,ConnectInterceptor 该酗,CallServerInterceptor, 以及我們在外面自定義的client.interceptors(),client.networkInterceptors()呜魄,一個(gè)一個(gè)來悔叽,先看第一個(gè)retryAndFollowUpInterceptor

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

    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response = null;
      boolean releaseConnection = true;
      try {
        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), false, request)) {
          throw e.getLastConnectException();
        }
        releaseConnection = false;
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } finally {
        // We're throwing an unchecked exception. Release any resources.
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

      // Attach the prior response if it exists. Such responses never have a body.
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Request followUp = followUpRequest(response);

      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

      closeQuietly(response.body());

      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (followUp.body() instanceof UnrepeatableRequestBody) {
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }

      if (!sameConnection(response, followUp.url())) {
        streamAllocation.release();
        streamAllocation = new StreamAllocation(
            client.connectionPool(), createAddress(followUp.url()), callStackTrace);
      } else if (streamAllocation.codec() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;
      priorResponse = response;
    }
  }

有點(diǎn)長,但是我們只看關(guān)鍵點(diǎn)爵嗅,首先這個(gè)攔截器是一個(gè)重定向攔截器娇澎,他的工作是負(fù)責(zé)創(chuàng)建一個(gè)連接對象,以及處理一些異扯蒙梗看需不需要重新發(fā)起請求趟庄,首先創(chuàng)建了一個(gè)streamAllocation連接對象(注意只是創(chuàng)建,沒有連接伪很,請多看他幾眼戚啥,后面的某一個(gè)攔截器你會又看到他的了),傳參分別為連接池锉试,連接地址猫十,堆棧對象。接下來創(chuàng)建了一個(gè)while循環(huán)while (true) {} 其中有一句

response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);

這里是執(zhí)行下一個(gè)攔截器的意思键痛,從外面一層可以看出接下來的攔截器是BridgeInterceptor炫彩,怎樣看出是執(zhí)行下一個(gè)呢,我們點(diǎn)開proceed進(jìn)去絮短,你會發(fā)現(xiàn)

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      Connection connection) throws IOException {
   <省略部分代碼>

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

  <省略部分代碼>

    return response;
  }

是吧,index + 1昨忆,所以接下來幾個(gè)攔截器也是通過這樣的方式去調(diào)用下一個(gè)的丁频。
好,回到上面邑贴,當(dāng)執(zhí)行下個(gè)攔截器之后席里,后面的代碼就是等到里面的攔截器執(zhí)行完了,返回了之后才會繼續(xù)走的了拢驾。

Request followUp = followUpRequest(response);

      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

這里是判斷是否重定向或者是超時(shí)重試奖磁,接下來還有一些判斷是否超過最大限制

if (++followUpCount > MAX_FOLLOW_UPS) {
      streamAllocation.release();
      throw new ProtocolException("Too many follow-up requests: " + followUpCount);
    }

以及是否有相同連接

if (!sameConnection(response, followUp.url())) {
      streamAllocation.release();
      streamAllocation = new StreamAllocation(
          client.connectionPool(), createAddress(followUp.url()), callStackTrace);
    } else if (streamAllocation.codec() != null) {
      throw new IllegalStateException("Closing the body of " + response
          + " didn't close its backing stream. Bad interceptor?");
    }

一系列判斷,這個(gè)攔截器的任務(wù)就到這過繁疤,簡單來講的一個(gè)流程就是

(前)創(chuàng)建連接對象咖为,
開啟循環(huán),
執(zhí)行下一個(gè)攔截器稠腊,
——(數(shù)據(jù)返回后)如果有異常躁染,判斷是否需要恢復(fù),
檢查是否符合要求(符合則返回結(jié)果)架忌,
是否超過限制(超過拋出異常停止循環(huán))吞彤,
是否有相同的連接(如果有則復(fù)用,沒有則新建)。

ok饰恕,第一個(gè)攔截器的內(nèi)容過挠羔,看第二個(gè)BridgeInterceptor

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

    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }

    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }

    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);
      responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }

這是一個(gè)橋攔截器,放眼望去埋嵌,是不是看到了很多熟悉的東西破加?(如果你不熟悉。莉恼。拌喉。當(dāng)我沒說[反手就是一巴掌.png])
是的,這里在負(fù)責(zé)請求的拼裝俐银,全局的來講應(yīng)該稱之為轉(zhuǎn)換尿背,因?yàn)楹竺娴臄r截器返回的數(shù)據(jù)也會經(jīng)過這里轉(zhuǎn)換之后才會回到我們剛剛講的第一個(gè)攔截器retryAndFollowUpInterceptor那里去的

Response networkResponse = chain.proceed(requestBuilder.build());

這句代碼之上就是一些請求包裝,具體的每個(gè)點(diǎn)就不細(xì)說了捶惜,比如cookie什么的田藐,都可以自定義的,否則就添加默認(rèn)的吱七,這句代碼就是執(zhí)行了下一個(gè)攔截器汽久,執(zhí)行完成后數(shù)據(jù)返回,接下來

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

public static void receiveHeaders(CookieJar cookieJar, HttpUrl url, Headers headers) {
    if (cookieJar == CookieJar.NO_COOKIES) return;

    List<Cookie> cookies = Cookie.parseAll(url, headers);
    if (cookies.isEmpty()) return;

    cookieJar.saveFromResponse(url, cookies);
  }

這里解析服務(wù)器返回的Hearder(如果cookiejar為空則不做處理)

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

這里判斷是否支持gzip壓縮踊餐,可以的話就使用Okio庫處理
ok景醇,這個(gè)攔截器做的主要是

(前)對Hearder的一些處理,協(xié)議的包裝(默認(rèn)還是自定義)
執(zhí)行下一個(gè)攔截器
——(數(shù)據(jù)返回后)
判斷是否支持gzip吝岭,
數(shù)據(jù)處理完返回至上一層攔截器retryAndFollowUpInterceptor

接下來的幾個(gè)攔截器我們在下一篇講三痰,文章太長看的臉疼~
Android-OKhttp底層原理淺析(三)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市窜管,隨后出現(xiàn)的幾起案子散劫,更是在濱河造成了極大的恐慌,老刑警劉巖幕帆,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件获搏,死亡現(xiàn)場離奇詭異,居然都是意外死亡失乾,警方通過查閱死者的電腦和手機(jī)常熙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仗扬,“玉大人症概,你說我怎么就攤上這事≡绨牛” “怎么了彼城?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我募壕,道長调炬,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任舱馅,我火速辦了婚禮缰泡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘代嗤。我一直安慰自己棘钞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布干毅。 她就那樣靜靜地躺著宜猜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪硝逢。 梳的紋絲不亂的頭發(fā)上姨拥,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機(jī)與錄音渠鸽,去河邊找鬼叫乌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛徽缚,可吹牛的內(nèi)容都是我干的憨奸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼凿试,長吁一口氣:“原來是場噩夢啊……” “哼膀藐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起红省,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎国觉,沒想到半個(gè)月后吧恃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡麻诀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年痕寓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝇闭。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呻率,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出呻引,到底是詐尸還是另有隱情礼仗,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站元践,受9級特大地震影響韭脊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜单旁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一沪羔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧象浑,春花似錦蔫饰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至粒氧,卻和暖如春越除,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背外盯。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工摘盆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饱苟。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓孩擂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親箱熬。 傳聞我的和親對象是個(gè)殘疾皇子类垦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354