Android網絡平臺的三板斧基本被square公司承包了,Okhttp,Retrofit,Okio真但是三巨頭。平時用的okhttp比較多,因此咱們頗有必要來看看它的實現原理。java
相信你們對okhttp的使用一點都不陌生了,我這裏不會詳細講解它的使用,僅僅把官網上的搬下來看看,做爲研究的入口:web
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
複製代碼
這個是比較簡單的Get請求json
public static final MediaType JSON
= MediaType.get("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
RequestBody body = RequestBody.create(JSON, json);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
複製代碼
這個是Post請求設計模式
從上面的使用來看,不論是get仍是post請求,都會先建立OkHttpClient實例,這個很容易理解,沒有實例哪來的入口呢,觀摩一下:緩存
OkHttpClient client = new OkHttpClient();
複製代碼
接着看看實例化了哪些東西bash
public OkHttpClient() {
this(new Builder());
}
複製代碼
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
this.interceptors = Util.immutableList(builder.interceptors);
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
this.eventListenerFactory = builder.eventListenerFactory;
this.proxySelector = builder.proxySelector;
this.cookieJar = builder.cookieJar;
this.cache = builder.cache;
this.internalCache = builder.internalCache;
this.socketFactory = builder.socketFactory;
...
}
複製代碼
這是默認的實例化方式,builder裏面也都是默認值,主要實例了代理,緩存,超時等等一些參數,這些系統都給咱們設置了一些默認的值,好比超時是10s,讀寫也是10s等等。可是在實際狀況中,咱們每每去要自定義一些方式,好比,讀寫時間設置30s,增長一些攔截器等,這就須要另一種實例化的方式了。服務器
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.connectTimeout(20 * 1000, TimeUnit.MILLISECONDS)
.readTimeout(30 * 1000, TimeUnit.MILLISECONDS)
/* 關閉OkHttp失敗重試鏈接的機制,該問題致使發帖重複的問題 */
.retryOnConnectionFailure(false)
.addInterceptor(new EnhancedCacheInterceptor())
.addInterceptor(new SecurityInterceptor(mApplicationContext)) // 加密解密
.addNetworkInterceptor(new StethoInterceptor())
.dns(new dsn().build();
複製代碼
看上去很清晰,採用的是建造者模式,咱們設置自定義參數,而後經過build去實例化。這兩種方式具體怎麼用,選擇哪種那就看咱們具體業務了。cookie
接下來看看Request的封裝網絡
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
複製代碼
一樣的,無論什麼請求方式,都要構建Requestapp
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable RequestBody body;
final Object tag;
private volatile CacheControl cacheControl; // Lazily initialized.
Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tag = builder.tag != null ? builder.tag : this;
}
複製代碼
能夠看到request包含url,請求方法,header,以及body。這也就對應了http裏面的請求頭部,包含一些具體信息。它也是採用的建造者模式去構造的。平時咱們用得比較多的是post請求,這裏咱們作個簡要的分析:
RequestBody body = RequestBody.create(JSON, json);
...
public Builder post(RequestBody body) {
return method("POST", body);
}
複製代碼
首先咱們構造RequestBody,body裏面封裝表單或者json,而後調用post方法:
if (method == null) throw new NullPointerException("method == null");
if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
if (body != null && !HttpMethod.permitsRequestBody(method)) {
throw new IllegalArgumentException("method " + method + " must not have a request body.");
}
if (body == null && HttpMethod.requiresRequestBody(method)) {
throw new IllegalArgumentException("method " + method + " must have a request body.");
}
this.method = method;
this.body = body;
return this;
}
複製代碼
這裏面很簡單,分別對method,body作個校驗,這二者都不能爲空,也就是說,當採用post去發請求的時候body是不能爲空的,這個錯誤有時候咱們常常會遇到。而後賦值給request的變量存着備用。
ok,若是上面的都是準備工做,那麼接下來就是正式發送請求了。仍是直接看代碼:
Response response = client.newCall(request).execute()
複製代碼
這個是發送同步請求的,哇塞,一句話?是的,就是一句話解決了,看上去簡直難以想象,咱們看看源碼newCall方法,很顯然call是發送請求的核心:
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
複製代碼
接着去RealCall類中去調用newRealCall方法:
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
複製代碼
構造函數把參數賦值,而後new了一個實例RetryAndFollowUpInterceptor,這個咱們後面再來看。接着就是調用execut函數了:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
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 {
client.dispatcher().finished(this);
}
}
複製代碼
這裏作了幾件事:
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);
}
複製代碼
這看上去有點兒陌生,裏面增長了各類攔截器,而後調用了chain.proceed(originalRequest),感受這纔是大boss啊,官網裏面有句話
the whole thing is just a stack of built-in interceptors.
複製代碼
這句話頗有含金量,也就是說okhttp所作的是就是攔截各類請求去解析,對攔截器一一作處理,納悶了,爲啥要攔截這麼多?由於一個請求的發送到接收到返回數據,中間要作不少是事,好比失敗重傳,緩存,還有自定義的攔截器,好比對數據加密等等。
首先給出一張總體圖:
上一節中,實例化RealInterceptorChain 後調用了chain.proceed();最終會執行下面的代碼public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
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);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null. if (response == null) { throw new NullPointerException("interceptor " + interceptor + " returned null"); } if (response.body() == null) { throw new IllegalStateException( "interceptor " + interceptor + " returned a response with no body"); } return response; } 複製代碼
這是核心中的核心,它採用了責任鏈模式,這種模式,是java設計模式中比較重要的一種,你們不懂的能夠單獨去查查,它的整體思想就是若是本身能夠消費事件就消費,不消費就傳遞給下一個攔截器。
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);
複製代碼
能夠看到這裏的index進行了+1處理,而後開始啓動下一個攔截器。很顯然,這這些攔截器的執行是有順序的。咱們先看看這種攔截器的做用:
/**
* Bridges from application code to network code. First it builds a network request from a user
* request. Then it proceeds to call the network. Finally it builds a user response from the network
* response.
*/
複製代碼
它的意思是把用戶的代碼,也就是咱們寫的代碼,轉換爲服務器標準的代碼。解釋:它會提取http請求所須要的一些參數,好比agent,heades,contenttype等,而後發送出去。響應的過程也是同樣,把服務器的響應結果轉換爲咱們須要的,好比把respones轉換爲咱們須要的對象。實質就是按照http協議標準化格式。
/** Serves requests from the cache and writes responses to the cache. */
複製代碼
這個主要是處理緩存的,咱們發送請求和獲取響應結果的時候,不能每次都去從新建立吧,那樣效率過低,首先去從緩存中去取沒,若是緩存中有現有的鏈接,咱們直接發請求,不用重複建立。響應結果也是同樣的。
/** Opens a connection to the target server and proceeds to the next interceptor. */
複製代碼
若是緩存沒有命中,那麼就去新建鏈接啦,這個用來新建鏈接的。
/** This is the last interceptor in the chain. It makes a network call to the server. */
複製代碼
這個是責任鏈模式的尾端,它最終會去經過網絡請求服務器資源。
這裏我來着重分析一下ConnectInterceptor,CallServerInterceptor這兩種,由於這是最終創建鏈接和發送請求的過程,能夠說是最重要的。
@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);
}
複製代碼
這個過程建立了HttpCodec,可是它並無在這個攔截器中去使用,而是傳遞到後面發動請求的攔截器中去了也便是CallServerInterceptor。HttpCodec究竟是什麼,它是對HTTP協議作了抽象處理,在 Http1Codec 中,它利用 Okio 對 Socket 的讀寫操做進行封裝,Okio 之後有機會再進行分析,如今讓咱們對它們保持一個簡單地認識:它對 java.io 和 java.nio 進行了封裝,讓咱們更便捷高效的進行 IO 操做。
@Override public Response intercept(Chain chain) throws IOException {
HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
Request request = chain.request();
long sentRequestMillis = System.currentTimeMillis();
httpCodec.writeRequestHeaders(request);
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
httpCodec.finishRequest();
Response response = httpCodec.readResponseHeaders()
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
// 省略部分檢查代碼
return response;
}
複製代碼
這個裏面代碼比較長,咱們只保留核心分心,上面不是傳遞了HttpCodec了嘛,這裏就利用起來了,它是利用okio進行發送請求,這裏okio不是咱們分析的重點,讀者能夠單獨找文章去看看。 總之:Okhttp發送請求是經過okio去作的,okio實質上就是對socket進行了封裝,一層套一層而已,這就是神祕的面紗後面的簡單。 這裏作了幾件事:
咱們對okhttp作了個總體介紹,固然裏面有不少細節沒有解釋,也不想去解釋,讀者有須要本身去研究。這裏我總結一下:
OKhttp中使用的設計模式:建造者模式和責任鏈模式;
最後上一張圖片,這個是從其餘讀者文章弄過來的,很用心,我就直接使用了;