OkHttp的實現原理(一)之同步
最近我做的一個專案的網路框架就是選用的OkHttp,僅僅只是呼叫一下Api當然是不夠的,想要駕馭它並靈活的運用則需要了解它的實現原理,那麼就需要去看它的原始碼了。
Okhttp有兩種請求方式:
1. 同步請求: execute();
2. 非同步請求 :
public void enqueue(Callback responseCallback) {
this.enqueue(responseCallback, false);
}
不管是同步請求還是非同步請求,最先開始的都是需要有一個Request物件,然後通過OkhttpClient例項會生成一個Call物件
(1) post請求 Request request = new Request.Builder().url(url).post(getRequestBody()).build();
get請求 Request request = new Request.Builder().url(url).get().build();
(2) Call call = client.newCall(request);
這兩步操作對於同步請求和非同步請求是相同的,當然你可以往request物件裡面加入請求頭引數,快取的設定等,這就看你自己的需求了。
我們先分析同步請求的方式:
public Response execute() throws IOException {
synchronized(this) {
if(this.executed) {
throw new IllegalStateException("Already Executed");
}
this.executed = true;
}
Response var2;
try {
this.client.getDispatcher().executed(this );
Response result = this.getResponseWithInterceptorChain(false);
if(result == null) {
throw new IOException("Canceled");
}
var2 = result;
} finally {
this.client.getDispatcher().finished(this);
}
return var2;
}
首先會先判斷這一次請求是否正在處理,如果是就丟擲一個異常,如果不是才往下走,可見OkHttp是不允許對一個還沒處理完的請求再次傳送請求的情況出現的。接下來會看到這段程式碼this.client.getDispatcher().executed(this);
看語意是得到了一個分發器,這個分發器是在new OkHttpClient物件的時候初始化的
public OkHttpClient() {
this.routeDatabase = new RouteDatabase();
this.dispatcher = new Dispatcher();
}
得到了分發器後呼叫executed(this);這個this就是我們一開始生成的Call物件,
synchronized void executed(Call call) {
this.executedCalls.add(call);
}
是將這個call物件新增到了executedCalls佇列中(底層使用陣列實現的),我們繼續往下面看
Response result = this.getResponseWithInterceptorChain(false);`
if(result == null) {
throw new IOException("Canceled");
}
var2 = result;
} finally {
this.client.getDispatcher().finished(this);
}
return var2;
這段程式碼是將result返回,看來這個result就是我們從伺服器拿到的資料,好吧,那麼這個getResponseWithInterceptorChain(false);
方法就很關鍵了啊
private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
Call.ApplicationInterceptorChain chain = new Call.ApplicationInterceptorChain(0, this.originalRequest, forWebSocket);
return chain.proceed(this.originalRequest);
}
首先構造了一個ApplicationInterceptorChain物件chain ,看這寫法就知道是Call的內部類,這個originalRequest其實就是我們之前的request物件,而forWebSocket = false 是我們呼叫這個方法的時候傳進去的,然後看proceed(this.originalRequest)這個方法,這個方法就是傳送請求並獲得響應的方法了。
public Response proceed(Request request) throws IOException {
if(this.index < Call.this.client.interceptors().size()) {
Call.ApplicationInterceptorChain chain = Call.this.new ApplicationInterceptorChain(this.index + 1, request, this.forWebSocket);
Interceptor interceptor = (Interceptor)Call.this.client.interceptors().get(this.index);
Response interceptedResponse = interceptor.intercept(chain);
if(interceptedResponse == null) {
throw new NullPointerException("application interceptor " + interceptor + " returned null");
} else {
return interceptedResponse;
}
} else {
return Call.this.getResponse(request, this.forWebSocket);
}
}
因為這個index一開始傳入的是0,所以先判斷okHttpClient物件中是否有攔截器,如果有,就首先建立一個新的ApplicationInterceptorChain物件,跟之前的比就是將index+1傳入,然後去interceptors集合裡面取出攔截器,通過 Response interceptedResponse = interceptor.intercept(chain);放大獲取響應並返回,這個方法是當你給OkHttpClient設定攔截器的時候會呼叫的方法,這個方法得由開發者自己去寫。如果沒有攔截器,那麼就會呼叫Call.this.getResponse(request, this.forWebSocket);這個方法去獲取響應了。
Response getResponse(Request request, boolean forWebSocket) throws IOException {
RequestBody body = request.body();
if(body != null) {
Builder followUpCount = request.newBuilder();
MediaType releaseConnection = body.contentType();
if(releaseConnection != null) {
followUpCount.header("Content-Type", releaseConnection.toString());
}
long response = body.contentLength();
if(response != -1L) {
followUpCount.header("Content-Length", Long.toString(response));
followUpCount.removeHeader("Transfer-Encoding");
} else {
followUpCount.header("Transfer-Encoding", "chunked");
followUpCount.removeHeader("Content-Length");
}
request = followUpCount.build();
}
this.engine = new HttpEngine(this.client, request, false, false, forWebSocket, (StreamAllocation)null, (RetryableSink)null, (Response)null);
int var20 = 0;
while(!this.canceled) {
boolean var21 = true;
boolean var15 = false;
StreamAllocation streamAllocation;
label173: {
label172: {
try {
HttpEngine followUp;
try {
var15 = true;
this.engine.sendRequest();
this.engine.readResponse();
var21 = false;
var15 = false;
break label173;
} catch (RequestException var16) {
throw var16.getCause();
} catch (RouteException var17) {
followUp = this.engine.recover(var17);
if(followUp == null) {
throw var17.getLastConnectException();
}
} catch (IOException var18) {
followUp = this.engine.recover(var18, (Sink)null);
if(followUp != null) {
var21 = false;
this.engine = followUp;
var15 = false;
break label172;
}
throw var18;
}
var21 = false;
this.engine = followUp;
var15 = false;
} finally {
if(var15) {
if(var21) {
StreamAllocation streamAllocation1 = this.engine.close();
streamAllocation1.release();
}
}
}
if(var21) {
streamAllocation = this.engine.close();
streamAllocation.release();
}
continue;
}
if(var21) {
streamAllocation = this.engine.close();
streamAllocation.release();
}
continue;
}
if(var21) {
StreamAllocation var23 = this.engine.close();
var23.release();
}
Response var22 = this.engine.getResponse();
Request var24 = this.engine.followUpRequest();
if(var24 == null) {
if(!forWebSocket) {
this.engine.releaseStreamAllocation();
}
return var22;
}
streamAllocation = this.engine.close();
++var20;
if(var20 > 20) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + var20);
}
if(!this.engine.sameConnection(var24.httpUrl())) {
streamAllocation.release();
streamAllocation = null;
}
this.engine = new HttpEngine(this.client, var24, false, false, forWebSocket, streamAllocation, (RetryableSink)null, var22);
}
this.engine.releaseStreamAllocation();
throw new IOException("Canceled");
}
首先判斷該請求是否是Post請求,如果是,就會對該request物件做一些處理,隨後會建立一個HttpEngine物件,將okHttpClient物件和request物件forWebSocket = false作為引數傳入進去了。
this.engine = new HttpEngine(this.client, request, false, false, forWebSocket, (StreamAllocation)null, (RetryableSink)null, (Response)null);
之後會進入一個迴圈,進入該迴圈的條件就是該請求沒有被取消掉,
this.engine.sendRequest();
this.engine.readResponse();
這兩句程式碼是不是很好理解啊,不就是engine傳送了請求,然後讀響應嗎,然後會跳出標籤label173,接著看92行程式碼 Response var22 = this.engine.getResponse();好傢伙,這裡就得到響應了,你肯定不爽,怎麼這麼快就傳送請求和獲得響應了啊?都還沒看到sendRequest(),getResponse()這兩個方法內部做了什麼操作呢,好吧,那麼我必須要來滿足你:
public void sendRequest() throws RequestException, RouteException, IOException {
if(this.cacheStrategy == null) {
if(this.httpStream != null) {
throw new IllegalStateException();
} else {
Request request = this.networkRequest(this.userRequest);
InternalCache responseCache = Internal.instance.internalCache(this.client);
Response cacheCandidate = responseCache != null?responseCache.get(request):null;
long now = System.currentTimeMillis();
this.cacheStrategy = (new Factory(now, request, cacheCandidate)).get();
this.networkRequest = this.cacheStrategy.networkRequest;
this.cacheResponse = this.cacheStrategy.cacheResponse;
if(responseCache != null) {
responseCache.trackResponse(this.cacheStrategy);
}
if(cacheCandidate != null && this.cacheResponse == null) {
Util.closeQuietly(cacheCandidate.body());
}
if(this.networkRequest != null) {
this.httpStream = this.connect();
this.httpStream.setHttpEngine(this);
if(this.callerWritesRequestBody && this.permitsRequestBody(this.networkRequest) && this.requestBodyOut == null) {
long contentLength = OkHeaders.contentLength(request);
if(this.bufferRequestBody) {
if(contentLength > 2147483647L) {
throw new IllegalStateException("Use setFixedLengthStreamingMode() or setChunkedStreamingMode() for requests larger than 2 GiB.");
}
if(contentLength != -1L) {
this.httpStream.writeRequestHeaders(this.networkRequest);
this.requestBodyOut = new RetryableSink((int)contentLength);
} else {
this.requestBodyOut = new RetryableSink();
}
} else {
this.httpStream.writeRequestHeaders(this.networkRequest);
this.requestBodyOut = this.httpStream.createRequestBody(this.networkRequest, contentLength);
}
}
} else {
this.streamAllocation.release();
if(this.cacheResponse != null) {
this.userResponse = this.cacheResponse.newBuilder().request(this.userRequest).priorResponse(stripBody(this.priorResponse)).cacheResponse(stripBody(this.cacheResponse)).build();
} else {
this.userResponse = (new Builder()).request(this.userRequest).priorResponse(stripBody(this.priorResponse)).protocol(Protocol.HTTP_1_1).code(504).message("Unsatisfiable Request (only-if-cached)").body(EMPTY_BODY).build();
}
this.userResponse = this.unzip(this.userResponse);
}
}
}
}
6-19行:獲取使用者設定的快取策略。
21-41行:需要從網路上獲取資料。
43 - 50行:從快取中獲取資料。這個userResponse 就是快取從中的拿到的資料。
我們主要看從網路上獲取資料這部分內容,要從伺服器上獲取內容首先要跟伺服器建立起連線吧,那麼我們來看22行connet();
private HttpStream connect() throws RouteException, RequestException, IOException {
boolean doExtensiveHealthChecks = !this.networkRequest.method().equals("GET");
return this.streamAllocation.newStream(this.client.getConnectTimeout(), this.client.getReadTimeout(), this.client.getWriteTimeout(), this.client.getRetryOnConnectionFailure(), doExtensiveHealthChecks);
}
doExtensiveHealthChecks = false 說明該請求是GET請求,doExtensiveHealthChecks = ture 說明是POST請求,newStream(this.client.getConnectTimeout(), this.client.getReadTimeout(), this.client.getWriteTimeout(), this.client.getRetryOnConnectionFailure(), doExtensiveHealthChecks);這個方法就是建立連線的方法,讓我們看一下:
public HttpStream newStream(int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks) throws RouteException, IOException {
try {
RealConnection e = this.findHealthyConnection(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
Object resultStream;
if(e.framedConnection != null) {
resultStream = new Http2xStream(this, e.framedConnection);
} else {
e.getSocket().setSoTimeout(readTimeout);
e.source.timeout().timeout((long)readTimeout, TimeUnit.MILLISECONDS);
e.sink.timeout().timeout((long)writeTimeout, TimeUnit.MILLISECONDS);
resultStream = new Http1xStream(this, e.source, e.sink);
}
ConnectionPool var8 = this.connectionPool;
synchronized(this.connectionPool) {
++e.streamCount;
this.stream = (HttpStream)resultStream;
return (HttpStream)resultStream;
}
} catch (IOException var11) {
throw new RouteException(var11);
}
}
RealConnection 這個就是真的連線物件了吧,findHealthyConnection這個方法裡面會建立連線物件,然後建立連線會呼叫newConnection.connect(connectTimeout, readTimeout, writeTimeout, this.address.getConnectionSpecs(), connectionRetryEnabled);這個方法,最後會返回這個連線物件就是RealConnection e這個物件了,讓我們看看他是如何建立連線的吧
public void connect(int connectTimeout, int readTimeout, int writeTimeout, List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {
if(this.protocol != null) {
throw new IllegalStateException("already connected");
} else {
RouteException routeException = null;
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
Proxy proxy = this.route.getProxy();
Address address = this.route.getAddress();
if(this.route.getAddress().getSslSocketFactory() == null && !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
throw new RouteException(new UnknownServiceException("CLEARTEXT communication not supported: " + connectionSpecs));
} else {
while(this.protocol == null) {
try {
this.rawSocket = proxy.type() != Type.DIRECT && proxy.type() != Type.HTTP?new Socket(proxy):address.getSocketFactory().createSocket();
this.connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
} catch (IOException var11) {
Util.closeQuietly(this.socket);
Util.closeQuietly(this.rawSocket);
this.socket = null;
this.rawSocket = null;
this.source = null;
this.sink = null;
this.handshake = null;
this.protocol = null;
if(routeException == null) {
routeException = new RouteException(var11);
} else {
routeException.addConnectException(var11);
}
if(!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(var11)) {
throw routeException;
}
}
}
}
}
}
Address address = this.route.getAddress();獲得需要連線到伺服器的地址,第14行,建立了Socket物件,所以okHttp是Socket來進行連線的。有了Socket就可以獲取輸入輸出流了,建立了了連線以後你會發現會走下面兩句程式碼:
this.httpStream.writeRequestHeaders(this.networkRequest);
this.requestBodyOut = this.httpStream.createRequestBody(this.networkRequest, contentLength);
httpStream這個物件是什麼呢?我們知道http協議網路資料通訊,其實就是客戶端將請求資料以請求報文的格式傳送給伺服器,伺服器獲取請求後,執行相應的處理,然後將返回的結果以響應報文的格式返回給客戶端,httpStream就是用來寫請求報文和讀取響應報文的。
我們再來看看engine.getResponse();獲取響應的方法:
public Response getResponse() {
if(this.userResponse == null) {
throw new IllegalStateException();
} else {
return this.userResponse;
}
}
就是將userResponse返回了,userResponse是什麼呢?我們還記得如果是從快取中拿的資料,那麼
if(this.cacheResponse != null) {
this.userResponse = this.cacheResponse.newBuilder().request(this.userRequest).priorResponse(stripBody(this.priorResponse)).cacheResponse(stripBody(this.cacheResponse)).build();
} else {
this.userResponse = (new Builder()).request(this.userRequest).priorResponse(stripBody(this.priorResponse)).protocol(Protocol.HTTP_1_1).code(504).message("Unsatisfiable Request (only-if-cached)").body(EMPTY_BODY).build();
}
this.userResponse = this.unzip(this.userResponse);
}
此時userResponse 確實被賦值了,但是如果從網路上請求資料,那麼這個userResponse 是什麼時候被賦值的呢?不知道大家對 this.engine.readResponse();感覺到奇怪沒有,它在傳送請求之後,獲取響應之前呼叫,我一開始就得挺奇怪的,我以為這個方法就是獲取響應的方法,但是它沒有返回值啊,它到底是來幹嘛的呢?只有看原始碼了
public void readResponse() throws IOException {
if(this.userResponse == null) {
if(this.networkRequest == null && this.cacheResponse == null) {
throw new IllegalStateException("call sendRequest() first!");
} else if(this.networkRequest != null) {
Response networkResponse;
if(this.forWebSocket) {
this.httpStream.writeRequestHeaders(this.networkRequest);
networkResponse = this.readNetworkResponse();
} else if(!this.callerWritesRequestBody) {
networkResponse = (new HttpEngine.NetworkInterceptorChain(0, this.networkRequest)).proceed(this.networkRequest);
} else {
if(this.bufferedRequestBody != null && this.bufferedRequestBody.buffer().size() > 0L) {
this.bufferedRequestBody.emit();
}
if(this.sentRequestMillis == -1L) {
if(OkHeaders.contentLength(this.networkRequest) == -1L && this.requestBodyOut instanceof RetryableSink) {
long responseCache = ((RetryableSink)this.requestBodyOut).contentLength();
this.networkRequest = this.networkRequest.newBuilder().header("Content-Length", Long.toString(responseCache)).build();
}
this.httpStream.writeRequestHeaders(this.networkRequest);
}
if(this.requestBodyOut != null) {
if(this.bufferedRequestBody != null) {
this.bufferedRequestBody.close();
} else {
this.requestBodyOut.close();
}
if(this.requestBodyOut instanceof RetryableSink) {
this.httpStream.writeRequestBody((RetryableSink)this.requestBodyOut);
}
}
networkResponse = this.readNetworkResponse();
}
this.receiveHeaders(networkResponse.headers());
if(this.cacheResponse != null) {
if(validate(this.cacheResponse, networkResponse)) {
this.userResponse = this.cacheResponse.newBuilder().request(this.userRequest).priorResponse(stripBody(this.priorResponse)).headers(combine(this.cacheResponse.headers(), networkResponse.headers())).cacheResponse(stripBody(this.cacheResponse)).networkResponse(stripBody(networkResponse)).build();
networkResponse.body().close();
this.releaseStreamAllocation();
InternalCache responseCache1 = Internal.instance.internalCache(this.client);
responseCache1.trackConditionalCacheHit();
responseCache1.update(this.cacheResponse, stripBody(this.userResponse));
this.userResponse = this.unzip(this.userResponse);
return;
}
Util.closeQuietly(this.cacheResponse.body());
}
this.userResponse = networkResponse.newBuilder().request(this.userRequest).priorResponse(stripBody(this.priorResponse)).cacheResponse(stripBody(this.cacheResponse)).networkResponse(stripBody(networkResponse)).build();
if(hasBody(this.userResponse)) {
this.maybeCache();
this.userResponse = this.unzip(this.cacheWritingResponse(this.storeRequest, this.userResponse));
}
}
}
}
6 - 38 行 : 根據不同的條件獲取響應networkResponse 。
42 - 60行 :就是真正的對userResponse 進行賦值操作了。
好了,到這裡有關OkHttp的同步請求方式的原始碼就全部分析完了,下一次我將繼續分析OkHttp非同步請求的原理,最後我想用一張圖來簡單的總結一下同步請求的流程: