1. 程式人生 > >OkHttp的實現原理(一)之同步

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();

(2Call 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非同步請求的原理,最後我想用一張圖來簡單的總結一下同步請求的流程:

這裡寫圖片描述