簡(jiǎn)介
OkHttp是當(dāng)下Android使用最頻繁的網(wǎng)絡(luò)請(qǐng)求框架闽巩,由Square公司開源品山。Google在Android4.4以后開始將源碼中的HttpURLConnection底層實(shí)現(xiàn)替換為OKHttp,同時(shí)現(xiàn)在流行的Retrofit框架底層同樣是使用OKHttp的黍析。
優(yōu)點(diǎn):
- 支持Spdy、Http1.X苫纤、Http2互订、Quic以及WebSocket
- 連接池復(fù)用底層TCP(Socket)吱肌,減少請(qǐng)求延時(shí)
- 無縫的支持GZIP減少數(shù)據(jù)流量
- 緩存響應(yīng)數(shù)據(jù)減少重復(fù)的網(wǎng)絡(luò)請(qǐng)求
- 請(qǐng)求失敗自動(dòng)重試主機(jī)的其他ip,自動(dòng)重定向
…….
tip:本文版本為OkHttp 3.10.0仰禽,最新版本為:4.0.1氮墨,邏輯與3版本并沒有太大變化,但是改為kotlin實(shí)現(xiàn)吐葵。
OkHttpClient client = new OkHttpClient();
void syncGet(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
// 執(zhí)行同步請(qǐng)求
Call call = client.newCall(request);
Response response = call.execute();
// 獲得響應(yīng)
ResponseBody body = response.body();
System.out.println(body.string());
}
void AsyncGet(String url) {
Request request = new Request.Builder()
.url(url)
.build();
// 執(zhí)行異步請(qǐng)求
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {}
@Override
public void onResponse(Call call, Response response) throws IOException {
// 獲得響應(yīng)
ResponseBody body = response.body();
System.out.println(body.string());
}
});
}
使用起來很簡(jiǎn)單规揪,我們用一張流程圖描述一下
在使用Okhttp發(fā)送一次請(qǐng)求時(shí),對(duì)于使用者最少要用到OkHttpClient温峭、Request和Call這3個(gè)對(duì)象猛铅;
OkHttpClient 中是一些配置信息,比如代理的配置诚镰,ssl證書配置等等奕坟;
Request 是封裝請(qǐng)求參數(shù)信息,比如請(qǐng)求地址清笨、請(qǐng)求方法、請(qǐng)求頭刃跛、請(qǐng)求體等等抠艾;
Call 本身是一個(gè)接口,實(shí)現(xiàn)類為RealCall桨昙,execute()是負(fù)責(zé)同步請(qǐng)求检号,enqueue()是負(fù)責(zé)異步請(qǐng)求,兩者唯一區(qū)別在于一個(gè)會(huì)直接發(fā)起網(wǎng)絡(luò)請(qǐng)求蛙酪,而另一個(gè)使用OkHttp內(nèi)置的線程池來進(jìn)行齐苛。
請(qǐng)求任務(wù)是如何分配的呢?這就涉及到OkHttp的任務(wù)分發(fā)器桂塞。
Dispatcher 分發(fā)器
分發(fā)器就是來調(diào)配請(qǐng)求任務(wù)的凹蜂,內(nèi)部會(huì)包含一個(gè)線程池以及任務(wù)隊(duì)列。在創(chuàng)建OkHttpClient時(shí)阁危,我們也可以傳遞自己定義的線程池來創(chuàng)建分發(fā)器玛痊。我看一下Dispatcher 的成員屬性:
public final class Dispatcher {
// 異步請(qǐng)求最大同時(shí)請(qǐng)求數(shù)量
private int maxRequests = 64;
// 異步請(qǐng)求同一域名同時(shí)存在的最大請(qǐng)求數(shù)量
private int maxRequestsPerHost = 5;
// 閑置任務(wù)(沒有請(qǐng)求時(shí)可執(zhí)行一些任務(wù),由使用者設(shè)置)
private @Nullable
Runnable idleCallback;
//異步請(qǐng)求使用的線程池
private @Nullable
ExecutorService executorService;
//異步請(qǐng)求等待執(zhí)行隊(duì)列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//異步請(qǐng)求正在執(zhí)行隊(duì)列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//同步請(qǐng)求正在執(zhí)行隊(duì)列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
// 上面說的可配置線程池的構(gòu)造函數(shù)
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
public Dispatcher() {}
// 創(chuàng)建線程池(懶加載)
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;
}
}
execute() 同步請(qǐng)求
這是我們來看一下我們使用的時(shí)候調(diào)用Call 對(duì)象的同步方法execute()做了什么(實(shí)際上調(diào)用的是Call的實(shí)現(xiàn)類RealCall的execute方法):
@Override
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
// 我們先看這里狂打,調(diào)用了dispatcher的executed方法
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 {
// 任務(wù)結(jié)束或者出現(xiàn)異常擂煞,將任務(wù)標(biāo)記為結(jié)束
client.dispatcher().finished(this);
}
}
很明顯調(diào)用了dispatcher的executed()方法,ok再跟到dispatcher中:
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
就一句話趴乡,將call添加到正在運(yùn)行的同步隊(duì)列中对省,表示當(dāng)前的請(qǐng)求任務(wù)已經(jīng)開始執(zhí)行了蝗拿,等到請(qǐng)求完成再把它從隊(duì)列中移除 (dispatcher中的finished方法)。
enqueue() 異步請(qǐng)求
再看一下RealCall的enqueue方法
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
// 調(diào)用Dispatcher中的enqueue方法
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
同樣的調(diào)用了Dispatcher中的enqueue方法蒿涎,看一眼:
synchronized void enqueue(AsyncCall call) {
// 如果同時(shí)執(zhí)行的請(qǐng)求數(shù)量不超過64蛹磺,同時(shí)同一域名主機(jī)的請(qǐng)求數(shù)量不超過5個(gè)
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
// 滿足條件將請(qǐng)求加入正在運(yùn)行的隊(duì)列中,并且開始執(zhí)行
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
// 不滿足條件將請(qǐng)求加入等待隊(duì)列
readyAsyncCalls.add(call);
}
}
上面的描述已經(jīng)很詳細(xì)了同仆,那么等待隊(duì)列的請(qǐng)求啥時(shí)候才能被臨幸呢萤捆?有沒有注意到
Dispatcher的enqueue方法的參數(shù)是AsyncCall,其實(shí)他就是一個(gè)Runnable俗批。
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
.....
@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 {
// 當(dāng)請(qǐng)求執(zhí)行完成調(diào)用了Dispatcher的finished方法
client.dispatcher().finished(this);
}
}
}
和同步請(qǐng)求一樣俗或,當(dāng)一個(gè)異步請(qǐng)求結(jié)束也調(diào)用了Dispatcher的finished方法,看一眼:
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
調(diào)用重載的finished:
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
// 先把當(dāng)前的請(qǐng)求移出正在執(zhí)行隊(duì)列
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
// promoteCalls這里是true岁忘, 執(zhí)行promoteCalls()
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
先把當(dāng)前的請(qǐng)求移出正在執(zhí)行隊(duì)列辛慰,然后執(zhí)行了promoteCalls(),八成我們想要的就在這里了:
private void promoteCalls() {
// 如果同時(shí)執(zhí)行的請(qǐng)求數(shù)量不超過64個(gè)干像,直接return
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
// 如果等待隊(duì)列是空的帅腌,return!
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
// 遍歷等待隊(duì)列
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
// 檢查一下正在執(zhí)行的同一host的請(qǐng)求數(shù)量是不是不滿5個(gè)
if (runningCallsForHost(call) < maxRequestsPerHost) {
// 滿足條件麻汰,移出等待隊(duì)列速客,加入正在執(zhí)行隊(duì)列,直接執(zhí)行請(qǐng)求任務(wù)五鲫!
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
// 正在執(zhí)行隊(duì)列滿了溺职,return 吧!
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
這里就是我們要找的位喂,當(dāng)一個(gè)異步任務(wù)執(zhí)行完成時(shí)浪耘,檢查正在執(zhí)行隊(duì)列有沒有空位置,遍歷等待隊(duì)列塑崖,把其中滿足條件的請(qǐng)求任務(wù)拿出來七冲,放到正在執(zhí)行隊(duì)列中去執(zhí)行,執(zhí)行完了再次檢查规婆,如此循環(huán)澜躺。
Dispatcher中的線程池
Dispatcher中的線程池很有意思,我們來看一下
public synchronized ExecutorService executorService() {
if (executorService == null) {
// 核心線程數(shù)0聋呢,最大線程數(shù)Integer.MAX_VALUE苗踪,空閑線程閑置時(shí)間 60,
// 閑置時(shí)間單位 秒削锰,線程等待隊(duì)列 SynchronousQueue通铲, 以及線程創(chuàng)建工廠
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher",
false));
}
return executorService;
}
這是Dispatcher中默認(rèn)創(chuàng)建的線程池,注意里面幾個(gè)關(guān)鍵的參數(shù)器贩,第一個(gè)參數(shù)核心線程數(shù)量0颅夺,最大線程數(shù) Integer.MAX_VALUE 和隊(duì)列 SynchronousQueue朋截。
為啥要這么配置呢?
首先核心線程為0吧黄,表示線程池就不用一直為我們緩存線程部服,線程池中所有線程都是在60s內(nèi)沒有工作就會(huì)被回收,在一些網(wǎng)絡(luò)請(qǐng)求不頻繁的app上可以節(jié)約一些內(nèi)存拗慨。而最大線程Integer.MAX_VALUE與等待隊(duì)列SynchronousQueue的組合能夠得到最大的吞吐量廓八,即當(dāng)需要線程池執(zhí)行任務(wù)時(shí),如果不存在空閑線程不需要等待赵抢,馬上新建線程執(zhí)行任務(wù)剧蹂!來分析一下:
SynchronousQueue 是一種無容量的隊(duì)列,當(dāng)向線程池中加入新請(qǐng)求時(shí)烦却,只會(huì)有以下幾種情況:
- 由于沒有核心線程宠叼,第一次進(jìn)來的請(qǐng)求會(huì)直接創(chuàng)建新線程執(zhí)行。
- 此時(shí)有未被回收的空閑線程其爵,復(fù)用該線程執(zhí)行請(qǐng)求冒冬。
- 沒有空閑線程了,新請(qǐng)求需要添加到隊(duì)列摩渺,但是隊(duì)列的容量為0简烤,相當(dāng)于滿了,此時(shí)要判斷當(dāng)前正在執(zhí)行的線程數(shù)是否大于最大線程數(shù)证逻,由于最大線程數(shù)為Integer.MAX_VALUE乐埠,肯定不會(huì)超過,所以要新建一個(gè)線程執(zhí)行請(qǐng)求囚企。
可以得出結(jié)論,我們的請(qǐng)求任務(wù)是可以立即執(zhí)行的瑞眼,其實(shí)還有另外2種阻塞隊(duì)列:ArrayBlockingQueue 和 LinkedBlockingQueue龙宏,但是它們一般都有容量,當(dāng)請(qǐng)求任務(wù)進(jìn)入等待隊(duì)列后伤疙,可能會(huì)出現(xiàn)以下幾種情況:
- 沒有空閑線程了银酗,新請(qǐng)求添加到隊(duì)列,隊(duì)列中的請(qǐng)求一直在等待空閑線程徒像,出現(xiàn)阻塞黍特。
- 沒有空閑線程了,等待隊(duì)列也滿了锯蛀,正在執(zhí)行的線程數(shù)小于最大線程數(shù)灭衷,新建線程執(zhí)行請(qǐng)求,出現(xiàn)后提交的任務(wù)先執(zhí)行旁涤,而先提交的任務(wù)一直在等待的情況翔曲,同樣阻塞了迫像。
- 沒有空閑線程了,等待隊(duì)列也滿了瞳遍,正在執(zhí)行的線程數(shù)大于等于最大線程數(shù)闻妓,請(qǐng)求被拒絕了,尷尬掠械。
總結(jié)一句話由缆,Dispatcher的默認(rèn)線程池參數(shù)配置保證了新建的請(qǐng)求都可以被立即執(zhí)行,避免阻塞猾蒂。
當(dāng)然需要注意的時(shí)均唉,進(jìn)程的內(nèi)存是存在限制的,而每一個(gè)線程都需要分配一定的內(nèi)存婚夫。所以線程并不能無限個(gè)數(shù)浸卦。那么當(dāng)設(shè)置最大線程數(shù)為Integer.MAX_VALUE時(shí),OkHttp同時(shí)還有最大請(qǐng)求任務(wù)執(zhí)行個(gè)數(shù): 64的限制案糙,這樣既解決了這個(gè)問題同時(shí)也能獲得最大吞吐限嫌。