一. okHttp簡介
okhttp已經成為Android開發中必不可少的網絡請求工具,無論在平時開發還是面試的過程中,都會有所涉及,弄清okhttp的使用流程,已經是每一個android開發者成為進階工程師的必經之路。其實其原理并不複雜,本文将分兩篇來着重介紹okhttp中分發器和攔截器原理。
二. 大綱
- okHttp的優點以及請求過程
- Dispatcher的分發流程
三. okHttp的優點以及請求過程
- 首先我們來看看使用okhttp都有哪些優點:
- 支援HTTP/2并允許對同一主機的所有請求共享一個套接字
- 通過連接配接池,複用socket,減少請求延遲
- 預設通過gzip壓縮資料
- 響應緩存,避免重複請求網絡
- 請求失敗自動重試主機其它ip,自動重定向
- okHttp的請求流程
使用okhttp的大緻流程為:
- 建立一個OkHttpClient
- 建立一個Request對象
- 建立一個call對象,接受request
- 進行請求執行任務
- 内部通過Dispatcher分發任務
- 五大預設攔截器完成整個請求過程
- 傳回結果
get請求流程:
//1.建立OkHttpClient對象
OkHttpClient okHttpClient = new OkHttpClient();
//2.建立Request對象,設定請求方式。
Request request = new Request.Builder()
.url("https://www.jianshu.com/u/f260c485f077")
.get()
.build();
//3.建立一個call對象
Call call = okHttpClient.newCall(request);
//4.同步請求
new Thread(new Runnable() {
@Override
public void run() {
Response response = call.execute();
}}).start();
//5.異步請求
call.enqueue(new Callback() {
//請求失敗執行的方法
@Override
public void onFailure(Call call, IOException e) {
String err = e.getMessage().toString();
}
//請求成功執行的方法
@Override
public void onResponse(Call call, Response response) throws IOException {
inal String rtn = response.body().string();
}
});
post請求流程:
//1.建立OkHttpClient對象,設定參數
OkHttpClient okHttpClient = new OkHttpClient();
FormBody.Builder mBuild = new FormBody.Builder();
mBuild.add("key1", "vaule1")
.add("key2", "vaule2");
RequestBody requestBodyPost = mBuild.build();
//2.建立Request對象,設定請求方式。
Request request = new Request.Builder()
.url("http://www.baidu.com")
.post(requestBodyPost)
.build();
//3.建立一個call對象
Call call = okHttpClient.newCall(request);
//4.請求回調方法
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
//請求成功執行的方法
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
四. Dispatcher的分發流程
okHttp中Dispatcher的分發流程大緻分為以下幾步:
- 調用execute或者enqueue加入任務
- Dispatcher根據條件判斷将目前任務加入隊列 runningAsyncCalls(執行隊列)還是readyAsyncCalls(等待隊列)
- runningAsyncCalls隊列調用線程池執行任務,
- runningAsyncCalls執行任務完成,根據條件擷取readyAsyncCalls中任務執行
根據okhttp中Dispatcher的分發流程,我們有幾個問題需要弄清楚:
Q: Dispatcher将請求分發到隊列過程中,如何決定放入ready還是running?
Q: 從ready移動running的條件是什麼?(如何移動,從哪裡移動)
Q: Dispatcher分發器線程池的工作行為是怎樣的。
帶着以上三個問題,我們可以看看源碼是如何實作的。
第一個問題 Dispatcher将請求分發到隊列過程中,如何決定放入ready還是running
當我們要執行一個異步請求的時候,會調用okhttp的enqueue方法,其内部就會調用分發器的enqueue方法,我們直接來看分發器的enqueue方法做了什麼:
okhttp的enqueue方法:
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
// client就是OkHttpClient 通過client擷取Dispatcher 調用Dispatcher的enqueue
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
Dispatcher的enqueue方法:
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
synchronized void enqueue(AsyncCall call) {
//runningAsyncCalls.size() < maxRequests(最大正在請求的數量)
//runningCallsForHost(call) < maxRequestsPerHost(同一域名最大正在請求的個數限制)
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
看到這裡相信大家都能明白第一個問題,由注解可以看到,Dispatcher将任務加入隊列的兩個條件,目前最大請求數不超過64,同一域名最大正在請求的個數限制不超過5,同時滿足這兩個的情況下,想請求加入執行隊列runningAsyncCalls中,并立即執行。不滿足條件加入等待隊列readyAsyncCalls中。
第二個問題 從ready移動running的條件是什麼?(如何移動,從哪裡移動)
然後我們再回過頭來看看AsyncCall是個什麼,我們的請求加入這個AsyncCall以後都做了什麼,打開AsyncCal源碼
首先可以看到RealCall implements Call ,當我們調用call.enqueue時,也就是再調用RealCall.enqueue,那麼我們隻要看RealCall.enqueue中做了什麼即可。
@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 {
//請求執行完畢,調用finish();
client.dispatcher().finished(this);
}
}
如何把ready移動running,那麼肯定是running中任務已經執行完成,是以我們繼續看finnally中 client.dispatcher().finished(this);做了什麼。
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();
}
}
private void promoteCalls() {
//判斷正在執行的隊列個數,不滿足傳回
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
//判斷等待隊列是否有請求,不滿足傳回
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
//循環擷取等待隊列中的請求
for (Iterator<RealCall.AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
RealCall.AsyncCall call = i.next();
//判斷目前call的host執行個數是否小于maxRequestsPerHost
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
//将等待隊列中的call加入到runningAsyncCalls
runningAsyncCalls.add(call);
//開始執行任務
executorService().execute(call);
}
//判斷正在執行的隊列個數,滿足就returen,不在繼續增加
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
以上代碼邏輯,注釋的很清晰。會通過相同的條件,将等待隊列中的請求加入到執行隊列
最後一個問題,分發器線程池的工作行為是怎樣的。
看到這裡,相信大家已經對okhtt的分發流程了解個大概,此時我們已經将請求加入到了隊列中,而在分發執行請求的過程中,okhttp是怎麼來執行這些任務的呢,還是繼續來看源碼,通過以上源碼的分析我們可以看到Dispatcher是通過executorService().execute(call)方法執行任務,是以我們直接看executorService()方法的源碼
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
熟悉線程池的同學,看到這裡估計已經明白了,okhttp内部幫我們建立了一個線程池,通過線程池來執行我們的請求,這裡我們主要介紹一下,線程池中的幾個參數
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
corePoolSize = 0 okhttp預設不幫我們緩存空閑線程個數,隻有當我們真正需要請求的時候才會幫我們建立線程
maximumPoolSize = Integer.MAX_VALUE 最大值保證了我們任務的最大并發數
keepAliveTime = 60 任務閑時最多存活時間
TimeUnit.SECONDS
new SynchronousQueue() 阻塞隊列,用于工作線程都被占用情況下,儲存新的任務請求
Util.threadFactory(“OkHttp Dispatcher”, false) 線程工廠
這裡我們隻需要這幾個問題便可
- corePoolSize 為何設定為0
- maximumPoolSize 為何設定為最大值
- 阻塞隊列為何隻能使用SynchronousQueue,不能用ArrayBlockingQueue或者LinkedBlockingQueue。
當一個請求過來,線程池内部首先判斷corePoolSize 大小為0,是以加入SynchronousQueue,而SynchronousQueue是一個無容量隊列,再來判斷maximumPoolSize 值,是一個Int最大值,是以任務被立馬建立執行。之後來的每個請求都是這樣被建立,保障了最大并發。
再來看看ArrayBlockingQueue,我們給定ArrayBlockingQueue中容量大小為1,假設此時有兩個請求A,B。A請求過來,被加入ArrayBlockingQueue中,因為線程池内部判斷,目前正在執行線程個數為0是,會建立線程,是以A不會加入ArrayBlockingQueue中,而是直接被建立。這時B請求再過來時,我們假設A是個耗時操作,而這時線程池中無空閑線程,B被加入ArrayBlockingQueue,且處于無限等待狀态。
LinkedBlockingQueue的邏輯和ArrayBlockingQueue差不多,隻不過ArrayBlockingQueue容量可以無限大。具體邏輯這裡不在分析。
分發器的流程以及原理就到這裡,okhttp每個版本代碼可能會略有不同,本文基于okhttp3.6.0源碼進行分析。下一篇我們将介紹okhttp中的攔截器是如何實作的呢。