从前面的 OkHttp 源码解析中我们可以知道,OkHttp 中的 I/O 都不是通过我们平时所使用的 IOStream 来实现,而是使用了 Okio 这个第三方库,那它与寻常的 IOStream 有什么区别呢?让我们来分析一下它的源码。
Okio 中有两个非常重要的接口——Sink
以及 Source
,它们都继承了 Closeable
,其中 Sink
对应了我们原来所使用的 OutputStream
,而 Source
则对应了我们原来所使用的 InputStream
。
Okio 的入口就是Okio
类,它是一个工厂类,可以通过它内部的一些 static 方法来创建 Sink
、Source
等对象。
Sink
Sink
实际上只是一个接口,让我们看看 Sink
中有哪些方法:
public interface Sink extends Closeable, Flushable {
void write(Buffer source, long byteCount) throws IOException;
@Override void flush() throws IOException;
Timeout timeout();
@Override void close() throws IOException;
}
可以看到,它主要包含了 write
、flush
、timeout
、close
这几个方法,我们可以通过 Okio.sink
方法基于 OutputStream
获取一个 Sink
:
private static Sink sink(final OutputStream out, final Timeout timeout) {
if (out == null) throw new IllegalArgumentException("out == null");
if (timeout == null) throw new IllegalArgumentException("timeout == null");
return new Sink() {
@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);
}
}
}
@Override public void flush() throws IOException {
out.flush();
}
@Override public void close() throws IOException {
out.close();
}
@Override public Timeout timeout() {
return timeout;
}
@Override public String toString() {
return "sink(" + out + ")";
}
};
}
这里构建并实现了一个 Sink
的匿名内部类并返回,主要实现了它的 write
方法,剩余方法都是简单地转调到 OutputStream
的对应方法。
在 write
方法中,首先进行了一些状态检验,这里貌似在 Timeout
类中实现了对超时的处理,我们稍后再分析。之后从 Buffer
中获取了一个 Segment
,并从中取出数据,计算出写入的量后将其写入 Sink
所对应的 OutputStream
。
Segment
采用了一种类似链表的形式进行连接,看来 Buffer
中维护了一个 Segment
链表,代表了数据的其中一段。这里将 Buffer
中的数据分段取出并写入了 OutputStream
中。
最后,通过 SegmentPool.recycle
方法对当前 Segment
进行回收。
从上面的代码中我们可以获取到如下信息:
Buffer
其实就是内存中的一段数据的抽象,其中通过Segment
以链表的形式保存用于存储数据。Segment
存储数据采用了分段的存储方式,因此获取数据时需要分段从Segment
中获取数据。- 有一个
SegmentPool
池用于实现Segment
的复用。 Segment
的使用有点类似链表。
Source
Source
与 Sink
一样,也仅仅是一个接口:
public interface Source extends Closeable {
long read(Buffer sink, long byteCount) throws IOException;
Timeout timeout();
@Override void close() throws IOException;
}
在 Okio
中可以通过 source
方法根据 InputStream
创建一个 Source
:
private static Source source(final InputStream in, final Timeout timeout) {
if (in == null) {
throw new IllegalArgumentException("in == null");
} else if (timeout == null) {
throw new IllegalArgumentException("timeout == null");
} else {
return new Source() {
public long read(Buffer sink, long byteCount) throws IOException {
if (byteCount < 0L) {
throw new IllegalArgumentException("byteCount < 0: " + byteCount);
} else if (byteCount == 0L) {
return 0L;
} else {
try {
timeout.throwIfReached();
Segment tail = sink.writableSegment(1);
int maxToCopy = (int)Math.min(byteCount, (long)(8192 - tail.limit));
int bytesRead = in.read(tail.data, tail.limit, maxToCopy);
if (bytesRead == -1) {
return -1L;
} else {
tail.limit += bytesRead;
sink.size += (long)bytesRead;
return (long)bytesRead;
}
} catch (AssertionError var7) {
if (Okio.isAndroidGetsocknameError(var7)) {
throw new IOException(var7);
} else {
throw var7;
}
}
}
}
public void close() throws IOException {
in.close();
}
public Timeout timeout() {
return timeout;
}
public String toString()