OkHttp源码解析(一)——整体流程(上)

1、前言

这里的整体流程指的是从执行到响应的整体过程。在看过些许源码之后,我被这个框架迷住了。

2、从普通的get请求入手

一般来说,最简单的get请求是下面这种方式。

        Request request = new Request.Builder()
                .url("https://www.baidu.com/")
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

            }
        });

ok,我们知道了我们的请求是从okhttpclient的newcall方法入手。

  @Override public Call newCall(Request request) {
    return new RealCall(this, request);
  }

在newcall方式中,只是返回了一个realcall对象,那么,我们就看下RealCall的enqueue方法,看看如何插入请求的。

3、插入请求队列
  void enqueue(Callback responseCallback, boolean forWebSocket) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
  }

通过调用链调用到了client.dispatcher(),这个对象是什么呢?这是个分发器,用来管理和执行我们的众多请求的,这里调用分发器插入一个AsyncCall对象。

  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

这里通过判断,判断是加入执行队列执行还是加入准备队列(等待队列)。假设我们这里是执行。

4、执行请求

因为AsyncCall是实现了Runnable接口,并且是NamedRunnable的子类,因此,我们需要看下NamedRunnable内部的调用链才能知道这个时候要执行哪个方法。

  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }

从代码中,我们可以看到,执行了execute()方法。因此,我们看AsyncCall的execute方法。在这个方法里面,有这么一行代码。

Response response = getResponseWithInterceptorChain(forWebSocket);

所以,我们得继续看下去。

  private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
    Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
    return chain.proceed(originalRequest);
  }

调用ApplicationInterceptorChain#proceed方法获取Response返回结果。这里有一个很巧妙的设计。代码如下.

if (index < client.interceptors().size()) {
        Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
        Interceptor interceptor = client.interceptors().get(index);
        Response interceptedResponse = interceptor.intercept(chain);

        if (interceptedResponse == null) {
          throw new NullPointerException("application interceptor " + interceptor
              + " returned null");
        }

        return interceptedResponse;
      }

通过迭代的方式,将所有拦截器串联起来。反正最后会调用getResponse方法,这个方法很长,我们挑重点。

    /** 省略一堆代码 **/
    engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);
    /** 省略一堆代码 **/
    engine.sendRequest();
    engine.readResponse();
    /** 省略一堆代码 **/

这里的HttpEngine对象很重要,我们看他的sentRequest方法。其中有这么2句代码。

      httpStream = connect();
      httpStream.setHttpEngine(this);

我们猜测connect方法是创建链接的。

5、建立链接的过程

connect方法代码如下。

  private HttpStream connect() throws RouteException, RequestException, IOException {
    boolean doExtensiveHealthChecks = !networkRequest.method().equals("GET");
    return streamAllocation.newStream(client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis(),
        client.retryOnConnectionFailure(), doExtensiveHealthChecks);
  }

调用StreamAllocation的newStream方法,返回一个httpstream对象。
在这个方法当中,有这么一行代码,从名字上来看,这是真正的链接。

      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);

继续跟踪之后到了findConnect方法。在这个方法的最下面有这么一段代码。

    newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(),
        connectionRetryEnabled);

在RealConnection的connect方法中,调用buildConnection方法。

  private void buildConnection(int connectTimeout, int readTimeout, int writeTimeout,
      ConnectionSpecSelector connectionSpecSelector) throws IOException {
    connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
    establishProtocol(readTimeout, writeTimeout, connectionSpecSelector);
  }

在这个方法中,调用了connectSocket方法,到这里我们明白了,原来,okhttp是拿socket写的。在connecsocket方法中,

    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

这里的Address是什么呢?是什么时候初始化的?这个其实是在HttpEngine初始化的时候初始化的。我们看下HttpEngine的构造函数。

  public HttpEngine(OkHttpClient client, Request request, boolean bufferRequestBody,
      boolean callerWritesRequestBody, boolean forWebSocket, StreamAllocation streamAllocation,
      RetryableSink requestBodyOut, Response priorResponse) {
    this.client = client;
    this.userRequest = request;
    this.bufferRequestBody = bufferRequestBody;
    this.callerWritesRequestBody = callerWritesRequestBody;
    this.forWebSocket = forWebSocket;
    this.streamAllocation = streamAllocation != null
        ? streamAllocation
        : new StreamAllocation(client.connectionPool(), createAddress(client, request));
    this.requestBodyOut = requestBodyOut;
    this.priorResponse = priorResponse;
  }

