OkHttp的Okio在CacheInterceptor中的应用

目录

Okio的诞生

OKio的简单介绍

缓存模块

超时机制

几个重要的类

简单的读写操作

一个简单的java+socket来实现请求服务器

在CacheInterceptor的运用

1)写请求的头部header

2)写请求体body的数据

3)向服务器发送数据

4)接收服务器的返回的头部header

5)读取服务器返回的response的body数据

总结


Okio的诞生

Okio是用来对数据进行存储和处理IO数据。

InputStream/

OutputStream

传统的IO,阻塞式IO操作,一直等到有数据才会返回。
NIO非阻塞式IO。数据从通道(Channel)读取到缓冲区(Buffer),也可以从Buffer读取到Channel中。而Selector允许单线程处理多个通道,用来选出一个可用的通道。
Okio增加了缓存机制、超时机制来实现快速访问、存储和处理IO数据

OKio的简单介绍

缓存模块

由Buffer、Segment、SegmentPool组成。Buffer采用的是有Segment组成的循环链表,来缓存数据;SegmentPool存放的是暂时不用的Segment的单链表,防止频繁进行删除数据操作

超时机制

1)Timeout

在处理InputStream和OutputStream时,传入的超时类,在使用Okio进行读写操作的时候,如果超时就通timeout.throwIfReached()抛出异常。

2)AsyncTimeout

异步超时类,通过一个线程在后台监听Socket是否有超时的操作。

几个重要的类

1)Source/Sink:

接口类。代表输入流和输出流,类似于InputStream/OutputStream,用来进行读写数据;

2)BufferedSource/BufferedSink:

分别继承Source/Sink,扩展了读写功能;

3)RealBufferedSource/RealBufferedSink:

BufferedSource/BufferedSink的实现类,用来完成数据的读写操作。查看里面的源码中可以发现,其实里面就是含有一个Source/Sink对象的代理和一个Buffer对象,真正的去完成读写操作的就是这个Buffer。

简单的读写操作

在使用Okio在进行读写文件的时候,首先要将文件转换成一个Source/Sink,然后在换成BufferedSource/BufferedSink,最后通过API直接读写数据

//获取文件
File file = new File(fileName);
//将file转换成Source
Source source = Okio.source(file);
//将source转换成BufferedSource
BufferedSource buff = Okio.buffer(source);
//读取BufferedSource里面的内容
String result = buff.readString(Charset.forName("utf-8"));

一个简单的java+socket来实现请求服务器

在用java的Socket来进行实现请求服务器的过程,向服务器发送数据就是从socket去取出OutputStream,然后将请求数据写入OutputStream;同样读取服务器返回的数据时,就是从socket中取出InputStream,然后从InputStream数据中读取即可。下面是简单的代码实例

1)向服务器发送数据

//向服务器发送数据
OutputStream outputStream = socket.getOutputStream();
outputStream.write("写数据".getBytes("UTF-8"));
outputStream.close();

2)接收服务器返回的数据

BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//然后就等着服务器发送过数据之后,读取服务器发来的信息
 String line = null;
 StringBuffer buffer = new StringBuffer();
while ((line = bufferedReader.readLine()) != null) {
       buffer.append(line);
}

在CacheInterceptor的运用

1)写请求的头部header

  httpCodec.writeRequestHeaders(request);

这个httpCodec就是Http1Codec或者Http2Codec的实例。为了方便描述,我们拿Http1Codec举例说明。进入到Http1Codec源码中可以看到,最终调用的是下面的这个方法:

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

其实就是往sink里面写入了请求头header的数据。 而这个sink是在RealConnection中创建链接通道的时,在实例化Http1Codec的时候,从RealConnection中传入的,而sink的实例化也在RealConnection中。

private void connectSocket(int connectTimeout, int readTimeout, Call call,
      EventListener eventListener) throws IOException {
//...代码省略
    source = Okio.buffer(Okio.source(rawSocket));
    sink = Okio.buffer(Okio.sink(rawSocket));
//...代码省略
}

