在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

深入淺出OkHttp源碼解析及應用實踐

OSC開源社區 ? 來源:vivo互聯網技術 ? 2023-06-08 15:47 ? 次閱讀

OkHttp 在 JavaAndroid 世界中被廣泛使用,深入學習源代碼有助于掌握軟件特性和提高編程水平。

本文首先從源代碼入手簡要分析了一個請求發起過程中的核心代碼,接著通過流程圖和架構圖概括地介紹了OkHttp的整體結構,重點分析了攔截器的責任鏈模式設計,最后列舉了OkHttp攔截器在項目中的實際應用。

一、背景介紹

在生產實踐中,常常會遇到這樣的場景:需要針對某一類 Http 請求做統一的處理,例如在 Header 里添加請求參數或者修改請求響應等等。這類問題的一種比較優雅的解決方案是使用攔截器來對請求和響應做統一處理。

在 Android 和 Java 世界里 OkHttp 憑借其高效性和易用性被廣泛使用。作為一款優秀的開源 Http 請求框架,深入了解它的實現原理,可以學習優秀軟件的設計和編碼經驗,幫助我們更好到地使用它的特性,并且有助于特殊場景下的問題排查。本文嘗試從源代碼出發探究 OkHttp 的基本原理,并列舉了一個簡單的例子說明攔截器在我們項目中的實際應用。本文源代碼基于 OkHttp 3.10.0。

二、OkHttp 基本原理

2.1 從一個請求示例出發

OkHttp 可以用來發送同步或異步的請求,異步請求與同步請求的主要區別在于異步請求會交由線程池來調度請求的執行。使用 OkHttp 發送一個同步請求的代碼相當簡潔,示例代碼如下:

同步 GET 請求示例

// 1.創建OkHttpClient客戶端
OkHttpClient client = new OkHttpClient();
public String getSync(String url) throws IOException {
      OkHttpClient client = new OkHttpClient();
      // 2.創建一個Request對象
      Request request = new Request.Builder()
              .url(url)
              .build();
      // 3.創建一個Call對象并調用execute()方法
      try (Response response = client.newCall(request).execute()) {
          return response.body().string();
      }
  }

其中 execute() 方法是請求發起的入口,RealCall 對象的 execute() 方法的源代碼如下:

RealCall 的 execute() 方法源代碼

@Override public Response execute() throws IOException {
  synchronized (this) { // 同步鎖定當前對象,將當前對象標記為“已執行”
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace(); // 捕獲調用棧
  eventListener.callStart(this); // 事件監聽器記錄“調用開始”事件
  try {
    client.dispatcher().executed(this); // 調度器將當前對象放入“運行中”隊列
    Response result = getResponseWithInterceptorChain(); // 通過攔截器發起調用并獲取響應
    if (result == null) throw new IOException("Canceled");
    return result;
  } catch (IOException e) {
    eventListener.callFailed(this, e); // 異常時記錄“調用失敗事件”
    throw e;
  } finally {
    client.dispatcher().finished(this); // 將當前對象從“運行中”隊列移除
  }
}

execute() 方法首先將當前請求標記為“已執行”,然后會為重試跟蹤攔截器添加堆棧追蹤信息,接著事件監聽器記錄“調用開始”事件,調度器將當前對象放入“運行中”隊列,之后通過攔截器發起調用并獲取響應,最后在 finally 塊中將當前請求從“運行中”隊列移除,異常發生時事件監聽器記錄“調用失敗”事件。其中關鍵的方法 是

getResponseWithInterceptorChain() 其源代碼如下:

Response getResponseWithInterceptorChain() throws IOException {
    // 構建一個全棧的攔截器列表
    List 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, ……);
 
    return chain.proceed(originalRequest);
  }

該方法中按照特定的順序創建了一個有序的攔截器列表,之后使用攔截器列表創建攔截器鏈并發起 proceed() 方法調用。在chain.proceed() 方法中會使用遞歸的方式將列表中的攔截器串聯起來依次對請求對象進行處理。攔截器鏈的實現是 OkHttp 的一個巧妙所在,在后文我們會用一小節專門討論。在繼續往下分析之前,通過以上的代碼片段我們已經大致看到了一個請求發起的整體流程。

2.2 OkHttp 核心執行流程

一個 OkHttp 請求的核心執行過程如以下流程圖所示:

84127c36-05d0-11ee-962d-dac502259ad0.png

圖 2-1 OkHttp請求執行流程圖

圖中各部分的含義和作用如下:

