关于OkHttp3源码分析

OkHttp3可以说很目前很流行的网路访问框架了,它可以帮我实现重定向访问,而且其还有缓存机制能够帮我们减少与服务器的交互,它还支持gzip格式的response,这能够有效的帮我们节省流量。从以上可以看出OkHttp好处是大大的多,而OkHttp从使用上来说也是很简单的,即可以通过execute方法完成同步网络访问,通过enqueue方法来完成异步网络访问。

这边文章的目的并不是想对OkHttp3的各个相关文件做一个概括,而是作者因为好奇OkHttp3底层到底是如何实现网络访问的,从而对其底层实现网络访问机制这个问题做的一次探索。我们知道OkHttp3是依赖Okio包的,那么这个Okio包在OkHttp3实现网络访问的过程中起到了什么样的作用呢,这里先给出结论吧。

1、Okio包在OkHttp3中起到通过Buffer缓存发送以及接收到的数据;
2、OkHttp3底层通过Okio包以Socket的方式来进行网络访问;

下面我们就一步步的证明以上的两个结论吧

1、Http3的基本使用

为了研究问题,我们先看看一个简单的OkHttp3的使用,这里列举一个OkHttp3的同步post请求方法

String url = "http://192.168.8.136:8081/test/post";
OkHttpClient httpClient = new OkHttpClient();
RequestBody body = new FormBody.Builder().add("name", "yoryky").add("age", "12").build();
Request request = new Request.Builder().url(url).post(body).header("Cookie","{'name':'yoryky','age':'12'").build();
Call call = httpClient.newCall(request);
try {
    Response response = call.execute();
    System.out.println("status_code:" + response.code() + response.body().string());
} catch (Exception e) {
    e.printStackTrace();
}

从这里可以看出,OkHttp3通过call.execute来触发网络访问,并最终返回Response数据。

2、OkHttp的优点说明

第一小节的call是RealCall的实例,call.execute方法会调用到RealCall.getResponse方法中去,而该方法中有两行代码

engine.sendRequest();
engine.readResponse();

这两行代码就是OkHttp3的核心代码了,这里的engine变量是HttpEngine的实例。sendRequest()方法是client向server发送request的主要方法。它先对request的header添加了一些默认字段,如keep-alive。然后对cache进行处理,判断是否可以直接使用cache,如果不行,才真正发送网络request请求。这这一点可以从HttpEngine.sendRequest方法中的部分代码加以说明

if (networkRequest == null) {
  userResponse = cacheResponse.newBuilder()
      .request(userRequest)
      .priorResponse(stripBody(priorResponse))
      .cacheResponse(stripBody(cacheResponse))
      .build();
  userResponse = unzip(userResponse);
  return;
}

从代码中可以看出当有缓存时,直接从缓存中取数据,然后就返回了,而且这里可以unzip方法中的以下代码看出OkHttp3支持gzip格式。

GzipSource responseBody = new GzipSource(response.body().source());

这里的GzipSource是okio.Source接口的子类,当设置通过gzip方式来进行网络访问时,在获取服务器返回数据时会通过GzipSrource.read中以对应编码格式来返回response数据。

而在HttpEngine.networkRequest方法中可以看到OkHttp3默认是长连接的

if (request.header("Connection") == null) {
   result.header("Connection", "Keep-Alive");
}

3、HttpEngine.sendRequest方法

上面提到sendRequest方法是OkHttp3的核心代码之一,那么这一小节就来说说它大概干了些什么事情。这里还是先说出观点吧。

1、sendRequest方法主要功能是初始化请求头并与服务器建立连接。

在阅读这里要讲解的内容以及后面的内容之前,建议大家先看看Socket在Java中的使用这篇文章吧,后面讲的内容都会结合着这篇文章中的内容来说。

这里来看HttpEngine.sendRequest代码,这里就不贴具体的代码了,童鞋们对比着看自己的AS上的代码即可(这里用的okhttp3版本号为3.3.0,okio版本号我1.8.0,如果版本号不一致那么代码有可能不一样)。