进入到Okio中的sink()方法中,可以看到

  public static Sink sink(Socket socket) throws IOException {
    //.....代码省略
    Sink sink = sink(socket.getOutputStream(), timeout);
    return timeout.sink(sink);
  }

就是从socket中获取输出流进行转换成Sink,在经过buffer(),最终返回的是RealBufferedSink

  public static BufferedSink buffer(Sink sink) {
    return new RealBufferedSink(sink);
  }

所以我们在Http1Codec中的sink其实就是RealBufferedSink的对象实例。我们往sink中写入信息,其实就是写到了Buffer的缓存中。

 

2)写请求体body的数据

CountingSink requestBodyOut =
     new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();

和写header的方式一样,先通过httpCodec.createRequestBody()来获取到请求body的数据,然后将数据转换成Sink,再将sink转换成BufferedSink,然后就是调用request().body().writeTo()方法进行将请求体body写入Buffer缓存中。看下request().body()返回的值body该对象就是在通过建造者模式创建Request的时候,在build()方法

  Request build() {
//.....代码省略
    RequestBody body = this.body;
    if (body == null) {
      // Try to pull from one of the builders.
      if (formBuilder != null) {
        body = formBuilder.build();
      } else if (multipartBuilder != null) {
        body = multipartBuilder.build();
      } else if (hasBody) {
        // Body is absent, make an empty body.
        body = RequestBody.create(null, new byte[0]);
      }
    }
//.....代码省略
 }

假设返回的是MultipartBody,那么最终调用的就是MultipartBody里面的writeTo()方法,进入到源码中可以看到也就是调用sink.write()来将body写入到Buffer的缓存中。

3)向服务器发送数据

 httpCodec.finishRequest();

真正的将数据发送给服务器。进入到Http1Codec看下finishRequest()源码:

  @Override public void finishRequest() throws IOException {
    sink.flush();
  }

调用的是RealBufferedSink的flush(),进入到RealBufferedSink中查看源码

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

最终调用的是的就是RealBufferedSink的write()

  @Override public void write(Buffer source, long byteCount)
      throws IOException {
    if (closed) throw new IllegalStateException("closed");
    buffer.write(source, byteCount);
    emitCompleteSegments();
  }

将请求信息写入到OutputStream中,完成将数据发送给服务器。

4)接收服务器的返回的头部header

    if (responseBuilder == null) {
      realChain.eventListener().responseHeadersStart(realChain.call());
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

进入Http1Codec源码中查看

 @Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
 //......省略代码
    try {
      StatusLine statusLine = StatusLine.parse(readHeaderLine());
      Response.Builder responseBuilder = new Response.Builder()
          .protocol(statusLine.protocol)
          .code(statusLine.code)
          .message(statusLine.message)
          .headers(readHeaders());
 //......省略代码
  }

通过readHeaderLine()将服务器返回的数据转换成StatusLine。其中通过source将返回的数据读出,而source的传入同1)中提到的sink的方式一样,都是在RealConnection中实例化时传入的。

  private String readHeaderLine() throws IOException {
    String line = source.readUtf8LineStrict(headerLimit);
    headerLimit -= line.length();
    return line;
  }

查看Okio中的Okio.source()的源码可以发现

  public static Source source(Socket socket) throws IOException {
//......省略代码
    AsyncTimeout timeout = timeout(socket);
    Source source = source(socket.getInputStream(), timeout);
    return timeout.source(source);
  }

该source()就是从socket的inputStream中读取数据。

5)读取服务器返回的response的body数据

response = response.newBuilder()
      .body(httpCodec.openResponseBody(response))
      .build();

进入到源码中发现,在Http1Codec中的openResponseBody(),就是根据不同的条件返回RealResponseBody的对象。

  return new RealResponseBody(contentType, contentLength, Okio.buffer(source));

总结

Okio提供了阻塞IO和非阻塞IO的功能,同时增加了缓存和超时机制。在HttpCodec中通过Okio来实现读写服务器数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值