OKHttp 原理

OkHttp 是一个高性能的网络库,主要功能如下:

  1. 支持底层协议(HTTPS / HTTP1.1 / HTTP2 / WebSocket),包括连接池、连接复用、缓存控制等
  2. 维护任务队列线程池,支持友好并发访问
  3. 提供拦截器链,实现 Request 和 Response 分层处理

OkHttp 中涉及到的设计模式

  • 建造者模式。构建 OkHttpClient
  • 工厂模式。OkHttpClient 实现了 Call.Factory,用于生产 Call 产品(RealCall)
  • 享元模式。通过线程池、连接池共享对象
  • 责任链模式。将不同功能的拦截器形成一个链

同步请求

RealCall 表示被执行的请求,调用 execute 方法执行同步请求,最终会把 RealCall 放到 Dispatcher 的同步双端队列中顺序执行,执行结束后调用 finish 方法,把 RealCall 请求从 Dispatcher 中移除,无论是同步还是异步请求,最终都会调用 getResponseWithInterceptorChain 方法,在 OkHttp 中,拦截器链会依次完成相应功能

Call 对象只能执行一次,否则会抛出异常

 @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    client.dispatcher().executed(this);
    return getResponseWithInterceptorChain();
  }

  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

异步请求

AsyncCall 表示被执行的请求,调用 enqueue 方法执行异步请求,如果当前所有请求数(64)和单一 host 请求数(5)满足要求,则把 AsyncCall 放到 Dispatcher 的 Running 队列中顺序执行,否则放到 waiting 队列中,AsyncCall 执行结束后调用 finish 方法,把当前 AsyncCall 从 running 队列中移除并执行线程调度

 @Override public void enqueue(Callback responseCallback) {
   synchronized (this) {
     if (executed) throw new IllegalStateException("Already Executed");
     executed = true;
   }
   client.dispatcher().enqueue(new AsyncCall(responseCallback));
 }

 synchronized void enqueue(AsyncCall call) {
   if (runningAsyncCalls.size() < maxRequests
              && runningCallsForHost(call) < maxRequestsPerHost) {
     runningAsyncCalls.add(call);
     executorService().execute(call);
   } else {
     readyAsyncCalls.add(call);
   }
 }

  final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;
    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }
    @Override protected void execute() {
      Response response = getResponseWithInterceptorChain();
      if (retryAndFollowUpInterceptor.isCanceled()) {
        responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
      } else {
        responseCallback.onResponse(RealCall.this, response);
      }
    }
  }

Dispatcher 调度器:维护请求状态、维护线程池(减少创建线程的开销、减少线程过少造成 CPU 闲置、减少线程过多时内存和线程切换的开销)

  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    synchronized (this) {
      calls.remove(call)
      if (promoteCalls) promoteCalls();
    }
  }

  private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return;
    if (readyAsyncCalls.isEmpty()) return;
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext();) {
      AsyncCall call = i.next();
      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }
      if (runningAsyncCalls.size() >= maxRequests) return;
    }
  }

拦截器链

逻辑大致分为两部分:

  1. 创建一系列拦截器,并将其放入拦截器数组中。这部分拦截器即包括用户自定义的拦截器也包括框架内置的拦截器
  2. 创建一个拦截器链 RealInterceptorChain,并执行拦截器链的 proceed 方法
Response getResponseWithInterceptorChain() throws IOException {
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
}

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpCodec httpCodec;
  private final RealConnection connection;
  private final int index;
  private final Request request;
  private int calls;

  public RealInterceptorChain(List<Interceptor> interceptors,
                StreamAllocation streamAllocation, HttpCodec httpCodec,
                RealConnection connection, int index, Request request) {

  public Response proceed(Request request,
                  StreamAllocation streamAllocation, HttpCodec httpCodec,
                  RealConnection connection) throws IOException {

    ...

    RealInterceptorChain next = 
                  new RealInterceptorChain(interceptors, streamAllocation,
                  httpCodec, connection, index + 1, request);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
    
    ...

    return response;
  }
}

Chain.proceed 方法做了两件事

  • 创建下一个拦截链。传入 index + 1 使得下一个拦截器链只能从下一个拦截器开始访问
  • 执行索引为 index 的 intercept 方法,并将下一个拦截器链传入该方法

拦截器的 intercept 方法所执行的逻辑大致分为三部分

  • 在发起请求前对 request 进行处理
  • 调用下一个拦截器,获取 response
  • 对 response 进行处理,返回给上一个拦截器

Okhttp 中除了用户自定义的拦截器外还内置了若干核心拦截器,具体功能如下

RetryAndFollowUpInterceptor

  • 在网络请求失败后进行重试
  • 当服务器返回 3XX(重定向) 时直接发起新的请求,并在条件允许情况下复用当前连接

BridgeInterceptor

  • 设置内容长度,内容编码
  • 设置 GZIP 压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦
  • 添加 Cookie
  • 设置其他 Header 数据,如 User-Agent、Host、Connection = Keep-alive 等

CacheInterceptor

  • 当网络请求有符合要求的 Cache 时直接返回 Cache
  • 当服务器返回内容有改变时更新当前 cache
  • 如果当前cache失效,删除

ConnectInterceptor

为当前请求找到合适的连接,可能复用已有连接也可能是重新创建的连接

CallServerInterceptor

负责向服务器发起真正的访问请求,并在接收到服务器返回后读取响应返回

HTTP2连接复用规则

RealConnection 描述一个物理 Socket 连接,连接池中维护多个 RealConnection 实例。由于HTTP2 支持多路复用,一个 RealConnection 可以支持多个网络访问请求,所以 OkHttp 又引入了 StreamAllocation 来描述一个实际的网络请求。通过 RealConnection 中的 StreamAllocation 弱引用队列是否为 0 来判断当前 RealConnection 是否空闲

  • 通过 ConnectionPool 来管理连接池,ConnectionPool 有一个独立的线程 cleanupRunnable 来清理连接池,其触发时机有两个:当连接池新增连接时、当连接闲置时(connectionBecameIdle 接口被调用时)
  • 搜集所有的空闲连接并记录 KeepAlive 时间,如果空闲连接超过 5 个或者某个连接的 KeepAlive 时间大于五分钟,就移除 KeepAlive 时间最长的连接,立刻再次扫描
  • 如果目前空闲连接不多于 5 个且 KeepAlive 时间都不大于 5 分钟,就返回最大 KeepAlive 的剩余的时间,供下次清理
  • 如果(全部都是活跃的连接),就返回默认的 keep-alive 时间,也就是 5 分钟后再执行清理
  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

little-sparrow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值