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异步请求的原理,最后我想用一张图来简单的总结一下同步请求的流程:

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值