okhttp簡單使用
- 創(chuàng)建Request
- 創(chuàng)建Call胧华,將Request添加到Call中
- 使用異步enqueue驻谆,或者同步的execute方法獲得結(jié)果
okhttp網(wǎng)絡請求過程分析
Call
同步請求
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain(false);
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
首先加鎖置標志位氢橙,接著使用分配器的executed方法將call加入到同步隊列中潜腻,然后調(diào)用getResponseWithInterceptorChain方法(稍后分析)執(zhí)行http請求,最后調(diào)用finishied方法將call從同步隊列中刪除
異步請求
void enqueue(Callback responseCallback, boolean forWebSocket) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}
同樣先置標志位食茎,然后將封裝的一個執(zhí)行體放到異步執(zhí)行隊列中丹锹。這里面引入了一個新的類AsyncCall
稀颁,這個類繼承于NamedRunnable
,實現(xiàn)了Runnable
接口楣黍。NamedRunnable可以給當前的線程設置名字匾灶,并且用模板方法將線程的執(zhí)行體放到了execute方法中,所以我們分析AsyncCall只需要看execute方法
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain(forWebSocket);
if (canceled) {
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!
logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
通過getResponseWithInterceptorChain方法來執(zhí)行http請求租漂,這個方法是不是很熟悉阶女,在同步請求中也是用的這個方法來執(zhí)行http請求。緊接著判斷call是否被cancel來執(zhí)行不同的回調(diào)窜锯,最后使用finished方法將call從異步執(zhí)行隊列中移除张肾。這里有個需要注意的地方芭析,onResponse回調(diào)被執(zhí)行的條件是本次http請求是完整的锚扎,也就是說即使服務器返回的是錯誤信息,依然會走onResponse回調(diào)馁启,我們在應用層使用的時候驾孔,可以自己再封裝一次芍秆。
OK,以上就是okhttp可以同時支持同步和異步請求的分析過程翠勉,而在getResponseWithInterceptorChain方法中我們將會分析okhttp的另一個重要模塊:攔截器
攔截器
這是我最喜歡okhttp的地方妖啥,你可以攔截當前正在發(fā)出的請求。我們可以使用攔截器做很多事情对碌,例如:添加log方便調(diào)試荆虱,在服務器還沒有ready的情況下模擬一個網(wǎng)絡應答等。在getResponseWithInterceptorChain方法中處理了攔截器的相關邏輯
private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
return chain.proceed(originalRequest);
}
這里ApplicationInterceptorChain
實現(xiàn)了Interceptor.Chain
接口朽们,然后在preceed方法中處理相應的邏輯怀读,preceed代碼如下
@Override public Response proceed(Request request) throws IOException {
// If there's another interceptor in the chain, call that.
if (index < client.interceptors().size()) {
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
Interceptor interceptor = client.interceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain);
if (interceptedResponse == null) {
throw new NullPointerException("application interceptor " + interceptor
+ " returned null");
}
return interceptedResponse;
}
// No more interceptors. Do HTTP.
return getResponse(request, forWebSocket);
}
在index在getResponseWithInterceptorChain方法中被初始化為0,當我們添加了攔截器之后index < client.interceptors().size()
就會走到true的代碼段骑脱,之后會從client.interceptors()
中拿出一個攔截器菜枷,執(zhí)行我們的攔截回調(diào)。這里也可以看到在攔截回調(diào)中是必須要有個Response返回的叁丧,否則會出現(xiàn)異常啤誊。如果沒有自定義攔截器的話,將會調(diào)用getResponse
方法執(zhí)行真正的網(wǎng)絡請求邏輯(相對于攔截器模塊來說是執(zhí)行了真正的網(wǎng)絡請求拥娄,其實后面還有緩存模塊)
有意思的是我們可以定義多個攔截器蚊锹,這就對應了ApplicationInterceptorChain
類的名稱應用攔截鏈
。只要我們在自定義的攔截器回調(diào)方法中調(diào)用chan.proceed
稚瘾,攔截器就會鏈式的調(diào)用下去枫耳。如果我們不希望okhttp執(zhí)行真正的網(wǎng)絡請求,只需要在攔截器中虛擬一個response即可孟抗。需要注意的是迁杨,如果某個攔截器內(nèi)部沒有調(diào)用chan.proceed
方法,那么在它之后添加的攔截器都不會再被執(zhí)行
getResponse
方法將會把網(wǎng)絡請求交給Engine
處理
Response getResponse(Request request, boolean forWebSocket) throws IOException {
// Copy body metadata to the appropriate request headers.
RequestBody body = request.body();
if (body != null) {
Request.Builder requestBuilder = request.newBuilder();
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
request = requestBuilder.build();
}
// Create the initial HTTP engine. Retries and redirects need new engine for each attempt.
engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);
int followUpCount = 0;
while (true) {
if (canceled) {
engine.releaseStreamAllocation();
throw new IOException("Canceled");
}
boolean releaseConnection = true;
try {
engine.sendRequest();
engine.readResponse();
releaseConnection = false;
} catch (RequestException e) {
// The attempt to interpret the request failed. Give up.
throw e.getCause();
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
HttpEngine retryEngine = engine.recover(e.getLastConnectException(), null);
if (retryEngine != null) {
releaseConnection = false;
engine = retryEngine;
continue;
}
// Give up; recovery is not possible.
throw e.getLastConnectException();
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
HttpEngine retryEngine = engine.recover(e, null);
if (retryEngine != null) {
releaseConnection = false;
engine = retryEngine;
continue;
}
// Give up; recovery is not possible.
throw e;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
StreamAllocation streamAllocation = engine.close();
streamAllocation.release();
}
}
Response response = engine.getResponse();
Request followUp = engine.followUpRequest();
if (followUp == null) {
if (!forWebSocket) {
engine.releaseStreamAllocation();
}
return response;
}
StreamAllocation streamAllocation = engine.close();
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
if (!engine.sameConnection(followUp.url())) {
streamAllocation.release();
streamAllocation = null;
}
request = followUp;
engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,
response);
}
}
}
getResponse
會先根據(jù)request body的contentType來設置相應的header凄硼,Content-Length
相信都比較熟悉铅协,在http header中Transfer-Encoding: chunked
表示的是內(nèi)容長度不定,這里比較奇怪的是header的屬性分別在不同的設置摊沉,不清楚為何不放在一起設置狐史。接著會創(chuàng)建一個HttpEngine
對象,設置追加發(fā)送的請求次數(shù)说墨,在HttpEngine
中處理網(wǎng)絡請求代碼如下
engine.sendRequest();
engine.readResponse();
Response response = engine.getResponse();
緊接著是處理各種異常和發(fā)送追加請求骏全,獲取發(fā)送追加請求是在HttpEngine
的followUpRequest
方法中處理,在三種情況下okhttp會發(fā)送追加請求尼斧,通過MAX_FOLLOW_UPS = 20
控制最大追加請求
- 未授權(quán)(401):調(diào)用
okhttpclient
授權(quán)方法重新授權(quán) - 重定向(3xx)
- 請求超時(408):重復發(fā)送原請求
未完待續(xù)...