okhttp源码解析-okio组件

629 阅读4分钟

       OKHttp的网络IO是通过OKio组件封装实现,通过OKio组件封装了读写操作,下图是关于OKio的整体框架,可以看到Sink和Source相当于jdk中的的InputStream和outputStream。我们来看下这个okio内存池的整个框架设计。

我们从一个简单的文件读写操作的例子开始,就很容易知道这两个接口是怎么用的了。

File file;
BufferedSink bufferSink;

file = new File ("./test_okio.txt");
if (!file.exists()) {
    file.createNewFile();
}

//输入
bufferSink = Okio.buffer(Okio.sink(file));
bufferSink.writeString("are you ok? \n", Charset.forName("utf-8"));
bufferSink.close();

//输出
BufferedSource bufferedSource = Okio.buffer(Okio.source(file));
System.out.println(bufferedSource.readByteString().string(Charset.forName("utf-8")));

        可以了解到Sink和Source是OKio是最基本的接口,定义了写入数据到缓冲区或直接读取缓冲区数据等操作。

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;
}

public interface Source extends Closeable {
  //通过缓冲区
  long read(Buffer sink, long byteCount) throws IOException;
  Timeout timeout();
  //关闭读操作
  @Override void close() throws IOException;
}

BufferedSink作为输入数据的扩展接口,定义了非常多关于不同数据格式写入的操作接口。

public interface BufferedSink extends Sink {
  Buffer buffer();
  BufferedSink write(ByteString byteString) throws IOException;
  BufferedSink write(byte[] source) throws IOException;
  BufferedSink write(byte[] source, int offset, int byteCount) throws IOException;
  long writeAll(Source source) throws IOException;
  BufferedSink write(Source source, long byteCount) throws IOException;
  BufferedSink writeUtf8(String string) throws IOException;
  BufferedSink writeUtf8(String string, int beginIndex, int endIndex) throws IOException;
  BufferedSink writeUtf8CodePoint(int codePoint) throws IOException;
  BufferedSink writeString(String string, Charset charset) throws IOException;
  BufferedSink writeString(String string, int beginIndex, int endIndex, Charset charset)
      throws IOException;
  BufferedSink writeByte(int b) throws IOException;
  BufferedSink writeShort(int s) throws IOException;
  BufferedSink writeShortLe(int s) throws IOException;
  BufferedSink writeInt(int i) throws IOException;
  BufferedSink writeIntLe(int i) throws IOException;
  BufferedSink writeLong(long v) throws IOException;
  BufferedSink writeLongLe(long v) throws IOException;
  BufferedSink writeDecimalLong(long v) throws IOException;
  BufferedSink writeHexadecimalUnsignedLong(long v) throws IOException;
  BufferedSink emitCompleteSegments() throws IOException;
  BufferedSink emit() throws IOException;
  OutputStream outputStream();
}

        RealBufferedSink是BufferedSink的实现类,在这个类中能看到是写入Buffer类对象的,我们越来越接近底层的缓存写入了。

final class RealBufferedSink implements BufferedSink {

//通过缓冲区把String类型的数据写入
  @Override public BufferedSink writeString(String string, Charset charset) throws IOException {
    if (closed) throw new IllegalStateException("closed");
    buffer.writeString(string, charset);
    return emitCompleteSegments();
  }
  
  //完成写入
  @Override public BufferedSink emitCompleteSegments() throws IOException {
    if (closed) throw new IllegalStateException("closed");
    long byteCount = buffer.completeSegmentByteCount();
    if (byteCount > 0) sink.write(buffer, byteCount);
    return this;
  }
}

       数据量可能很大,会写入到多个Buffer中。其中Buffer中依赖的是Segment-内存块来实现类来实现数据的暂时存放的。

public final class Buffer implements BufferedSource, BufferedSink, Cloneable {

  ..........

  //offset:写入数据的数组下标起点,
  //byteCount :写入数据的长度
  @Override 
  public Buffer write(byte[] source, int offset, int byteCount) {
    int limit = offset + byteCount;
    //开始循环写入数据
    while (offset < limit) {  
      Segment tail = writableSegment(1);
      // limit - offset是代写入的数据的长度
      // Segment.SIZE - tail.limit是这个容器剩余空间的长度
      int toCopy = Math.min(limit - offset, Segment.SIZE - tail.limit);
      //调用Java方法把数据复制到容器中。
      System.arraycopy(source, offset, tail.data, tail.limit, toCopy);
      //记录相关偏移量
      offset += toCopy;
      tail.limit += toCopy;
    }
    //增加buffer的size
    size += byteCount;
    return this;
  }
  
  //获取一个Segment
  Segment writableSegment(int minimumCapacity) {
    if (minimumCapacity < 1 || minimumCapacity > Segment.SIZE) throw new IllegalArgumentException();
    if (head == null) {
      //假如当前Segment为空,则从Segment池中拿到一个
      head = SegmentPool.take(); // Acquire a first segment.
      return head.next = head.prev = head;
    }
    //获取当前Segment的前一个Segment
    Segment tail = head.prev;
    //检查这个Segment容器是否有剩余空间可供写入 
    if (tail.limit + minimumCapacity > Segment.SIZE || !tail.owner) {
      //假如没有,则拿一个新的的Segment来代替这个(即链表的下一个)
      tail = tail.push(SegmentPool.take()); // Append a new empty segment to fill up.
    }
    return tail;
  }
}

科普一下,内存池其实可以减少内存碎片,分配内存更快,还避免内存泄露,用在非常频繁的网络IO的内存使用场景是非常合适的。可以看到内存池的设计如下所示,SegmentPool内存池由多个内存块组成,由双端链表组织起来的。其中内存池最大为64KB,内存块最大为8K最小为1K。

public final class SegmentPool {
  static final long MAX_SIZE = 64 * 1024; // 64 KiB.
  static Segment next;
  static long byteCount;
  
  //拿出一个内存块
  static Segment take() {
    synchronized (SegmentPool.class) {
      if (next != null) {
        Segment result = next;
        next = result.next;
        result.next = null;
        byteCount -= Segment.SIZE;
        return result;
      }
    }
    return new Segment();
  }

  //回收一个闲置的Segment
  static void recycle(Segment segment) {
    if (segment.next != null || segment.prev != null) throw new IllegalArgumentException();
    if (segment.shared) return;
    synchronized (SegmentPool.class) { 
      if (byteCount + Segment.SIZE > MAX_SIZE) return; 
      byteCount += Segment.SIZE;
      segment.next = next;
      segment.pos = segment.limit = 0;
      next = segment;
    }
  }
}

public final class Segment {
    
  static final int SIZE = 8192;
  static final int SHARE_MINIMUM = 1024;
  final byte[] data;
  int pos;
  int limit;
  boolean shared;
  boolean owner;
  //下一个内存块
  Segment next;
  //前一个内存块
  Segment prev;
  
  //从链表中移除一个segment
  public Segment pop() {
    Segment result = next != this ? next : null;
    prev.next = next;
    next.prev = prev;
    next = null;
    prev = null;
    return result;
  }

  //从链表中添加一个segment
  public Segment push(Segment segment) {
    segment.prev = this;
    segment.next = next;
    next.prev = segment;
    next = segment;
    return segment;
  }
}

        至此,关于okhttp框架的主要实现原理,对拦截器链/IO模型/连接池组件/OKio组件的实现原理做了详细的讲解,虽然代码简短,但是包含了大量精巧的设计。这里建议是针对每个组件单独拿出来把玩下,更加容易理解。