sendRequest先是如上一小节所说的先是判断缓存,如果无缓存或者缓存过期时,便重新通过创建Socket来连接服务器。sendRequest同服务器建立连接的调用堆栈为

这里写图片描述

从调用堆栈可以最终看出会调用到RealConnection.connectSocket方法,该方法中先创建了一个Socket变量

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

rawSocket.setSoTimeout(readTimeout);

由“Socket在Java中的使用”文章我们知道创建并初始化Socket的过程就是与服务器建立连接的过程,于是这里客户端便与服务器端建立好了连接。然后在connectSocket方法中另外两行特别重要的代码

source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));

这里的source是RealBufferedSource实例,sink是RealBufferedSink。我们可以这样理解这里的sink和source : sink相当于“Socket在Java中的使用”文章中的OutputStream,而source相当于InputStream(不懂为什么的先这里理解着,看完下面的内容,就能明白为什么能这么理解)。于是我们便可以同故宫sink的write方法来写服务器数据,根据source来读取服务器数据。

这里还是要特别说明一点,HttpEngine.sendRequest方法作用只是创建Socket并实例化sink和source,其并没有进行与服务器的读写操作,那么读写操作实在什么地方进行的呢,这个就是下一小节要讲的内容了。

4、HttpEngine.readResponse方法

HttpEngine的readResponse方法其实实现了两个功能,一个是向服务器写数据,一个是获取服务器返回的数据。

向服务器写数据

这里我们通过一张堆栈图来说明向服务器写数据的具体过程。

这里写图片描述

我们看到HttpEngine.readResponse方法会调用到RealBufferedSink的flush方法

@Override public void flush() throws IOException {
  if (closed) throw new IllegalStateException("closed");
  if (buffer.size > 0) {
    sink.write(buffer, buffer.size);
  }
  sink.flush();
}

这里的buffer变量中是Okio包中的Buffer类的实例,其中存放着包括要发送的请求头信息以及body数据,这一点可以从以下图片中看出

这里写图片描述

那么buffer中的数据又是在什么时候,添加进去的呢,实际上是通过HttpEngine.proceed方法中的下面代码来往buffer中添加请求头部和body数据的的

httpStream.writeRequestHeaders(request);

//Update the networkRequest with the possibly updated interceptor request.
networkRequest = request;

if (permitsRequestBody(request) && request.body() != null) {
  Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
  BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
  request.body().writeTo(bufferedRequestBody);
  bufferedRequestBody.close();
}

其中httpStream.writeRequestHeaders(request)是添加头部数据,而request.body().writeTo(bufferedRequestBody);方法是添加body数据,当然具体深入的代码逻辑需要小伙伴们自己去梳理下,实际上也不是很难的,跟着代码调试下就知道了,这里不深入(要不然不知道要写到什么时候去了)。

现在buffer数据有了,sink也有了,接着看sink是怎么向服务器发送数据的sink.write(buffer, buffer.size)这个代码实际上会先调用到AysncTimeout的Sink类的write方法中去,然后才调用到Okio.wirte方法,至于为什么是这个调用逻辑,请去看看RealConnnection.connectSocket中的

sink = Okio.buffer(Okio.sink(rawSocket));

这段代码,为什么要看这段,因为RealBufferedSink中的sink变量就是这里的sink,至于为什么是这种关系,请去梳理本小节的堆栈图。

到这里我们知道了,OkHttp3向服务器写数据,最终会调用到Okio.write方法中来,接着看Okio.write方法

@Override public void write(Buffer source, long byteCount) throws IOException {
  checkOffsetAndCount(source.size, 0, byteCount);
  while (byteCount > 0) {
    timeout.throwIfReached();
    Segment head = source.head;
    int toCopy = (int) Math.min(byteCount, head.limit - head.pos);
    out.write(head.data, head.pos, toCopy);

    head.pos += toCopy;
    byteCount -= toCopy;
    source.size -= toCopy;

    if (head.pos == head.limit) {
      source.head = head.pop();
      SegmentPool.recycle(head);
    }
  }
}