OkHttpClient:是整個 OkHttp 的核心管理類,從面向對象的抽象表示上來看它代表了客戶端本身,是請求的調用工廠,用來發送請求和讀取響應。在大多數情況下這個類應該是被共享的,因為每個 Client 對象持有自己的連接池和線程池。重復創建則會造成在空閑池上的資源浪費。Client對象可以通過默認的無參構造方法創建也可以通過 Builder 創建自定義的 Client 對象。Client 持有的線程池和連接池資源在空閑時可以自動釋放無需客戶端代碼手動釋放,在特殊情況下也支持手動釋放。

Request:一個 Request 對象代表了一個 Http 請求。它包含了請求地址 url,請求方法類型 method ,請求頭 headers,請求體 body 等屬性,該對象具有的屬性普遍使用了 final 關鍵字來修飾,正如該類的說明文檔中所述,當這個類的 body 為空或者 body 本身是不可變對象時,這個類是一個不可變對象。

Response:一個 Response 對象代表了一個 Http 響應。這個實例對象是一個不可變對象,只有 responseBody 是一個可以一次性使用的值,其他屬性都是不可變的。

RealCall:一個 RealCall 對象代表了一個準備好執行的請求調用。它只能被執行一次。同時負責了調度和責任鏈組織的兩大重任。

Dispatcher:調度器。它決定了異步調用何時被執行,內部使用 ExecutorService 調度執行,支持自定義 Executor。

EventListener:事件監聽器。抽象類 EventListener 定義了在一個請求生命周期中記錄各種事件的方法,通過監聽各種事件,可以用來捕獲應用程序 HTTP 請求的執行指標。從而監控 HTTP 調用的頻率和性能。

Interceptor:攔截器。對應了軟件設計模式中的攔截器模式,攔截器可用于改變、增強軟件的常規處理流程,該模式的核心特征是對軟件系統的改變是透明的和自動的。OkHttp 將整個請求的復雜邏輯拆分成多個獨立的攔截器實現,通過責任鏈的設計模式將它們串聯到一起,完成發送請求獲取響應結果的過程。

2.3 OkHttp 整體架構

通過進一步閱讀 OkHttp 源碼,可以看到 OkHttp 是一個分層的結構。軟件分層是復雜系統設計的常用手段,通過分層可以將復雜問題劃分成規模更小的子問題,分而治之。同時分層的設計也有利于功能的封裝和復用。OkHttp 的架構可以分為:應用接口層,協議層,連接層,緩存層,I/O層。不同的攔截器為各個層次的處理提供調用入口,攔截器通過責任鏈模式串聯成攔截器鏈,從而完成一個Http請求的完整處理流程。如下圖所示:

841f4240-05d0-11ee-962d-dac502259ad0.png

圖 2-2 OkHttp架構圖(圖片來自網絡)

2.4 OkHttp 攔截器的種類和作用

OkHttp 的核心功能是通過攔截器來實現的,各種攔截器的作用分別為:

client.interceptors:由開發者設置的攔截器,會在所有的攔截器處理之前進行最早的攔截處理,可用于添加一些公共參數,如自定義 header、自定義 log 等等。

RetryAndFollowUpInterceptor:主要負責進行重試和重定向的處理。

BridgeInterceptor:主要負責請求和響應的轉換。把用戶構造的 request 對象轉換成發送到服務器 request對象,并把服務器返回的響應轉換為對用戶友好的響應。

CacheInterceptor:主要負責緩存的相關處理,將 Http 的請求結果放到到緩存中,以便在下次進行相同的請求時,直接從緩存中讀取結果,提高響應速度。

ConnectInterceptor:主要負責建立連接,建立 TCP 連接或者 TLS 連接。

client.networkInterceptors:由開發者設置的攔截器,本質上和第一個攔截器類似,但是由于位置不同,所以用處也不同。

CallServerInterceptor:主要負責網絡數據的請求和響應,也就是實際的網絡I/O操作。將請求頭與請求體發送給服務器,以及解析服務器返回的 response。

除了框架提供的攔截器外,OkHttp 支持用戶自定義攔截器來對請求做增強處理,自定義攔截器可以分為兩類,分別是應用程序攔截器和網絡攔截器,他們發揮作用的層次結構如下圖:

84438646-05d0-11ee-962d-dac502259ad0.png

圖 2-3 攔截器(圖片來自OkHttp官網)

不同的攔截器有不同的適用場景,他們各自的優缺點如下:

應用程序攔截器

無需擔心重定向和重試等中間響應。

總是被調用一次,即使 HTTP 響應是從緩存中提供的。

可以觀察到應用程序的原始請求。不關心 OkHttp 注入的標頭。

