深入淺出 OkHttp 原始碼
OkHttp3是Square
出品的高質量Http網路請求庫,目前在GitHub上的star數超過17000。很多Android專案的網路元件都是基於OkHttp封裝的,還有著名的Retrofit
也是基於OkHttp封裝的。
OkHttp的基本使用
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(ENDPOINT)
.build();
//同步請求
Response response = client.newCall(request).execute();
//非同步請求
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
最基本的用法就是先建立一個OkHttpClient,然後build出一個Requset物件,最後傳送請求,可以是同步請求,也可以是非同步請求。使用起來很簡單,但背後是怎麼實現的,下面從原始碼層面來分析下。
OkHttp 呼叫流程
第一步: new OkHttpClient(Builder)
//OkHttpClient.java
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.readTimeout = builder.readTimeout;
this.writeTimeout = builder.writeTimeout;
this.pingInterval = builder.pingInterval;
}
這裡建立了一個預設的OkHttpCient.Builder,用於配置各種引數。
第二步:okhttpclient.newCall(request)
//OkHttpClient.java
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
//RealCall.java
RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
這裡用request物件建立了一個RealCall物件,把一些引數傳到RealCall。
第三步:execute() or enqueue()
//RealCall.java
@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);
}
}
同步請求,很直接就呼叫到了最核心的函式getResponseWithInterceptorChain()
。再看下非同步請求。
//RealCall.java
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
而非同步請求,將使用者介面的responseCallback
物件封裝成一個AsyncCall
物件提交給Dispather
來處理,這裡的AsyncCall
是RealCall
的一個內部類。再看下這個Dispather
怎麼處理這個AsyncCall
的。
//Dispatcher.java
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
Dispather
管理了一些請求佇列,如果正在執行的非同步請求沒有達到上限,就直接將這個請求提交給執行緒池,否則加入到等待佇列中。而且這裡直接把AsyncCall
的物件給了執行緒池,其實這個AsyncCall
就是一個Runnable
的實現類。
//RealCall.java
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
......
@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);
}
}
}
AsyncCall
父類的run()
方法會呼叫抽象方法execute()
,也就是將在Dispather
裡的執行緒池執行AsyncCall
物件的時候,就會執行到execute()
,在這個方法裡同樣呼叫了核心的網路請求方法getResponseWithInterceptorChain()
。
而且在execute()
裡會回撥使用者介面responseCallback
的回撥方法。注意:這裡的回撥是在非主執行緒直接回撥的,也就是在Android裡使用的話要注意這裡面不能直接更新UI操作。
至此,同步請求和非同步請求最終都是呼叫的getResponseWithInterceptorChain();
來傳送網路請求,只是非同步請求涉及到一些執行緒池操作,包括請求的佇列管理、排程。
第四步:getResponseWithInterceptorChain()
//RealCall.java
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);
}
在這個方法裡就是新增了一些攔截器,然後啟動一個攔截器呼叫鏈,攔截器遞迴呼叫之後最後返回請求的響應Response
。這裡的攔截器分層的思想就是借鑑的網路裡的分層模型的思想。請求從最上面一層到最下一層,響應從最下一層到最上一層,每一層只負責自己的任務,對請求或響應做自己負責的那塊的修改。
Q1:這裡為什麼每次都重新建立RealInterceptorChain
物件,為什麼不直接複用上一層的RealInterceptorChain
物件?(文末給出答案)
OkHttp攔截器分層結構
//RealInterceptorChain.java
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
Connection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
......
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
...
return response;
}
RealInterceptorChain的proceed()
,每次重新建立一個RealInterceptorChain物件,然後呼叫下一層的攔截器的interceptor.intercept()
方法。
每一個攔截器的intercept()
方法都是這樣的模型
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 1、該攔截器在Request階段負責的事情
// 2、呼叫RealInterceptorChain.proceed(),其實是遞迴呼叫下一層攔截器的intercept方法
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
//3、該攔截器在Response階段負責的事情,然後返回到上一層攔截器的 response階段
return response;
}
}
這差不多就是OkHttp的分層攔截器模型,借鑑了網路裡的OSI七層模型的思想。最底層是CallServerInterceptor
,類比網路裡的物理層
。OkHttp還支援使用者自定義攔截器,插入到最頂層和CallServerInterceptor
上一層的位置。比如官方寫了一個Logging
Interceptor
,用於列印網路請求日誌的攔截器。
BridgeInterceptor
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
// Request階段
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
......
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");
}
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
}
.....
Response networkResponse = chain.proceed(requestBuilder.build());
// Response階段
.....
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}
BridgeInterceptor攔截器在Request階段,將使用者的配置資訊,重新建立Request.Builder物件,重新build出Request物件,並新增一些請求頭,比如:host,content-length,keep-alive等。
BridgeInterceptor在Response階段做gzip解壓操作。
CacheInterceptor
CacheInterceptor攔截器在Request階段判斷該請求是否有快取,是否需要重新請求,如果不需要重新請求,直接從快取裡取出內容,封裝一個Response返回,不需要再呼叫下一層。
CacheInterceptor攔截器在Response階段,就是把下面一層的Response做快取。
ConnectInterceptor
//ConnectInterceptor.java
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, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
ConnectInterceptor攔截器只在Request階段建立連線,Response階段直接把下一層的Response返回給上一層。再看下建立連線的過程。
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
....
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, this);
......
} catch (IOException e) {
throw new RouteException(e);
}
}
findHealthyConnection()函式尋找一條健康的網路連線,其內部主要呼叫了findConnection()
。
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {
.....
// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this);
if (connection != null) {
return connection;
}
selectedRoute = route;
}
// If we need a route, make one. This is a blocking operation.
if (selectedRoute == null) {
selectedRoute = routeSelector.next();
}
// Create a connection and assign it to this allocation immediately. This makes it possible for
// an asynchronous cancel() to interrupt the handshake we're about to do.
RealConnection result;
synchronized (connectionPool) {
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result);
if (canceled) throw new IOException("Canceled");
}
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
// Pool the connection.
Internal.instance.put(connectionPool, result);
.....
}
closeQuietly(socket);
return result;
}
這裡面大概就是從連線池裡去找已有的網路連線,如果有,則複用,減少三次握手;沒有的話,則建立一個RealConnection物件,三次握手,建立連線,然後將連線放到連線池。具體的內部connect
過程,就不深入了。
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
}
ConnectionPool
最多支援保持5個地址的連線keep-alive,每個keep-alive
5分鐘,並有非同步執行緒迴圈清理無效的連線。
CallServerInterceptor
@Override public Response intercept(Chain chain) throws IOException {
...
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
......
if (responseBuilder == null) {
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
}
httpCodec.finishRequest();
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
.....
return response;
}
CallServerInterceptor 精簡出來的程式碼就是writeRequestHeaders(),flushRequest(),finishRequest(),傳送請求,然後readResponseHeaders,openResponseBody讀取response。
CallServerInterceptor底層的IO流讀寫依賴於Square
自家的Okio專案,HttpCodec
是封裝的IO編碼和解碼的實現。
至此,OkHttp中幾個核心的攔截器就到此為止了,OkHttp最精髓的部分也就體現在這個攔截器上。最後補充幾個關於OkHttp的面試問題。
* OkHttp是如何做鏈路複用?
* OkHttp的Intereptor能不能取消一個request?
這兩個問題在分析原始碼之後應該很容易回答了。
回答上面留的一個問題:
每次重新建立一個RealInterceptorChain
物件,因為這裡是遞迴呼叫,在呼叫下一層攔截器的interupter()方法的時候,本層的
response階段還沒有執行完成,如果複用RealInterceptorChain
物件,必然導致下一層修改RealInterceptorChain
,所以需要重新建立RealInterceptorChain
物件。
相關文章
- 深入淺出Semaphore原始碼解析原始碼
- 深入淺出AQS原始碼解析AQS原始碼
- 深入淺出ReentrantLock原始碼解析ReentrantLock原始碼
- RxJava+Retrofit+OkHttp深入淺出-mvp(使用篇)RxJavaHTTPMVP
- 【深入淺出jQuery】原始碼淺析2–奇技淫巧jQuery原始碼
- 【深入淺出jQuery】原始碼淺析–整體架構jQuery原始碼架構
- 深入淺出ReentrantReadWriteLock原始碼解析原始碼
- RxJava+Retrofit+OkHttp 深入淺出-終極封裝一RxJavaHTTP封裝
- 深入理解OkHttp原始碼及設計思想HTTP原始碼
- 深入理解OkHttp原始碼(一)——提交請求HTTP原始碼
- 深入淺出Java執行緒池:原始碼篇Java執行緒原始碼
- 深入淺出Mybatis原始碼系列(一)---Mybatis入門MyBatis原始碼
- 深入淺出解讀 Spring 原始碼:IOC/AOP 篇Spring原始碼
- 深入淺出URL編碼
- OKHttp原始碼解析HTTP原始碼
- OkHttp原始碼分析HTTP原始碼
- 跑馬燈帶你深入淺出TextView的原始碼世界TextView原始碼
- 深入淺出FE(十四)深入淺出websocketWeb
- Android主流三方庫原始碼分析(一、深入理解OKHttp原始碼)Android原始碼HTTP
- 深入OKHttp原始碼分析(一)----同步和非同步請求流程和原始碼分析HTTP原始碼非同步
- 基於React 原始碼深入淺出setState:setState非同步實現React原始碼非同步
- 深入淺出Spring原始碼,終於把學Spring原始碼的技巧吃透了!Spring原始碼
- OkHttp原始碼深度解析HTTP原始碼
- RxJava+Retrofit+OkHttp深入淺出-終極封裝三(檔案上傳)RxJavaHTTP封裝
- RxJava+Retrofit+OkHttp深入淺出-終極封裝五(資料持久化)RxJavaHTTP封裝持久化
- RxJava+Retrofit+OkHttp深入淺出-終極封裝七(異常處理)RxJavaHTTP封裝
- 原始碼淺入淺出 Java ConcurrentHashMap原始碼JavaHashMap
- 基於React 原始碼深入淺出setState:官方文件的啟示錄React原始碼
- Java併發:深入淺出AQS之獨佔鎖模式原始碼分析JavaAQS模式原始碼
- OkHttp3.0-原始碼分析HTTP原始碼
- 原始碼分析三:OkHttp—CacheInterceptor原始碼HTTP
- 原始碼分析三:OkHttp—CallServerInterceptor原始碼HTTPServer
- 原始碼分析三:OkHttp—RetryAndFollowUpInterceptor原始碼HTTP
- 原始碼分析筆記——OkHttp原始碼筆記HTTP
- okhttp get post 使用原始碼HTTP原始碼
- 徹底理解OkHttp - OkHttp 原始碼解析及OkHttp的設計思想HTTP原始碼
- 深入淺出——MVCMVC
- 深入淺出mongooseGo