0%

Read source code of Okio

对 Okio 源码作分析的文章已有很多,想深刻理解 Okio 还是要自己看一遍源码。
Okio代码不多,整个框架涉及的源文件有几个,可以先阅读它们;

Okio.java
Source.java
BufferedSource.java
RealBufferedSource.java
Sink.java
BufferedSink.java
RealBufferedSink.java
Buffer.java

下图来自https://juejin.im/post/5856680c8e450a006c6474bd

Okio主要结构

装饰模式

先学习一下装饰模式,wiki

decorator_diagram

Okio中应用了装饰模式

decorator_diagram_okio

RealBufferedSource 就是装饰器,RealBufferedSource 的代码十分简单,有两个属性Buffer、Source。
Buffer 是一个十分复杂实现类,包括了缓存、实际上的读写操作;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final class RealBufferedSource implements BufferedSource {
public final Buffer buffer = new Buffer();
public final Source source;
boolean closed;

RealBufferedSource(Source source) {
if (source == null) throw new NullPointerException("source == null");
this.source = source;
}

@Override public byte readByte() throws IOException {
require(1);
return buffer.readByte();
}
}

AnonymousSource 又是什么呢,是 Source 实现类, 负责从 InputStream 复制数据到 Buffer 的缓存中。  
Okio.source() 返回的是一个匿名实现类 AnonymousSource,当然你也可以自己实现 Source 类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/** Returns a source that reads from {@code in}. */
public static Source source(InputStream in) {
return source(in, new Timeout());
}

private static Source source(final InputStream in, final Timeout timeout) {
if (in == null) throw new IllegalArgumentException("in == null");
if (timeout == null) throw new IllegalArgumentException("timeout == null");

return new Source() {
@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;
}
}

@Override public void close() throws IOException {
in.close();
}

@Override public Timeout timeout() {
return timeout;
}

@Override public String toString() {
return "source(" + in + ")";
}
};
}

okio的使用

示例: 向文件 out.txt 输入 “Hello, Okio”, 复制 out.txt 到 copy.txt;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void okio() {
try {
File copy = new File(Environment.getExternalStorageDirectory(), "copy.txt");
File out = new File(Environment.getExternalStorageDirectory(), "out.txt");
BufferedSink bufferedSink = Okio.buffer(Okio.sink(out));
bufferedSink.writeString("Hello, Okio", Charset.forName("UTF-8"));
bufferedSink.close();

BufferedSource bufferedSource = Okio.buffer(Okio.source(out));
BufferedSink bufferedSink1 = Okio.buffer(Okio.sink(copy));
bufferedSink1.writeAll(bufferedSource);
bufferedSink1.close();
bufferedSource.close();

} catch (IOException e) {
e.printStackTrace();
//close source or sink yourself
}
}

step 1: Okio.source()
第一步,得到一个输入源,Okio.source() 返回一个匿名实现类 AnonymousSource:Source 的实现类。

step2: Okio.buffer()
第二步,得到 BufferedSource,缓存的输入源。实际上是 BufferedSource 的一个实现类 RealBufferedSource,
RealBufferedSource 就是装饰器,所有的读取工作实际上是由Buffer类实现。

1
2
3
public static BufferedSource buffer(Source source) {
return new RealBufferedSource(source);
}

step3: Read data
Read data by your need

扩展

如果输入源是已压缩/已加密的数据,那怎么读取到解压/解密的数据呢?
Okio 提供的扩展是可以根据需求实现Source类,可以阅读GzipSource类来学习,该类会解码 Gzip 数据,让我们读取到的数据就是已解压的。

使用示例:

1
2
3
4
5
6
7
8
try {
File file = new File(Environment.getExternalStorageDirectory(), "in.gz");
BufferedSource bufferedSource = Okio.buffer(new GzipSource(Okio.source(file)));
String s = bufferedSource.readString(Charset.forName("UTF-8"));
Log.d("okio", "->" + s);
} catch (IOException e) {
e.printStackTrace();
}

Okio, Fantastic!!!