允許短路而不調用 Chain.proceed()方法。

允許重試并多次調用 Chain.proceed()方法。

可以使用 withConnectTimeout、withReadTimeout、withWriteTimeout 調整呼叫超時。

網絡攔截器

能夠對重定向和重試等中間響應進行操作。

緩存響應不會調用。

可以觀察到通過網絡傳輸的原始數據。

可以訪問攜帶請求的鏈接。

2.5 責任鏈模式串聯攔截器調用

OkHttp 內置了 5 個核心的攔截器用來完成請求生命周期中的關鍵處理,同時它也支持添加自定義的攔截器來增強和擴展 Http 客戶端,這些攔截器通過責任鏈模式串聯起來,使得的請求可以在不同攔截器之間流轉和處理。

2.5.1 責任鏈模式

責任鏈模式是一種行為設計模式,允許將請求沿著處理者鏈發送。收到請求后,每個處理者均可對請求進行處理,或將其傳遞給鏈上的下一個處理者。

適用場景包括:

當程序需要使用不同方式處理不同種類的請求時

當程序必須按順序執行多個處理者時

當所需要的處理者及其順序必須在運行時進行改變時

優點:

可以控制請求處理的順序

可對發起操作和執行操作的類進行解耦。

可以在不更改現有代碼的情況下在程序中新增處理者。

2.5.2 攔截器的串聯

責任鏈的入口從第一個 RealInterceptorChain 對象的 proceed() 方法調用開始。這個方法的設計非常巧妙,在完整的 proceed() 方法里會做一些更為嚴謹的校驗,去掉這些校驗后該方法的核心代碼如下:

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
 
    // ……  
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
 
    // ……
    return response;
  }

這段代碼可以看成三個步驟:

索引判斷。index 初始值為0,它指示了攔截器對象在列表中的索引順序,每執行一次 proceed 方法該參數自增1,當索引值大于攔截器列表的索引下標時異常退出。

創建下一個責任鏈對象。

按照索引順序獲取一個攔截器,并調用 intercept() 方法。

責任鏈串聯

單獨看這個方法似乎并不能將所有攔截器都串聯起來,串聯的關鍵在于 intercept() 方法,intercept() 方法是實現 interceptor 接口時必須要實現的方法,該方法持有下一個責任鏈 對象 chain,在攔截器的實現類里只需要在 intercept() 方法里的適當地方再次調用 chain.proceed() 方法,程序指令便會重新回到以上代碼片段,于是就可以觸發對于下一個攔截器的查找和調用了,在這個過程中攔截器對象在列表中的先后順序非常重要,因為攔截器的調用順序就是其在列表中的索引順序。

遞歸方法

從另一個角度來看,proceed() 方法可以看成是一個遞歸方法。遞歸方法的基本定義為“函數的定義中調用函數自身”,雖然 proceed() 方法沒有直接調用自身,但是除了最后一個攔截器以外,攔截器鏈中的其他攔截器都會在適當的位置調用 chain.proceed() 方法,責任鏈對象和攔截器對象合在一起則組成了一個自己調用自己的邏輯循環。按照筆者個人理解,這也是為什么源代碼里 Chain 接口被設計成 Interceptor 接口的內部接口,在理解這段代碼的時候要把它們兩個接口當成一個整體來看,從這樣的角度看的話,這樣的接口設計是符合“高內聚”的原則的。

攔截器 interceptor 和責任鏈 chain 的關系如下圖:

847327b6-05d0-11ee-962d-dac502259ad0.png

圖 2-5 Interceptor 和 Chain 的關系圖

三、OkHttp 攔截器在項目中的應用

在我們的項目中,有一類請求需要在請求頭 Header 中添加認證信息,使用攔截器來實現可以極大地簡化代碼,提高代碼可讀性和可維護性。核心代碼只需要實現符合業務需要的攔截器如下:

添加請求頭的攔截器

public class EncryptInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originRequest = chain.request();
 
        // 計算認證信息
        String authorization = this.encrypt(originRequest);
         
        // 添加請求頭
        Request request = originRequest.newBuilder()
                .addHeader("Authorization", authorization)
                .build();
        // 向責任鏈后面傳遞
        return chain.proceed(request);
    }
}

之后在創建 OkHttpClient 客戶端的時候,使用 addInterceptor() 方法將我們的攔截器注冊成應用程序攔截器,即可實現自動地、無感地向請求頭中添加實時的認證信息的功能。

注冊應用程序攔截器

OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new EncryptInterceptor())
    .build();

四、回顧總結

