Okhttp的源碼分析
Okhttp的線程池和高并發(fā)
Okhttp鏈接池的使用
Okhttp的緩存機(jī)制
Okhttp的責(zé)任鏈模式
Okhttp的框架使用
建議安裝目錄插件食用
在實際項目中使用okhttp的時候(異步)禁荸,通常遵循以下步驟:
-
創(chuàng)建okhttp實例用于代理(OkhttpClient 的對象擁有自己的線程池和鏈接池右蒲,所以可以將其封裝成單例模式)
OkhttpClient client = new OkhttpClient();
-
創(chuàng)建用于存放提交參數(shù)的RequestBody對象
MediaType mediaType = MediaType.parse("application/json"); RequestBody body = RequestBody.create(mediaType, RequestBodyHelper.getUserStarredRepoListInActivity(login)); //mediaType 是傳遞的數(shù)據(jù)的MIME(媒體類型:json\xml\png\jpg\gif等) // 第二個參數(shù)為所需獲得的數(shù)據(jù)
-
創(chuàng)建用于發(fā)送請求的Request對象
Request request = builder.method("POST", body).build();
-
發(fā)送請求并獲取服務(wù)器返回的數(shù)據(jù),執(zhí)行call的enqueue()(異步方式,實際使用)或者execute()(同步赶熟,第一行代碼中的例子)
client .newCall(request).enqueue(callback) //okttp 的操作元是Call 對象瑰妄。
線程池的使用
源碼位于Dispatcher.java(依賴用的是3.8.1版本,Kotlin工作量有點大映砖。间坐。。)
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
調(diào)度器 dispatcher 管理了三個隊列:同步運行隊列,異步運行隊列竹宋、異步等待隊列劳澄。同步暫時不做分析(加載耗時太長,用不到)異步如圖所示蜈七,注Call本質(zhì)是一個Runnable秒拔。
Runnable與Thread的區(qū)別
Thread類是在java.lang包中定義的。一個類只要繼承了Thread類同時覆寫了本類中的run()方法就可以實現(xiàn)多線程操作了宪潮,但是一個類只能繼承一個父類
在實際開發(fā)中一個多線程的操作很少使用Thread類溯警,而是通過Runnable接口完成。但是在使用Runnable定義的子類中沒有start()方法狡相,只有Thread類中才有梯轻。此時觀察Thread類,有一個構(gòu)造方法:public Thread(Runnable targer)此構(gòu)造方法接受Runnable的子類實例尽棕,也就是說可以通過Thread類來啟動Runnable實現(xiàn)的多線程喳挑。
異步請求最大同時請求數(shù)量
private int maxRequests = 64;
異步請求同一域名同時存在的最大請求數(shù)量
private int maxRequestsPerHost = 5;
執(zhí)行過程
-
用戶調(diào)用最后一步為enqueue,
- 執(zhí)行隊列里面不足最大線程數(shù)maxRequests 并且Call 對應(yīng)的host 數(shù)目不超過maxRequestsPerHost 的時候直接把call 對象直接推入到執(zhí)行隊列
- 否則,當(dāng)前線程數(shù)過多滔悉,就把 他推入到等待隊列中伊诵。
synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } }
-
將等待隊列的線程調(diào)入到運行隊列
可以看到在線程的執(zhí)行的時候最后都調(diào)用了, client.dispatcher().finished(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 { responseCallback.onFailure(RealCall.this, e); } } finally { client.dispatcher().finished(this); } }
進(jìn)入finish方法的底層,可以看到call 在runningAsyncCalls 隊列中被移除了(calls.remove(call)回官,并重新計算了目前正在執(zhí)行 線程數(shù)量曹宴,如果為零執(zhí)行idle。此外最重要的是通過promoteCalls進(jìn)行任務(wù)隊列的調(diào)度歉提。
- 這里注意到remove和promotcalls的調(diào)用都加入了synchronized 同步鎖(其他試圖訪問該代碼塊的線程會被阻塞)笛坦,原因在步驟3中解釋。
private <T> void finished(Deque<T> 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任務(wù)隊列調(diào)度:
經(jīng)過簡單的判斷后苔巨,首先remove方法版扩,將線程移出等待隊列,再通過add加入運行隊列侄泽。
- 最開始定義過runningAsyncCalls和readyAsyncCalls為雙端隊列礁芦,其在插入和刪除的時候是非線程安全的,因此在調(diào)用他的時候(步驟2)加入了同步鎖
- 通過循環(huán)也可以知道悼尾,每次任務(wù)調(diào)度的時候迭代器試圖要遍歷整個隊列柿扣,直到運行隊列滿時才return。這樣做要比來回傳遞計數(shù)變量(運行隊列還能添加幾個進(jìn)程)來的簡單安全诀豁。
private void promoteCalls() { if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote. for (Iterator<AsyncCall> 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. } }
執(zhí)行過程的總結(jié)
用戶在調(diào)用client .newCall(request).enqueue(callback)的時候窄刘,調(diào)度器dispatcher首先檢查運行隊列是否滿,若滿則將其調(diào)度到等待隊列舷胜,每一個執(zhí)行的線程(call)在execute的結(jié)束時都會調(diào)用promotcalls,從而使得等待進(jìn)程進(jìn)入運行隊列。
整體流程如下圖所示