OkHttp同步請求步驟:
- 創建OkHttpClient,客戶對象
- 創建Request,請求主體,在請求主體設置請求的url,超時時間等
- 用newCall(request)將Reuqest對象封裝成Call對象,然後用Call對象的execute()發起同步請求。
- execute()返回的是Response對象。可以用execute().body().toString()得到請求所返回的主體內容。
val client = OkHttpClient()
val request = Request.Builder()
.url("https://www.baidu.com")
.build()
val response = client.newCall(request).execute().body().toString()
注意:發送請求後,就會進入阻塞狀態,直到收到響應。
OkHttp異步請求步驟:
- 創建OkHttpClient,客戶對象
- 創建Request,請求主體,在請求主體設置請求的url,超時時間等
- 用newCall(request)將Reuqest對象封裝成Call對象,然後用Call對象的enqueue()發起異步請求。
- enqueue(object: Callback{重寫onFailure、onResponse方法}) 在onResponse方法中獲取申請數據內容。
val client = OkHttpClient()
val request = Request.Builder()
.url("https://www.baidu.com")
.build()
val response = client.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
TODO("Not yet implemented")
}
override fun onResponse(call: Call, response: Response) {
response.body().toString()
}
})
源碼分析:
注意:下面的源代碼段可能來自不同一個類文件,只是將他們放一起,容易觀察,主要放一些關鍵代碼,其他會有...代替。
1.關於創建OkHttpClient對象,下面源碼:
public OkHttpClient() {
this(new Builder());
}
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
......
connectionPool = new ConnectionPool();
.....
}
可以看到OkHttp採用了建造者模式,在Builder()裏面封裝各種需要的屬性,關鍵的主要有dispatcher分發器,connectionSpecs決定是異步還是同步,connectionPool 連接池。連接池具體到連接攔截器纔會使用到,每個連接都會放入連接池中,由它進行管理。
總結:新建Client對象時,新建了一個分發器和一個連接池,還有一些屬性的初始化。
2.創建Request對象時,源碼:
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tags = Util.immutableMap(builder.tags);
}
可以看到Request裏面也有一個Builder類,Builder構造函數默認請求方式爲Get,還有對請求頭部的封裝。
總結:新建一個Request對象裏面主要封裝了請求路徑,頭部信息等。
3.用newCall(request)將Reuqest對象封裝成Call對象時,源碼:
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
//可以看到newCall方法裏面是調用了RealCall類的newRealCall方法,下面到RealCall類裏看看。
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// 調用RealCall的構造函數
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
//下面是RealCall類構造函數
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
......
}
總結:newCall方法實際生成RealCall對象,對象裏面包含了Client客戶對象和Request的請求對象,還新建了一個RetryAndFollowUpInterceptor 重定向攔截器。
4.Call對象調用的execute()同步請求方法,源碼:
@Override public Response execute() throws IOException {
.....
//開啓事件監聽
eventListener.callStart(this);
try {
//分發器用executed方法將Call對象添加進同步運行隊列
client.dispatcher().executed(this);
//結果是從攔截器鏈方法中獲取的
Response result = getResponseWithInterceptorChain();
......
} finally {
//finish方法裏將Call對象從Calls隊列中移出
client.dispatcher().finished(this);
}
}
//下面進到client.dispatcher().executed(this)的excuted方法裏面
synchronized void executed(RealCall call) {
//runningSyncCalls是正在運行的同步隊列
runningSyncCalls.add(call);
}
總結:excute()同步申請方法,分發器將Call對象添加到同步運行隊列。請求數據從Response result = getResponseWithInterceptorChain(); 中獲取。
5.enqueue異步請求方法,源碼:
@Override public void enqueue(Callback responseCallback) {
//判斷是否請求過這個Call對象
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
//異常檢測
captureCallStackTrace();
//事件監聽
eventListener.callStart(this);
//調用分發器的enqueue方法,分發器在client創建時新建的。
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
可以看到enqueue方法裏面又調用了分發器的enqueue方法,在enqueue方法裏新建了一個AsyncCall對象,
AsyncCall對象傳入我們上一層傳入enqueue方法的CallBack對象。
接下來看看上面的AsyncCall類是什麼東西。
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
...........
}
看到AsyncCall繼承自NamedRunnable,再來看看NamedRunnable是什麼東西
public abstract class NamedRunnable implements Runnable {
.....
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
.....
}
可以看到NamedRunnable實現了Runnable接口,裏面最核心的就是在run方法裏面運行了execute()方法,這個方法的具體實現其實跟同步請求execute方法一樣,在AsyncCall類裏,和同步請求最後的execute()是同一個方法。
@Override protected void execute() {
.......
Response response = getResponseWithInterceptorChain();
.....
}
我把大部分代碼都省了,最重要的就上面那句,跟同步請求一樣,最後結果也是經過一系列攔截器的方法後的數據。
那麼同步跟異步有什麼區別呢?
異步傳入enqueue方法的CallBack的對象實現了Runnable接口,讓它在子線程中運行。
還有,接下來回到開頭看看client.dispatcher().enqueue(new AsyncCall(responseCallback));這句,分發器類裏的變量和它的enqueue方法(剛剛看的是AsyncCall類)。
public final class Dispatcher {
//默認的最大併發請求量
private int maxRequests = 64;
//單個host支持的最大併發量
private int maxRequestsPerHost = 5;
.........
//異步等待隊列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//異步運行隊列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//同步運行隊列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
...........
//計算隊列內請求數量的方法,如果異步請求滿足不超過64,5的條件則進行請求操作。
//有的版本OkHttp是通過promoteAndExecute()進行條件判斷,原理差不多
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
//把Call對象添加進runningAsyncCalls異步進行隊列
runningAsyncCalls.add(call);
//創建線程池並執行Call請求
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
......
}
分發器對Request類型進行判斷,把Call對象添加進readyAsyncCalls異步等待隊列或runningAsyncCalls,而在同步請求時分發器是把Call對象直接添加到runningSyncCalls同步運行隊列。異步請求最後開啓線程池獲取數據。
總結:enqueue方法傳入CallBack對象,CallBack對象被封裝爲AsyncCall,AsyncCall內部實現了Runnable接口,分發器進行判斷,如果符合條件就把AsyncCall傳入了異步進行對列,開啓線程池在子線程獲取數據。否則添加進異步等待隊列。
readyAsyncCalls異步等待隊列的請求什麼時候能運行呢?
我們已經知道異步跟同步請求通過分發器分發隊列,但是最後都要經過AsyncCall類的execute()方法來獲取數據,execute()方法最後finally裏面運行client.dispatcher().finished(this);方法,我們進去finished方法看看。
//異步請求的finished方法
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
//同步請求的finished方法
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
//具體的finished方法
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
//將實現的Call對象從隊列中移出
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
//注意promoteCalls是第三個參數,既如果是異步請求才會運行該方法,重新整理隊列。
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.
//循環到隊列最後一個元素,call爲最後一個請求
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
//如果符合條件就把call從等待隊列移除加入運行隊列。
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
總結:在readyAsyncCalls隊列中的請求會在異步請求的finished方法裏進行判斷,如果符合條件則進入runningAsyncCalls。
Dispatcher分發器:
從上面的Dispatcher類可以看出分發器有3個隊列,異步有兩個隊列是運用了消費者模式,類中還有excuted,enqueue,finished方法,Dispatcher主要作用就是根據Request類型將Call對象調入不同隊列,最後用finished將完成的請求移除隊列並把等待的請求調進運行隊列。
ExecutorService線程池:
下面進到executorService().execute(call)看看,
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;
}
第一個參數代表核心線程數量,爲0就代表線程空閒之後不會被保留,會被銷燬;如果大於0,即使本地任務執行完畢,核心線程也不會被銷燬。
第二個參數是int整數的最大值,他表示的是線程池中可以容納的最大線程數量。但是確實它得滿足64跟5的條件。
第三個keepAliveTime,當我們的線程池中線程數量大於核心線程數量時,空閒線程需要等待60秒的時間纔會被終止。
OkHttp攔截器
我們已經知道了請求最後都是從Response result = getResponseWithInterceptorChain()這句中獲取的數據。
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> 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);
}
總歸創建了6個攔截器
全部攔截器的基本流程:
- 在發起請求前對request進行處理。
- 調用chain.proceed()方法,獲取下一個攔截器的response。
- 對reponse進行處理,返回給上一個攔截器。
RetryAndFollowUpInterceptor 重定向攔截器
負責失敗重連的攔截器。
- 創建StreamAllocation對象,但是沒有使用,它的使用是在ConnectInterceptor。它負責爲一次“請求”尋找“連接”並建立“流”。Connection是建立在Socket之上的物理通信信道,而Stream則是代表邏輯的流,如果有多個stream(即多個 Request) 都是連接在一個 host 和 port上,那麼它們就可以共同使用同一個 socket ,這樣做的好處就是可以減少TCP的一個三次握手的時間。
- 調用RealInterceptorChain.proceed(...)進行網絡請求
- 根據異常結果或響應結果判斷是否進行重新請求(20次)
- 調用下一個攔截器,對response進行處理,返回上一個攔截器
BridgeInterceptor 橋攔截器
該攔截器是鏈接客戶端代碼和網絡代碼的橋樑,它首先將客戶端構建的Request對象信息構建成真正的網絡請求;然後發起網絡請求,最後就是講服務器返回的消息封裝成一個Response對象。
- 將用戶構建的Request請求轉化爲能夠進行網絡訪問的請求
- 執行符合條件的請求
- 將Response轉化爲用戶可用的Response,OkHttp支持Gzip壓縮,GzipSource類對數據進行解壓。
CacheInterceptor 緩存攔截器
該攔截器用於處理緩存的功能,主要取得緩存 response 返回並刷新緩存。
- 底層使用的是 DiskLruCache 緩存機制。
- CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
- get() 方法獲取一個 CacheStrategy 對象。CacheStrategy,它是一個策略器,負責判斷是使用緩存還是請求網絡獲取新的數據。
- responseCache.put(userResponse);
- put(userResponse)方法將 userResponse 緩存到本地。
爲什麼需要緩存 Response?
- 客戶端緩存就是爲了下次請求時節省請求時間,可以更快的展示數據。
- OKHTTP 支持緩存的功能
ConnectInterceptor 連接攔截器
該攔截器的功能就是負責與服務器建立 Socket 連接,並且創建了一個 HttpCodec它包括通向服務器的輸入流和輸出流。
- 獲取到第一個攔截器生成的StreamAllocation對象,
- 通過StreamAllocation對象,streamAllocation.newStream()創建HttpCodec對象,
- streamAllocation.connection()獲取一個RealConnection對象
- 將HttpCodec、RealConnection對象傳遞給攔截器
NetworkInterceptors 網絡攔截器
- 允許像重定向和重試一樣操作中間響應。
- 網絡發生短路時不調用緩存響應。
- 在數據被傳遞到網絡時觀察數據。
- 有權獲得裝載請求的連接。
CallServerInterceptor 調用服務攔截器
該攔截器的功能使用 HttpCodec與服務器進行數據的讀寫操作的。
- 首先是獲取了httpCodec對象,該對象的主要功能就是對不同http協議(http1.1和http/2)的請求和響應做處理,該對象的初始化是在ConnectIntercepor的intercept裏面
- OkHttp通過OKIO的Sink對象(該對象可以看做Socket的OutputStream對象)的writeRequest來向服務器發送請求的。
- 將OKIO的Source對象作爲輸入流InputStream對象讀取數據封裝爲Response對象。
- 100-continue用於客戶端在發送POST數據給服務器前,徵詢服務器情況,看服務器是否處理POST的數據,如果不處理,客戶端則不上傳POST數據,如果處理,則POST上傳數據。
ConnectionPool
OkHttp中所有的連接(RealConnection)都是通過ConnectionPool來管理。
- StreamAllocation裏面包含了RealConnection對象,該對象歸根是由ConnectionPool的get() 方法遍歷 connections 中的所有 RealConnection 尋找同時滿足條件的RealConnection,重複利用RealConnection。
- ConnectionPool類裏put方法,採用GC回收算法,異步觸發清理任務,然後將健康的connection添加到connections隊列中。調用cleanup方法執行清理,並等待一段時間,持續清理,其中cleanup方法返回的值來來決定而等待的時間長度。