okHttp目前能夠稱的上是Android主流網絡框架,甚至連谷歌官方也將網絡請求的實現替換成okHttp.java
網上也有不少人對okHttp的源碼進行了分析,不過基於每一個人的分析思路都不盡相同,讀者看起來的收穫也各不相同,因此我仍是整理了下思路,寫了點本身的分析感悟。算法
本文基於okhttp3.11.0版本分析json
String url = "http://www.baidu.com";
//'1. 生成OkHttpClient實例對象'
OkHttpClient okHttpClient = new OkHttpClient();
//'2. 生成Request對象'
Request request = new Request.Builder()
.url(url)
.post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"),"test content"))
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
}
});
複製代碼
借用別人的一張流程圖來歸納一下okHttp的請求走向 原圖出處c#
建立請求對象 (url, method,body)-->request-->Callapi
請求事件隊列,線程池分發 enqueue-->Runnable-->ThreadPoolExecutor緩存
遞歸Interceptor
攔截器,發送請求。 InterceptorChain安全
請求回調,數據解析。 Respose-->(code,message,requestBody)bash
其中 Request
維護請求對象的屬性服務器
public final class Request {
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable RequestBody body;
//請求的標記,在okHttp2.x的時候,okHttpClint提供Cancel(tag)的方法來批量取消請求
//不過在3.x上批量請求的api被刪除了,要取消請求只能在Callback中調用 call.cancel()
//所以這個tags參數只能由開發者本身編寫函數來實現批量取消請求的操做
final Map<Class<?>, Object> tags;
}
複製代碼
請求響應的包裝接口Call
cookie
public interface Call extends Cloneable {
Request request();
Response execute() throws IOException;
void enqueue(Callback responseCallback);
void cancel();
}
複製代碼
Call的實現類RealCall
和AsyncCall
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
//其中AsyncCall是RealCall的一個內部類,繼承自Runnable,這樣就能經過線程池來回調AsyncCall的execute函數
final class AsyncCall extends NamedRunnable {
@Override
protected void execute() {
boolean signalledCallback = false;
try {
//getResponseWithInterceptorChain 攔截鏈的邏輯,也是發起請求的真正入口
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) {
...
}
...
}
}
複製代碼
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//用戶自定義的攔截器(注意addAll 因此能夠添加多個自定義的攔截器)
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);
}
複製代碼
okHttp的核心部分就是這個Interceptor
攔截鏈,每一個Interceptor
各自負責一部分功能,內部經過遞歸的方式遍歷每個Interceptor
攔截器。遞歸邏輯在RealInterceptorChain
類下
public final class RealInterceptorChain implements Interceptor.Chain {
//攔截器遞歸的入口
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {
...
//攔截器遞歸的核心代碼,根據interceptors列表執行每個攔截器的intercept函數
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
....
return response;
}
}
複製代碼
遞歸結束後會得到請求響應,那麼說明咱們的request行爲就在這個攔截鏈中,接下來咱們先看看負責網絡請求的那部分攔截器,從類名上就能比較容易的看出 ConnectInterceptor
和 CallServerInterceptor
這兩個攔截器的主要工做。
ConnectInterceptor
public final class ConnectInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
複製代碼
其中有幾個對象說明一下
**StreamAllocation:**內存流的存儲空間,這個對象能夠直接從realChain中直接獲取,說明在以前的攔截鏈中就已經賦值過
HttpCodec(Encodes HTTP requests and decodes HTTP responses): 對請求的編碼以及對響應數據的解碼
**realChain.proceed():**通知下一個攔截器執行
接下來看建立HttpCodec對象的newStream函數中作了些什麼
//HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
public HttpCodec newStream( OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
...
try {
//findHealthyConnection內部經過一個死循環查找一個可用的鏈接,優先使用存在的可用鏈接,不然就經過 //線程池來生成,其中多處使用 synchronized關鍵字,防止由於多併發致使問題
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
複製代碼
沿着代碼往下走,你會發現實際上負責網絡鏈接功能的類是一個叫RealConnection
的類,該類中有一個connect
的函數
RealConnection#connect
public void connect(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
...
while (true) {
try {
if (route.requiresTunnel()) {
//這個函數最終仍是會走到connectSocket()函數中
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
if (rawSocket == null) {
// We were unable to connect the tunnel but properly closed down our resources.
break;
}
} else {
connectSocket(connectTimeout, readTimeout, call, eventListener);
}
}
...
}
//最終調用的仍是Socket對象來建立網絡鏈接,包括connectTimeout,readTimeout等參數也是這個時候真正設置的。
複製代碼
CallServerInterceptor
This is the last interceptor in the chain. It makes a network call to the server.
直接看CallServerInterceptor
的intercept函數
@Override
public Response intercept(Chain chain) throws IOException{
//下面的各參數都是以前幾個攔截器所生成的
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
//發送請求頭,也是網絡請求的開始
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
//請求不是get,而且有添加了請求體,寫入請求體信息
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
//若是請求頭中有Expect:100-continue這麼一個屬性
//會先發送一個header部分給服務器,並詢問服務器是否支持Expect:100-continue 這麼一個擴展域
//okhttp3提供這麼個判斷是爲了兼容http2的鏈接複用行爲的
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
//刷新緩存區,能夠理解爲向服務端寫入數據
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}
//寫入請求body
if (responseBuilder == null) {
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
}
...
httpCodec.finishRequest();
//響應相關的代碼
...
}
複製代碼
寫入請求body的核心代碼
//將請求體寫入到BufferedSink中,而BufferedSink是另一個類庫Okio中的類
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
//httpCodec.finishRequest 最終會調用 sink.flush(),sink是BufferedSink的對象,BufferedSink在底層
//會將其內的數據推給服務端,至關因而一個刷新緩衝區的功能
httpCodec.finishRequest();
複製代碼
響應相關的代碼
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
//讀取響應頭,實際的返回流存放位置在okio庫下的buffer對象中,讀取過程當中作了判斷,只有當code==100時纔會 //有返回,否則拋出異常並攔截,因此下面這段代碼確定有響應頭返回,否則直到超時也不會回調
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (code == 100) {
//若是服務端響應碼爲100,須要咱們再次請求,注意這裏的100是響應碼和以前的100不一樣
//以前的100是headerLine的標識碼
responseBuilder = httpCodec.readResponseHeaders(false);
response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
if (forWebSocket && code == 101) {
//Connection is upgrading, but we need to ensure interceptors see a
//non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
//讀取響應body
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
return response;
複製代碼
讀取響應body HttpCodec#openResponseBody
public ResponseBody openResponseBody(Response response) throws IOException {
...
Source source = newFixedLengthSource(contentLength);
return new RealResponseBody(contentType, contentLength, Okio.buffer(source));
...
}
//openResponseBody將Socket的輸入流InputStream對象交給OkIo的Source對象,而後封裝成RealResponseBody(該類是ResponseBody的子類)做爲Response的body.
//具體讀取是在RealResponseBody父類ResponseBody中,其中有個string()函數
//響應主體存放在內存中,而後調用source.readString來讀取服務器的數據。須要注意的是該方法最後調用closeQuietly來關閉了當前請求的InputStream輸入流,因此string()方法只能調用一次,再次調用的話會報錯
public final String string() throws IOException {
BufferedSource source = source();
try {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);
} finally {
Util.closeQuietly(source);
}
}
複製代碼
拿到請求回調的Response以後,再回到咱們最開始調用的代碼,
String url = "http://www.baidu.com";
//'1. 生成OkHttpClient實例對象'
OkHttpClient okHttpClient = new OkHttpClient();
//'2. 生成Request對象'
Request request = new Request.Builder()
.url(url)
.post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"),"test content"))
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) {
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(response.body().string());
}
});
複製代碼
咱們能夠從Response對象中獲取全部咱們所須要的數據,包括header,body.至此,okHttp的網絡請求的大體流程已經分析完成,至於還有部分沒有講到的攔截器就再也不本文綴述了.有興趣的能夠看下文末的參考鏈接或者自行谷歌。
Okhttp之CallServerInterceptor簡單分析
Android技能樹 — 網絡小結之 OkHttp超超超超超超超詳細解析
OkHttp3.0解析 —— 從源碼的角度談談發起網絡請求時作的操做
關於Http的請求頭 Expect:100-Continue
Expect請求頭部域,用於指出客戶端要求的特殊服務器行爲。若服務器不能理解或者知足
Expect域中的任何指望值,則必須返回417(Expectation Failed)狀態,或者若是請求
有其餘問題,返回4xx狀態。
Expect:100-Continue握手的目的,是爲了容許客戶端在發送請求內容以前,判斷源服務器是否願意接受
請求(基於請求頭部)。
Expect:100-Continue握手需謹慎使用,由於遇到不支持HTTP/1.1協議的服務器或者代理時會引發問題。
複製代碼
http2比起http1.x的有點主要體如今如下幾點