09 | Android 高级进阶(源码剖析篇) Square 高效易用的 IO 框架 okio
Posted ACE1985
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了09 | Android 高级进阶(源码剖析篇) Square 高效易用的 IO 框架 okio相关的知识,希望对你有一定的参考价值。
作者简介:ASCE1885, 《android 高级进阶》作者。本文由于潜在的商业目的,未经授权不开放全文转载许可,谢谢!本文分析的源码版本已经 fork 到我的 Github。
前文中我们了解了 Java I/O 的历史和 okio 的基本概念,本文将开始 okio 中的输入流 Source 的剖析,首先看下它的类结构图:
可以看到,层次还是挺丰富的,平时用的最多的自然是 Source-->BufferedSource-->RealBufferedSource
和 Buffer
。既然是 Source 专场,那么我们就先来聊聊 Source 的基本概念。
注意这里的 Source 是一个统称的概念,并不是指上图中的 Source 这个接口。
Source 是对输入字节流的封装,通过它提供的接口,我们可以从网络,本地存储或者内存缓存中读取数据,同时它也可以提供对数据的转换操作,例如解压,解密等。和 java.io
的 InputStream 相比,两者实现的功能其实是一样的。但 InputStream 处理异构数据时需要多种输入流,例如使用 DataInputStream 处理基本数据类型的数据, 使用 BufferedInputStream 增加缓存能力,使用 InputStreamReader 用来读取字符串数据等,而 Source 体系中的 BufferedSource 能够满足所有这些需求。接下来我们就来细细品味 Source 体系中的各个成员,首先从 Source 接口开始。
Source 接口
Source 是输入流,存在需要关闭的数据,因此,Source 接口继承了 Closeable 并在 close
方法中关闭资源。同时定义了从 Buffer 中读取指定个数的字节信息的 read
方法,相比 InputStream,Source 接口定义了 timeout
方法实现超时机制,代码如下所示:
public interface Source extends Closeable
long read(Buffer sink, long byteCount) throws IOException;
Timeout timeout();
@Override void close() throws IOException;
BufferedSource 接口
BufferedSource 接口在 Source 接口的基础上,主要做了两件事:
利用内部定义的缓冲区 Buffer 来提高数据读取的性能
定义一系列读取基本数据类型和字符串类型的方法,例如 readByte 读取一个字节的信息,readShort 读取两个字节的信息,readInt 读取四个字节的信息,readLong 读取八个字节的信息,readByteString 读取字节信息并转换为 ByteString 类型,readByteArray 读取字节数组信息,readUtf8 读取字节信息并转换为 UTF-8 编码的字符串等。
BufferedSource 作为接口,具体的实现在它的实现类 RealBufferedSource 类和 Buffer 类中。这里需要说明一下,RealBufferedSource 内部也是使用 Buffer 类提供的接口来实现底层的数据读取等操作,我们可以将 RealBufferedSource 当作对 Buffer 类的一个代理,它在 Buffer 类提供的方法基础上增加一些校验等逻辑。这三者的关系如下图所示:
RealBufferedSource 类
既然 RealBufferedSource 类是代理类,那么可以预知它本身的代码逻辑不会很复杂,例如在实现 Source 接口提供的 read 方法时,它首先对入参进行校验,当参数为 null 或者不符合逻辑时抛出相应的异常,接着确保内部缓冲区 buffer 中有数据,然后根据缓存区 buffer 中的字节数和 read 方法想读取的字节数决定最终可以读取的字节数,最后调用 Buffer 类的实现的 Source 接口的同名方法,代码如下所示:
/**
* 将数据从输入流 source 中读取到缓冲区 buffer,然后经过 buffer 将数据写入输出流 sink 中
*/
@Override public long read(Buffer sink, long byteCount) throws IOException
// 入参校验
if (sink == null) throw new IllegalArgumentException("sink == null");
if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
if (closed) throw new IllegalStateException("closed");
// 确保缓存区 buffer 中有数据,一次最多从 source 中读取一个 Segment 大小的数据块
if (buffer.size == 0)
long read = source.read(buffer, Segment.SIZE);
if (read == -1) return -1;
// 决定最终可以读取的字节大小
long toRead = Math.min(byteCount, buffer.size);
return buffer.read(sink, toRead);
这个类中其他方法也都是类似的校验逻辑,我们就不细说了,但有一段代码除外,那就是将 Source 转换为 InputStream 的逻辑,它位于 inputStream 方法中,这个方法也是实现自 BufferedSource 接口的。那么如何实现 Source 和 InputStream 的转换呢?由于 Source 是 okio 自身定义的概念,因此,想要转换成 InputStream 也就只能用最直接的方法,就是创建一个 InputStream,我们知道 InputStream 是一个抽象类,那么继承这个抽象类并实现它的抽象方法就可以了,当然,也可以用匿名内部类的方式,RealBufferedSource 中的 inputStream 方法就是使用匿名内部类的方式,也就是直接 new 出一个 InputStream,并实现它的抽象方法和重写部分方法,代码实现中,依然主要是作校验操作,具体数据读操作交由 Buffer 类。
@Override public InputStream inputStream()
return new InputStream()
// 读取数据流中下一个字节数组,大小在0~255之间
@Override public int read() throws IOException
if (closed) throw new IOException("closed");
if (buffer.size == 0)
// 确保 buffer 中有数据
long count = source.read(buffer, Segment.SIZE);
if (count == -1) return -1;
// 确保读取到的数据在0~255之间
return buffer.readByte() & 0xff;
@Override public int read(byte[] data, int offset, int byteCount) throws IOException
if (closed) throw new IOException("closed");
// 检查入参合法性
checkOffsetAndCount(data.length, offset, byteCount);
if (buffer.size == 0)
// 确保 buffer 中有数据
long count = source.read(buffer, Segment.SIZE);
if (count == -1) return -1;
return buffer.read(data, offset, byteCount);
@Override public int available() throws IOException
if (closed) throw new IOException("closed");
return (int) Math.min(buffer.size, Integer.MAX_VALUE);
@Override public void close() throws IOException
RealBufferedSource.this.close();
...
;
读者可以自己再好好阅读这个类的其他方法实现,基本上大同小异。
ForwardingSource 类
ForwardingSource 是一个抽象类,顾名思义,它是用来将请求转发给其他类的,一般开发者可以通过继承这个类来实现一些扩展功能,例如实现一个监听下载进度的 Source,或者实现一个能够将输入字符串进行哈希的 Source 等等。其实这种用法有一个正式的名字:装饰者(Decorator)模式,具体来说,它指的是在不改变原类文件和使用继承的情况下,动态的给一个对象增加一些附加的职责。它通过创建一个包装对象,也就是装饰类来包装真实的对象。ForwardingSource 就是一个装饰者类,代码如下所示,是典型的装饰者的代码结构,也就是实现一个 Source 接口,同时构造方法 ForwardingSource 又接收一个 Source 对象作为参数,代码中几乎所有核心实现都交由这个 Source 参数对象来实现:
public abstract class ForwardingSource implements Source
private final Source delegate;
public ForwardingSource(Source delegate)
if (delegate == null) throw new IllegalArgumentException("delegate == null");
this.delegate = delegate;
public final Source delegate()
return delegate;
@Override public long read(Buffer sink, long byteCount) throws IOException
return delegate.read(sink, byteCount);
@Override public Timeout timeout()
return delegate.timeout();
@Override public void close() throws IOException
delegate.close();
...
... 更多内容请点击阅读原文继续阅读。
以上是关于09 | Android 高级进阶(源码剖析篇) Square 高效易用的 IO 框架 okio的主要内容,如果未能解决你的问题,请参考以下文章
04 | Android 高级进阶(源码剖析篇) 优美的日志框架 logger
Android 高级进阶(源码剖析篇) 小而美的日志框架 timber(上)
Android 高级进阶(源码剖析篇) 小而美的日志框架 timber(下)