看到,有个createAddress方法。而这个方法就初始化了一个Address对象,并且他的socketFactory对象就是okhttpclient中的socketfactory。

 socketFactory = SocketFactory.getDefault();

通过一些列的调用,到这里,创建了socket对象,并成功建立了链接。

6、请求头的写入过程

在HttpEngine的sendRequest方法中,有

httpStream.writeRequestHeaders(networkRequest);

这里就是写入请求头的入口。而我们的httpstream是什么呢,httpstream是在StreamAllocation的newStream方法中的到初始的。在这里有两种类型,Http1xStream和Http2xStream。分别对应http1.1和http2,spdy3.我们分开来说。

6.1、http1.1写入过程
  public void writeRequest(Headers headers, String requestLine) throws IOException {
    if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
    sink.writeUtf8(requestLine).writeUtf8("\r\n");
    for (int i = 0, size = headers.size(); i < size; i++) {
      sink.writeUtf8(headers.name(i))
          .writeUtf8(": ")
          .writeUtf8(headers.value(i))
          .writeUtf8("\r\n");
    }
    sink.writeUtf8("\r\n");
    state = STATE_OPEN_REQUEST_BODY;
  }

这里很简单,就是将我们的请求头一个一个以utf-8的格式写入sink中,这里的sink是我们在打开socket链接的时候,用okio包装后的。相应的代码在RealConnection的connectSocket方法中。

    source = Okio.buffer(Okio.source(rawSocket));
    sink = Okio.buffer(Okio.sink(rawSocket));
6.2、http2写入过程

Http2xStream#writeRequestHeaders中,有下面代码片段。

    List<Header> requestHeaders = framedConnection.getProtocol() == Protocol.HTTP_2
        ? http2HeadersList(request)
        : spdy3HeadersList(request);
    boolean hasResponseBody = true;
    stream = framedConnection.newStream(requestHeaders, permitsRequestBody, hasResponseBody);

先根据协议类型生成对应的请求头list,在调用FramedStream的newStream方法。在这个方法中,有这个一个代码片段。

frameWriter.synStream(outFinished, inFinished, streamId, associatedStreamId,
            requestHeaders);

而这里的framwWriter是什么呢?他的初始化过程是这样的

frameWriter = variant.newWriter(builder.sink, client);

variant对应的是Http2或者是spdy3对象,我这里用http2对象来说明。所以上面产生的frameWriter队形是Http2的内部类Writer的实例,因此会调用他的synStream方法,而这个方法内部也很简单,直到调用下面的方法。

    void headers(boolean outFinished, int streamId, List<Header> headerBlock) throws IOException {
      if (closed) throw new IOException("closed");
      hpackWriter.writeHeaders(headerBlock);

      long byteCount = hpackBuffer.size();
      int length = (int) Math.min(maxFrameSize, byteCount);
      byte type = TYPE_HEADERS;
      byte flags = byteCount == length ? FLAG_END_HEADERS : 0;
      if (outFinished) flags |= FLAG_END_STREAM;
      frameHeader(streamId, length, type, flags);
      sink.write(hpackBuffer, length);

      if (byteCount > length) writeContinuationFrames(streamId, byteCount - length);
    }

好的,我们看到了sink.write,那么,hpackBuffer就是处理过后的请求头了。好,我们现在看下这里的Writer的初始化过程。

    Writer(BufferedSink sink, boolean client) {
      this.sink = sink;
      this.client = client;
      this.hpackBuffer = new Buffer();
      this.hpackWriter = new Hpack.Writer(hpackBuffer);
      this.maxFrameSize = INITIAL_MAX_FRAME_SIZE;
    }

跟踪Hpack.Writew进去会发现,我们将buffer传给了out。那么我们现在看hpackWriter.writeHeaders(requestHeaders)也就是Hpack的内部类Writer的writeHeaders方法。在这里我们能看到将数据输入到out中,也就是我们的hpackBuffer。关于Hpack,这是一种请求头压缩算法,感兴趣的同学自己去学习吧。。。

7、下篇预告

下一篇将记录下整体流程的下一部分,请求体的写入以及响应过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值