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; }

【OkHttp源码解析(一)——整体流程(上)】看到,有个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 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 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、下篇预告 下一篇将记录下整体流程的下一部分,请求体的写入以及响应过程。

    推荐阅读