Okhttp3源碼解析(3)-Call分析(整體流程)

来源:https://www.cnblogs.com/qinzishuai/archive/2019/08/23/11401572.html
-Advertisement-
Play Games

### 前言 前面我們講了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源碼解析(1)-OkHttpClient分析](https://www.jianshu.com/p/bf1d01b79ce7) [Okhttp3源碼 ...


### 前言 前面我們講了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源碼解析(1)-OkHttpClient分析](https://www.jianshu.com/p/bf1d01b79ce7) [Okhttp3源碼解析(2)-Request分析](https://www.jianshu.com/p/5a85345c8ea7) ### newCall分析 ##### Call初始化 我們首先看一下在哪用到了Call: ``` final Call call = okHttpClient.newCall(request); ``` 想起來了吧?無論是get還是post請求 都要生成call對象,在上面我們發現call實例需要一個`okHttpClient`與`request`實例 ,我們先點進Call類去看看: ``` public interface Call extends Cloneable { //請求 Request request(); //同步 Response execute() throws IOException; //非同步 void enqueue(Callback responseCallback); //取消請求 void cancel(); //是否在請求過程中 boolean isExecuted(); //是否取消 boolean isCanceled(); Call clone(); //工廠介面 interface Factory { Call newCall(Request request); } } ``` 我們發現Call是個介面, 並定義了一些方方法(方法含義在註釋上)。 我們繼續看`newCal()`方法 ``` @Override public Call newCall(Request request) { return RealCall.newRealCall(this, request, false /* for web socket */); } ``` 繼續點擊`newRealCall()`去: ``` private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { this.client = client; this.originalRequest = originalRequest; this.forWebSocket = forWebSocket; this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket); } static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) { // Safely publish the Call instance to the EventListener. RealCall call = new RealCall(client, originalRequest, forWebSocket); call.eventListener = client.eventListenerFactory().create(call); return call; } ``` 從代碼中我們發現在`newRealCall()`中初始化了`RealCall`,`RealCall`中初始化了`retryAndFollowUpInterceptor` : - client: OkHttpClient 實例 - originalRequest : 最初的Request - forWebSocket :是否支持websocket通信 - retryAndFollowUpInterceptor 從字面意思來說, 是重試和重定向攔截器 ,至於它有什麼作用我們繼續往下看 ### 同步請求分析 ``` Response response = call.execute(); ``` 我們點進`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); } } ``` 從上面代碼得知步驟: (1).通過 ` synchronized ` 保證線程同步,判斷是否已經執行過 ,如果是直接拋異常 (2). `captureCallStackTrace();` 字面意思:捕獲調用堆棧跟蹤,我們通過源碼發現裡面涉及到了`retryAndFollowUpInterceptor` (3). ` eventListener` 回調` CallStart()` (4). `client.dispatcher().executed(this);` 看到了`dispatcher`是不是很熟悉?之前在分析`okhttpClient`初始化的時候遇到了,我們點擊`executed()`方法進去: ``` synchronized void executed(RealCall call) { runningSyncCalls.add(call); } ``` 發現把我們傳進來的`realcall`放到了`runningSyncCalls`隊列中,從字面意思來說就是正在運行的同步的調用隊列中,為什麼說是隊列呢? : ``` private final Deque runningSyncCalls = new ArrayDeque<>(); ``` > Deque即雙端隊列。是一種具有隊列和棧的性質的數據結構。雙端隊列中的元素可以從兩端彈出,相比list增加[]運算符重載。 (5).我們回到`execute()`繼續往下分析,剩下的代碼我們提取出三行代碼: - `equesr result = getResponseWithInterceptorChain();` 生成一個Response 實例 - `eventListener.callFailed(this, e);` :eventListener的callFailed回調 - `client.dispatcher().finished(this);` :dispatcher實例的finished方法 不難看出,**`getResponseWithInterceptorChain()`**一定是此方法中的**核心**,字面意思是獲取攔截器鏈的響應,這就明白了,就是**通過攔截器鏈處理後返回Response** ![](https://img2018.cnblogs.com/blog/1312938/201908/1312938-20190823173540636-96122427.png) ###### getResponseWithInterceptorChain() 分析 ``` Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. 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, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); return chain.proceed(originalRequest); } ``` 從上面代碼不難看出, 對最初的request做了層層攔截,每個攔截器的原理我們放在以後的章節去講, 這裡就不展開了! 這裡需要強調的一下 ` interceptors.addAll(client.interceptors()); ` ,` client.interceptors()` 是我們自定義的攔截器 它是在哪定義的?如何添加?我們去OkHttpClient類中發現: ![](https://img2018.cnblogs.com/blog/1312938/201908/1312938-20190823173540985-1903691954.png) 可以通過初始化`okHttpClient`實例 ` .addInterceptor`的形式 添加。 ### 非同步請求分析 ``` call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d("okhttp_error",e.getMessage()); } @Override public void onResponse(Call call, Response response) throws IOException { Gson gson=new Gson(); Log.d("okhttp_success",response.body().string()); } }); ``` 點擊`enqueue()`查看: ``` @Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); client.dispatcher().enqueue(new AsyncCall(responseCallback)); } ``` (1).通過 ` synchronized ` 保證線程同步,判斷是否已經執行過 ,如果是直接拋異常 (2). `captureCallStackTrace();` 字面意思:捕獲調用堆棧跟蹤,我們通過源碼發現裡面涉及到了`retryAndFollowUpInterceptor` (3). ` eventListener` 回調` CallStart()` (4). `client.dispatcher().enqueue(new AsyncCall(responseCallback));` 調用了`Dispatcher.enqueue()`並傳入了一個**`new AsyncCall(responseCallback)`**實例,點擊**AsyncCall**查看: **AsyncCall 是RealCall的內部類!** ``` final class AsyncCall extends NamedRunnable { private final Callback responseCallback; AsyncCall(Callback responseCallback) { super("OkHttp %s", redactedUrl()); this.responseCallback = responseCallback; } String host() { return originalRequest.url().host(); } Request request() { return originalRequest; } RealCall get() { return RealCall.this; } @Override protected void execute() { boolean signalledCallback = false; try { Response response = getResponseWithInterceptorChain(); if (retryAndFollowUpInterceptor.isCanceled()) { signalledCallback = true; responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { signalledCallback = true; responseCallback.onResponse(RealCall.this, response); } } catch (IOException e) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); } else { eventListener.callFailed(RealCall.this, e); responseCallback.onFailure(RealCall.this, e); } } finally { client.dispatcher().finished(this); } } } ``` `AsyncCall`繼承了`NamedRunnable` ,我們看下`NamedRunnable`是什麼: ``` public abstract class NamedRunnable implements Runnable { protected final String name; public NamedRunnable(String format, Object... args) { this.name = Util.format(format, args); } @Override public final void run() { String oldName = Thread.currentThread().getName(); Thread.currentThread().setName(name); try { execute(); } finally { Thread.currentThread().setName(oldName); } } protected abstract void execute(); } ``` 原來`NamedRunnable` 實現了`Runnable` 介面 是個線程類,在`run()`中 添加了抽象的`execute();`方法,看到這裡 我們應該有一個反應,那就是**AsyncCall中具體的execute()應該在子線程執行** 我們繼續分析,`client.dispatcher().enqueue(new AsyncCall(responseCallback));` 點擊進入enqueue(): ``` synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } } ``` - `runningAsyncCalls` 正在運行的非同步請求的隊列 - `maxRequests` 最大的請求數 64 - `maxRequestsPerHost` host最大請求數 5 (可以通過Get與Set方式自定義設置) 如果正在運行的非同步請求的隊列大小低於64並且 正在請求的host數量低於5,就會執行(滿足條件) ``` runningAsyncCalls.add(call); executorService().execute(call); ``` 這裡把 `AsyncCall `實例添加到 `runningAsyncCalls `中。 `ExecutorService` 表示線程池 繼續看 `executorService()`: ``` public synchronized ExecutorService executorService() { if (executorService == null) { executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false)); } return executorService; } ``` 其實就是生成了executorService 實例,這就明白了,`AsyncCall `實例放入線程池中執行了! 如果不滿足上面的請求數等條件: ``` readyAsyncCalls.add(call); ``` 就會被添加到一個等待就緒的非同步請求隊列中,目的是什麼呢??? 當然是等待時機再次添加到runningAsyncCalls中並放入線程池中執行,這塊邏輯在 `AsyncCall `類中的 `execute() ` 至於原因我們繼續往下看! ![](https://img2018.cnblogs.com/blog/1312938/201908/1312938-20190823173541531-1963303270.png) 剛纔我們說了,如果條件滿足, `AsyncCall `實例就會線上程池中執行(.start),那我們直接去看run()中的 `execute() ` : ``` @Override protected void execute() { boolean signalledCallback = false; try { Response response = getResponseWithInterceptorChain(); if (retryAndFollowUpInterceptor.isCanceled()) { signalledCallback = true; responseCallback.onFailure(RealCall.this, new IOException("Canceled")); } else { signalledCallback = true; responseCallback.onResponse(RealCall.this, response); } } catch (IOException e) { if (signalledCallback) { // Do not signal the callback twice! Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e); } else { eventListener.callFailed(RealCall.this, e); responseCallback.onFailure(RealCall.this, e); } } finally { client.dispatcher().finished(this); } } ``` 上面代碼中得知, 首先通過層層攔截器鏈處理生成了`response`;然後通過一系列的判斷,`responseCallback`進行`onResponse`與`onFailure`回調,最後調用的`Dispatcher.finifshed()` 這裡需要註意的是 **這裡的`Dispatcher.finifshed(this)`與同步中的`Dispatcher.finifshed(this)`不一樣** 參數不同。 ``` /** Used by {@code AsyncCall#run} to signal completion. */ void finished(AsyncCall call) { finished(runningAsyncCalls, call, true); } ``` 我們繼續看具體的finifshed()方法: ``` private void finished(Deque calls, T call, boolean promoteCalls) { int runningCallsCount; Runnable idleCallback; synchronized (this) { if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!"); if (promoteCalls) promoteCalls(); runningCallsCount = runningCallsCount(); idleCallback = this.idleCallback; } if (runningCallsCount == 0 && idleCallback != null) { idleCallback.run(); } } ``` 線上程同步的情況下 執行了`promoteCalls();`: ``` private void promoteCalls() { if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCall call = i.next(); if (runningCallsForHost(call) < maxRequestsPerHost) { i.remove(); runningAsyncCalls.add(call); executorService().execute(call); } if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity. } } ``` 經過一系列的判斷, 對等待就緒的非同步隊列進行遍歷,生成對應的`AsyncCall `實例,並添加到runningAsyncCalls中,最後放入到線程池中執行! 這裡就是我們上面說到的等待就緒的非同步隊列如何與runningAsyncCalls對接的邏輯。 ### 總結 ##### 同步請求流程: - 生成`call`實例realcall - `Dispatcher.executed()`中的`runningSyncCalls` 添加realcall到此隊列中 - 通過 `getResponseWithInterceptorChain()` 對request層層攔截,生成Response - 通過`Dispatcher.finished()`,把call實例從隊列中移除,返回最終的response ##### 非同步請求流程: - 生成一個`AsyncCall(responseCallback)`實例(實現了Runnable) - `AsyncCall`實例放入了`Dispatcher.enqueue()`中,並判斷`maxRequests` (最大請求數)`maxRequestsPerHost`(最大host請求數)是否滿足條件,如果滿足就把`AsyncCall`添加到`runningAsyncCalls`中,並放入線程池中執行;如果條件不滿足,就添加到等待就緒的非同步隊列,當那些滿足的條件的執行時 ,在`Dispatcher.finifshed(this)`中的`promoteCalls();`方法中 對等待就緒的非同步隊列進行遍歷,生成對應的`AsyncCall `實例,並添加到`runningAsyncCalls`中,最後放入到線程池中執行,一直到所有請求都結束。 至此OKhttp整體流程就分析完了, 下一篇會分塊去分析,希望對大家有所幫助... ![](https://img2018.cnblogs.com/blog/1312938/201908/1312938-20190823173541905-1601958458.png) 大家可以關註我的微信公眾號:「秦子帥」一個有質量、有態度的公眾號! ![公眾號](https://img2018.cnblogs.com/blog/1312938/201908/1312938-20190823173542046-1151555749.jpg)
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 對於大數據集群來說,監控功能是非常必要的,通過日誌判斷故障低效,我們需要完整的指標來幫我們管理Kafka集群。本文討論Kafka的監控以及一些常用的第三方監控工具。 一、Kafka Monitoring 首先介紹kafka的監控原理,第三方工具也是通過這些來進行監控的,我們也可以自己去是實現監控,官 ...
  • 距離申請這個博客號已經過了九個月,思前想後還是把知識沉澱放這裡吧,不過初心一樣,依舊是 '謹以此文,見證成果'。有 興趣的話也歡迎大家去我的csdn博客轉一轉。以下是正文: 1.mysql安裝 windows系統下下載 phpstudy(一個集成環境),下載完成後一直按下一步即可,然後 打開phps ...
  • 語法:select 列名, length(列名) from 表名where length(列名) = ( select max(length(列名)) from 表名); 實例:select project_num, length(project_num) from project_infor_ta ...
  • 在mysql查詢中,我們會通過排序,分組等在一張表中讀取數據,這是比較簡單的,但是在真正的應用中經常需要從多個數據表中讀取數據。下麵就為大家介紹這種方式,鏈接查詢join。 INNER JOIN(內連接,或等值連接):獲取兩個表中欄位匹配關係的記錄。 LEFT JOIN(左連接):獲取左表所有記錄, ...
  • 修改表格中的列名稱 alter table \ change [column] \ \ \ 查詢數據表時獲取特定幾行的方式 若需要實現獲取數據表中前幾行或者後幾行的特定內容,需要藉助於limit。 limit子句可以被用於強制select語句返回指定的記錄數。limit 接受一個或兩個數字參數。參數 ...
  • 版權聲明:本文為xing_star原創文章,轉載請註明出處! 本文同步自http://javaexception.com/archives/188 Glide.3x的版本是3.7.0,Glide4.x的版本是4.2.0 Glide3.x中最基礎的用法 那麼在Glide4.x中,其實還是一樣的,最基本 ...
  • 入門學習Flutter有一段時間了,Demo用過的Widget也有不少,想著整體的梳理一下,所以今天結合Flutter中文網和書籍梳理一下Widget的使用,首先梳理一下擁有單個子元素的佈局Widget。 Container:一個擁有繪製、定位、調整大小的Widget Pad... ...
  • ### 前言 前面我們講了 [Okhttp的基本用法](https://www.jianshu.com/p/8e404d9c160f) [Okhttp3源碼解析(1)-OkHttpClient分析](https://www.jianshu.com/p/bf1d01b79ce7) 今天主要分析下Req ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...