注意到这里的out变量,还是要看下面代码

sink = Okio.buffer(Okio.sink(rawSocket));

这个代码在实例化sink的过程中会调用Okio.sink

public static Sink sink(Socket socket) throws IOException {
  if (socket == null) throw new IllegalArgumentException("socket == null");
  AsyncTimeout timeout = timeout(socket);
  Sink sink = sink(socket.getOutputStream(), timeout);
  return timeout.sink(sink);
}

这里的Sink sink = sink(socket.getOutputStream(), timeout)代码中的socket.getOutputSteam就是上面的out变量。所以对比“Socket在Java中的使用”这篇文章,我们就能理解为什么out.write(head.data, head.pos, toCopy)这条语句就能够向服务器发送数据了。

其实到这里已经知道刚看时抛出的两条结论了

1、Okio包在OkHttp3中起到通过Buffer缓存发送以及接收到的数据;
2、OkHttp3底层通过Okio包以Socket的方式来进行网络访问;

但是写文章要写全,在看看从服务器获取数据吧

从服务器获取数据

获取数据还是看Okio读数据的调动堆栈

这里写图片描述

我们看到读数据的调用堆栈还是从HttpEngine.readResponse进去的,后面会去到RealBufferedSource类的indexof方法,然后调用AsyncTimeout的read方法,最后还是调用到了Okio.read方法,至于为什么会调用到Okio.read,也要去看RealConnection.connectSocket中的以下代码

 source = Okio.buffer(Okio.source(rawSocket));

RealBufferedSource中的source变量,就是这个source。再看Okio.read方法

@Override public long read(Buffer sink, long byteCount) throws IOException {
  if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
  if (byteCount == 0) return 0;
  try {
    timeout.throwIfReached();
    Segment tail = sink.writableSegment(1);
    int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit);
    int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
    if (bytesRead == -1) return -1;
    tail.limit += bytesRead;
    sink.size += bytesRead;
    return bytesRead;
  } catch (AssertionError e) {
    if (isAndroidGetsocknameError(e)) throw new IOException(e);
    throw e;
  }
}

这里有个in变量,这个in变量是通过socket.getInputStream()实例化的,对应“Socket在Java中的使用”文章中的is变量,我们也能够理解为什么OkHttp3能够能服务器端获取数据了。

5、总结

文章写了一大篇,最后还是来总结下吧。

1、OkHttp3使用起来简单,但是实际上其原理代码十分复杂,这也使得OkHttp3功能很强大,包括缓存、gzip、长连接、重定向等。

2、OkHttp3原理复杂,但是它的主要功能就是网络访问,网络访问就是要完成读写功能,于是我们只需要好好把握HttpEngine中的sendRequest作用和readResponse作用即可,即sendRequest完成初始化,并通过Socket方式建立同服务器的网络连接,而readResponse通过Socket完成读写功能。

3、OkHttp3依赖于Okio,主要体现在网络访问数据放在okio.Buffer中,通过okio.Okio write方法写服务器数据,通过okio.Okio read方法读取服务器返回数据。

6、参考文献

以下几篇参考文献,有些是参考了的,有些是我觉得写的比较好的,都贴出来吧

1、[Socket在Java中的使用](http://blog.csdn.net/yoryky/article/details/78557801)

2、[OKHttp源码分析3 - HttpEngine底层实现](http://blog.csdn.net/u013510838/article/details/52431516)

3、[OkHttp3源码分析\[缓存策略\]](http://www.jianshu.com/p/9cebbbd0eeab)

4、[UrlConnection连接和Socket连接的区别](http://blog.csdn.net/u013378580/article/details/51770124)

7、源码Demo

提供一个自己平时写着玩的Demo地址吧,Android Demo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值