OkHttp 在 Java 和 Android 世界中被廣泛使用,通過使用 OkHttp 攔截器可以解決一類問題——針對一類請求統一修改請求或響應內容。深入了解 OkHttp 的設計和實現不僅可以幫助我們學習優秀開源軟件的設計和編碼經驗,也有利于更好地使用軟件特性以及對特殊場景下問題的排查。本文嘗試從一個同步 GET 請求的例子開始,首先通過源代碼片段簡要分析了一個請求發起過程中涉及的核心代碼,接著用流程圖的形式總結了請求執行過程,然后用架構圖展示了OkHttp的分層設計,介紹了各種攔截器的用途、工作層次及優缺點,之后著重分析了攔截器的責任鏈模式設計——本質是一個遞歸調用,最后用一個簡單的例子介紹了 OkHttp 攔截器在實際生產場景中的應用。




審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 編碼器
    +關注

    關注

    45

    文章

    3747

    瀏覽量

    136457
  • JAVA語言
    +關注

    關注

    0

    文章

    138

    瀏覽量

    20452
  • HTTP協議
    +關注

    關注

    0

    文章

    67

    瀏覽量

    10031
  • 調度器
    +關注

    關注

    0

    文章

    98

    瀏覽量

    5419

原文標題:深入淺出OkHttp源碼解析及應用實踐

文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    深入淺出AVR(傻孩子)

    本帖最后由 eehome 于 2013-1-5 09:56 編輯 深入淺出AVR(傻孩子)
    發表于 06-29 15:43

    深入淺出AVR

    深入淺出AVR,一本書。
    發表于 07-15 12:02

    深入淺出玩轉FPGA

    深入淺出玩轉FPGA
    發表于 07-21 09:21

    深入淺出ARM7

    深入淺出ARM7
    發表于 08-18 10:12

    HDMI技術深入淺出

    HDMI技術深入淺出
    發表于 08-19 10:52

    深入淺出Android

    深入淺出Android
    發表于 08-20 10:14

    深入淺出Android

    深入淺出Android
    發表于 04-26 10:48

    深入淺出安防視頻監控系統

    深入淺出安防視頻監控系統深入淺出安防視頻監控系統
    發表于 05-22 19:28

    深入淺出AVR

    深入淺出AVR
    發表于 08-23 10:10

    深入淺出排序學習使用指南

    深入淺出排序學習:寫給程序員的算法系統開發實踐
    發表于 09-16 11:38

    深入淺出數據分析

    深入淺出數據分析,有需要的朋友下來看看。
    發表于 01-15 14:22 ?0次下載

    深入淺出談多層面板布線技巧

    深入淺出談多層面板布線技巧
    發表于 12-13 22:20 ?0次下載

    深入淺出Android—Android開發經典教材

    深入淺出Android—Android開發經典教材
    發表于 10-24 08:52 ?15次下載
    <b class='flag-5'>深入淺出</b>Android—Android開發經典教材

    深入淺出數字信號處理

    深入淺出數字信號處理
    發表于 12-07 20:14 ?593次閱讀

    深入淺出學習250個通信原理資源下載

    深入淺出學習250個通信原理資源下載
    發表于 04-12 09:16 ?29次下載
    主站蜘蛛池模板: 在线免费国产 | 天天涩综合| 嗯好舒服好爽好快好大 | 奇米第四狠狠777高清秒播 | 日本免费网站在线观看 | 午夜亚洲福利 | 日本一区二区视频在线观看 | 国产精品女人在线观看 | 亚洲一区二区三区在线视频 | 中文字幕在线看精品乱码 | 国产福利午夜 | 亚洲综合五月天欧美 | 欧美一区二区三区在线 | 欧美三级视频在线播放 | 主人扒开腿揉捏花蒂调教cfh | 插插插叉叉叉 | 视频免费黄色 | 亚洲免费成人 | 麒麟色欧美影院在线播放 | 在线视频播放大全 | 大杳蕉伊人狼人久久一本线 | 资源新版在线天堂 | 38pao强力打造永久免费高清视频 | 毛片一区二区三区 | 精品女视频在线观看免费 | 五月网婷婷| www一区| 亚洲综合精品一区二区三区中文 | 午夜看片网站 | 88av在线视频 | 在线小视频你懂的 | 凹厕所xxxxbbbb偷拍视频 | 欧美一卡2卡三卡四卡五卡 欧美一卡二卡3卡4卡无卡六卡七卡科普 | 国产三级在线观看视频 | 四虎最新免费观看网址 | 天天干小说 | 久久性 | 欧美性色黄大片四虎影视 | 国产成人综合自拍 | 一级大片免费看 | 天天亚洲综合 |