天天看點

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來實作讀寫伺服器資料。

繼續閱讀