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