解析OKHttp首先走一遍正常的流程,然后將比較有意思的點拿出來說明
正常流程分析
1.OkHttpClient初始化
OkHttpClient mOkHttpClient = new OkHttpClient();
通過代碼查看,可以看到調用了內部的Builder構造
如下
public Builder() {
//調度器
dispatcher = new Dispatcher();
//默認支持的協議列表
protocols = DEFAULT_PROTOCOLS;
//默認的連接規范
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
//默認的代理選擇器(直連)
proxySelector = ProxySelector.getDefault();
//默認不管理cookie
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
//主機驗證
hostnameVerifier = OkHostnameVerifier.INSTANCE;
//證書鎖,默認不開啟
certificatePinner = CertificatePinner.DEFAULT;
//默認不進行授權
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
//初始化連接池
connectionPool = new ConnectionPool();
//DNS
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
//超時時間
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
接下來介紹下關于上面的OkHttpClient配置需要用到的類
Dispatcher
調度器,里面包含了線程池和三個隊列(readyAsyncCalls:保存等待執行的異步請求;runningAsyncCalls:保存正在運行的異步請求;runningSyncCalls:保存正在執行的同步請求)
//保存準備運行的異步請求(當運行請求超過限制數時會保存在此隊列)
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//保存正在運行的異步請求
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//保存正在運行的同步請求
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
對于異步請求,調用Dispatcher的enqueue方法,在這個方法會將相關請求提交到線程池中操作,從而異步執行
synchronized void enqueue(AsyncCall call) {
//檢查容量大小
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);//加入隊列
executorService().execute(call);//執行
} else {
//超過容量大小后,加入準備隊列中
readyAsyncCalls.add(call);
}
}
對于同步請求,不需要提交到線程池執行,通過Dispatcher的executed方法調用即可
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
當請求執行完畢后,調用finished將請求從runningAsyncCalls隊列中移除,并且檢查readyAsyncCalls以繼續提交在隊列中準備的請求。
//移除執行完畢的請求
synchronized void finished(AsyncCall call) {
if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");
promoteCalls();//推進請求隊列
}
//推進請求
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; //容量已滿,不提交新請求
if (readyAsyncCalls.isEmpty()) return; // 沒有正在準備的請求,返回
//從readyAsyncCalls中循環取出AsyncCall直到達到容量上限
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // 達到上限后返回
}
}
Protocal
協議類,用來表示使用的協議版本,比如http/1.0,http/1.1,spdy/3.1,h2等
ConnectionSpecs
連接規范,用于配置Socket連接層,對于HTTPS,還能配置安全傳輸層協議(TLS)版本與密碼套件(CipherSuite)
Proxy與ProxySelector
Proxy代理類,默認有三種代理模式DIRECT(直連),HTTP(Http代理),SOCKS(socks代理)
ProxySelector代理選擇類,默認不使用代理,即使用直連方式,當然,我們可以自定義配置,以指定URI使用某種代理,類似代理軟件的PAC功能。
CookieJar
用來管理cookie,可以根據url保存cookie,也可以通過url取出相應cookie。默認的不做cookie管理。該接口中有兩個抽象方法,用戶可以自己實現該接口以對cookie進行管理。
//保存cookie
void saveFromResponse(HttpUrl url, List<Cookie> cookies);
//根據Url導入保存的Cookie
List<Cookie> loadForRequest(HttpUrl url);
SocketFactory
Socket工廠,通過createSocket來創建Socket
HostnameVerifier
主機名驗證器,與HTTPS中的SSL相關,當握手時如果URL的主機名不是可識別的主機,就會要求進行主機名驗證
public interface HostnameVerifier {
//通過session驗證指定的主機名是否被允許
boolean verify(String hostname, SSLSession session);
}
CertificatePinner
證書鎖,HTTPS相關,用于約束哪些證書可以被信任,可以防止一些已知或未知的中間證書機構帶來的攻擊行為。如果所有證書都不被信任將拋出SSLPeerUnverifiedException異常。
其中用于檢查證書是否被信任的源碼如下:
//檢查證書是否被信任
public void check(String hostname, List<Certificate> peerCertificates)
throws SSLPeerUnverifiedException {
List<Pin> pins = findMatchingPins(hostname);//獲取Pin(網址,hash算法,hash值)
if (pins.isEmpty()) return;
if (certificateChainCleaner != null) {
//通過清潔器獲取信任的證書
peerCertificates = certificateChainCleaner.clean(peerCertificates, hostname);
}
for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
//對證書進行比對hash值,如果配對失敗就拋出SSLPeerUnverifiedException異常
X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);
// Lazily compute the hashes for each certificate.
ByteString sha1 = null;
ByteString sha256 = null;
for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
Pin pin = pins.get(p);
if (pin.hashAlgorithm.equals("sha256/")) {
if (sha256 == null) sha256 = sha256(x509Certificate);
if (pin.hash.equals(sha256)) return; // Success!
} else if (pin.hashAlgorithm.equals("sha1/")) {
if (sha1 == null) sha1 = sha1(x509Certificate);
if (pin.hash.equals(sha1)) return; // Success!
} else {
throw new AssertionError();
}
}
}
// ...
}
Authenticator
身份認證器,當連接提示未授權時,可以通過重新設置請求頭來響應一個新的Request。狀態碼401表示遠程服務器請求授權,407表示代理服務器請求授權。該認證器在需要時會被RetryAndFollowUpInterceptor觸發。
public interface Authenticator {
Authenticator NONE = new Authenticator() {
@Override public Request authenticate(Route route, Response response) {
return null;
}
};
Request authenticate(Route route, Response response) throws IOException;
}
關于授權的源碼實現如下:
class MyAuthenticator implements Authenticator {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(...)
Request.Builder builder=response.request().newBuilder();
if(response.code()==401){
builder .header("Authorization", credential);
}else if(response.code()==407){
builder .header("Proxy-Authorization", credential);
}
return builder.build();
}
}
ConnectionPool
連接池,用于管理HTTP和SPDY連接的復用以減少網絡延遲,HTTP請求相同的Address時可以共享同一個連接。
DNS
DNS這里就不用介紹了,用于根據主機名來查詢對應的IP。
2.發起請求
使用OKHttp發送請求一般有兩種方式,一種是同步方式,一種是異步方式,如下
//異步方式
Request request = new Request.Builder()
.url("").build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
//同步方式
try {
Request request = new Request.Builder()
.url("").build();
Call call = mOkHttpClient.newCall(request);
Response response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
接下來我們分別從源碼角度分析下這兩種方式
首先是同步方式
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
這里收先調用了Dispatcher的executed方法,將這個請求加入runningAsyncCalls隊列中,然后調用getResponseWithInterceptorChain方法獲取Respone,這個就是我們請求后得到的回復,獲取后返回這個Respone,最后在finally調用了Dispatcher的finished方法,將請求從runningAsyncCalls隊列中移除
接下來是異步方式
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
可以看到調用Dispatcher的enqueue方法傳遞了一個AsyncCall對象,注意這個AsyncCall對象繼承Runnable接口,所以在當在線程池中運行會調用AsyncCall中的execute方法,接下來我們看下AsyncCall的execute方法,如下
@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 {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
可以看到這里調用getResponseWithInterceptorChain方法獲取Respone,接下來通過回調傳遞出去,最后在finally調用了Dispatcher的finished方法,將請求從runningAsyncCalls隊列中移除
通過這兩個代碼分析,可以知道獲取Respone都是通過getResponseWithInterceptorChain方法,唯一的區別是一個是在主線程中,另外一個在線程池中的線程,接下來看一下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);
return chain.proceed(originalRequest);
}
這里是真正發出網絡請求的地方,可以看到這里有很多個Interceptor,Interceptor是OkHttp中最核心的一個東西,它把實際的網絡請求、緩存、透明壓縮等功能都統一了起來,每一個功能都只是一個 Interceptor,它們再連接成一個 Interceptor.Chain,環環相扣,最終圓滿完成一次網絡請求。
從 getResponseWithInterceptorChain 函數我們可以看到,Interceptor.Chain 的分布依次是:

流程如下:
1.在配置 OkHttpClient 時設置的 interceptors;
2.負責失敗重試以及重定向的 RetryAndFollowUpInterceptor;
3.負責把用戶構造的請求轉換為發送到服務器的請求、把服務器返回的響應轉換為用戶友好的響應的 BridgeInterceptor;
4.負責讀取緩存直接返回、更新緩存的 CacheInterceptor;
5.負責和服務器建立連接的 ConnectInterceptor;
6.配置 OkHttpClient 時設置的 networkInterceptors;
7.負責向服務器發送請求數據、從服務器讀取響應數據的 CallServerInterceptor。
這里很明顯的是使用了責任鏈模式,接下來就是分析一下每一個Interceptor究竟是干了什么事情
3.分析Interceptor
1.RetryAndFollowUpInterceptor 重試與重定向攔截器
這個攔截器主要用來實現重試與重定向的功能,核心代碼如下
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//初始化流分配器
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()));
int followUpCount = 0;
Response priorResponse = null;
while (true) {//死循環
//..
//省略了部分源碼
Response response = null;
boolean releaseConnection = true;
try {
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (Exception e) {
//..
//省略了部分源碼
releaseConnection = false;
continue;
} finally {
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
//將上次的請求放入priorResponse中
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
//檢查是否觸發重定向重試等條件,并返回Request
Request followUp = followUpRequest(response);
if (followUp == null) {//null表示無需重試
if (!forWebSocket) {
streamAllocation.release();
}
return response;//返回response
}
//..
//省略了部分源碼
request = followUp;
priorResponse = response;
//while循環進行下次請求
}
}
通過代碼可以發現RetryAndFollowUpInterceptor內部通過while(true)死循環來進行重試獲取Response(有重試上限,超過會拋出異常)。followUpRequest主要用來根據響應碼來判斷屬于哪種行為觸發的重試和重定向(比如未授權,超時,重定向等),然后構建響應的Request進行下一次請求。當然,如果沒有觸發重新請求就會直接返回Response。
2.BridgeInterceptor 橋接攔截器
橋接攔截器,用于完善請求頭,比如Content-Type、Content-Length、Host、Connection、Accept-Encoding、User-Agent等等,這些請求頭不用用戶一一設置,如果用戶沒有設置該庫會檢查并自動完善。此外,這里會進行加載和回調cookie。
核心代碼如下:
@Override
public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
//將用戶沒有寫入請求頭的內容自動補充進去,比如Content-Type、Content-Length、Host、Connection、Accept-Encoding、User-Agent等等
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
//..
}
//獲取cookie添加到請求頭中
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
//...
Response networkResponse = chain.proceed(requestBuilder.build());
//將響應cookie回調出去供用戶保存
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
//...
//省略了部分源碼
responseBuilder.headers(strippedHeaders);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
return responseBuilder.build();
}
3.CacheInterceptor 緩存攔截器
緩存攔截器首先根據Request中獲取緩存的Response,然后根據用于設置的緩存策略來進一步判斷緩存的Response是否可用以及是否發送網絡請求(CacheControl.FORCE_CACHE因為不會發送網絡請求,所以networkRequest一定為空)。如果從網絡中讀取,此時再次根據緩存策略來決定是否緩存響應。
核心代碼如下:
@Override
public Response intercept(Chain chain) throws IOException {
//通過Request從緩存中獲取Response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//根據請求頭獲取用戶指定的緩存策略,并根據緩存策略來獲取networkRequest,cacheResponse。cacheResponse為null表示當前策略就算有緩存也不讀緩存
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;//表示發往網絡的request,不請求網絡應為null
Response cacheResponse = strategy.cacheResponse;//返回從緩存中讀取的response
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
//cacheResponse表示不讀緩存,那么cacheCandidate不可用,關閉它
closeQuietly(cacheCandidate.body());
}
//..
//省略了部分源碼
//返回從緩存中讀取的Response
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
//..
//省略了部分源碼
//獲取網絡Response
networkResponse = chain.proceed(networkRequest);
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (HttpHeaders.hasBody(response)) {
//如果可以緩存(用戶允許,響應也允許)就進行緩存到本地
CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
response = cacheWritingResponse(cacheRequest, response);
}
return response;
}
配置緩存策略的方法如下:
Request request = new Request.Builder()
.cacheControl(CacheControl.FORCE_NETWORK)
.url("")
.build();
4.ConnectInterceptor 連接攔截器
連接攔截器,用于打開一個連接到遠程服務器。
核心代碼如下
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
boolean doExtensiveHealthChecks = !request.method().equals("GET");
//獲取HttpStream
HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks);
//獲取RealConnection
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpStream, connection);
}
實際上建立連接就是創建了一個HttpCodec對象,它將在后面的步驟中被使用,那它又是何方神圣呢?它是對HTTP協議操作的抽象,有兩個實現:Http1Codec和Http2Codec,顧名思義,它們分別對應HTTP/1.1和HTTP/2版本的實現。
在Http1Codec中,它利用Okio對Socket的讀寫操作進行封裝,我們對它們保持一個簡單地認識:它對java.io和java.nio進行了封裝,讓我們更便捷高效的進行IO操作。
5.CallServerInterceptor 調用服務攔截器
調用服務攔截器是攔截鏈中的最后一個攔截器,通過網絡與調用服務器。通過HttpStream依次次進行寫請求頭、請求頭(可選)、讀響應頭、讀響應體。
@Override
public Response intercept(Chain chain) throws IOException {
HttpStream httpStream = ((RealInterceptorChain) chain).httpStream();
StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
Request request = chain.request();
long sentRequestMillis = System.currentTimeMillis();
//寫請求頭
httpStream.writeRequestHeaders(request);
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
//寫請求體
Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
httpStream.finishRequest();
//獲取Response。
Response response = httpStream.readResponseHeaders()
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
//寫入Response的body
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpStream.openResponseBody(response))
.build();
}
//...
return response;
}
這里主要做的事情:
1.向服務器發送request header;
2.如果有request body,就向服務器發送;
3.讀取response header,先構造一個Response對象;
4.如果有response body,就在3的基礎上加上body構造一個新的Response對象;
這里我們可以看到,核心工作都由HttpCodec對象完成,而HttpCodec實際上利用的是Okio,而Okio實際上還是用的Socket。
到這里,一個請求的流程就基本走完了。接下來說一下OKHttp中比較有意思的點。