深入理解OkHttp:同步、异步请求逻辑

一、前言

开篇陈述:OkHttp作为一个优秀的网络请求库,陪我们这些Android开发佬走过了许多风风雨雨的夜晚,所以今天特写一篇深入理解作文。整个系列篇中,在okhttp3.14.0版本上,依照OkHttp的使用为引线,做结构如下剖析:

  1. 同步请求的实现流程
  2. 异步请求的实现流程
  3. 重要拦截器:CacheInterceptor 的解析
  4. 重要拦截器:ConnectInterceptor 的解析
  5. 重要拦截器:CallServerInterceptor 的解析

二、同步/异步请求的实现流程

【2.1】 同步请求
public static final MediaType JSON
    = MediaType.get("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(json, JSON);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
      //见【2.2】client.newCall()
      //见【2.3】RealCall.execute()
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
}

如上是一次同步的网络请求,主要做如下事情:

  1. 通过Builder模式构建Request。
  2. 调用client.newCall(),通过request生成一个Call对象。他的实现类是RealCall。
  3. 随后调用RealCall.execute(),进行同步请求。
【2.2】OkHttpClient.newCall()
 OkHttpClient.java
 @Override public Call newCall(Request request) {
    //直接调用Realcall的方法,新建了一个RealCall对象。
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }
  
  RealCall.java
  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
  }
  
 private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    //这里需要特别注意OkHttpClient对象被Call持有。
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
  }


如上所示,OkHttpClient.newCall() 的调用链最终构建了一个 RealCall 对象,并且把client作为 RealCall 的成员变量,方便后续请求从 client 获取配置。

【2.3】RealCall.execute()
RealCall.java
@Override public Response execute() throws IOException {
    synchronized (this) {
      // 如果该请求已经执行过,报错。
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.timeoutEnter();
    transmitter.callStart();
    try {
      //见【2.4】获取 client 里面的调度器 Dispatcher 并记录这个请求。
      client.dispatcher().executed(this);
      //见【2.5】通过责任链的方式将发起请求并返回结果。这里是同步动作,会阻塞。
      return getResponseWithInterceptorChain();
    } finally {
      //请求完后需要把这个请求从调度器中移走
      client.dispatcher().finished(this);
    }
  }


Call在被执行时做如下事情:

  1. 判断Call的合法性。
  2. 将RealCall传进Client里面的Dispatcher.executed()里,而Dispatcher是在 OkHttpClient 被的构建函数里被创建并作为成员变量的。
  3. 开启责任链模式,进行请求相关逻辑。
  4. 执行完成后,调度器对这个请求进行收尾工作。
【2.4】Dispatcher.executed()
Dispatcher.java  
 /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

就是将此次请求加入到一个双端队列数据集合中。

【2.5】RealCall.getResponseWithInterceptorChain()
Response getResponseWithInterceptorChain() throws IOException {
    // 将请求的具体逻辑进行分层,并采用责任链的方式进行构造。
    List<Interceptor> interceptors = new ArrayList<>();
    // 用户自已的请求拦截器
    interceptors.addAll(client.interceptors());
    //重试和重定向拦截器
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    //桥拦截器
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //缓存逻辑拦截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //网络连接逻辑拦截器
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    // 网络请求拦截器,真正网络通行的地方,这个拦截器处理过后会生成一个Response
    interceptors.add(new CallServerInterceptor(forWebSocket));

    //依照如上配置,构建出一个请求的处理逻辑责任链,特别注意:这条链开始于下标位置为的0拦截器。
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      //详见【2.6】,按下处理逻辑链条的开关。
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      //返回请求结果
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

通过该方法的名字我们也可以知道,这个方法就是通过拦截器链来获得Response的过程。他做了如下事情:

  1. 将用户自定义的拦截器先加入集合中。
  2. 加入一次请求中需要用的拦截器,这些拦截器代表一次完整的网络请求逻辑被分了几层以及他们的先后顺序。从代码中我们不难看出他们的流程是:重试/重定向逻辑->网络桥逻辑->缓存逻辑->建立网络连接逻辑->网络通行逻辑。
  3. 用以上拦截器集合构建出一条逻辑处理的拦截链,并将这条链需要使用的拦截器下标赋值为0,从第一个开始。
  4. 调用chain.proceed()启动这条链的处理流程。
    使用责任链的设计模式来处理一次网络请求中的逻辑可以有效的划分逻辑层。而前一个拦截器可以根据实际的处理情况来决定下一拦截器是否应该继续处理。

【2.6】RealInterceptorChain.proceed()

RealInterceptorChain.java
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
      throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    ...

    /**
    * index+1:构建出新的拦截链,不过新的拦截链的处理拦截器是下标为index+1的
    * 实现了责任链中,处理逻辑的流转。
    */
    RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
    //此时index = 0;所以拿到了第一个拦截器,并且调用他的intercept 方法进行具体逻辑处理。
    Interceptor interceptor = interceptors.get(index);
    //当前拦截器对网络请求进行处理。
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (exchange != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // 省略对response合法性的检查代码
    ...

    return response;
  }

总结:

  1. 先index+1,构建index指向下一个拦截器的责任链。
  2. 在链中取出下标当前index(此时为0)的拦截器,并调用intercept(next)进行此次拦截器逻辑的真正处理。这里注意,传递进去的参数正是1中构建出来的next责任链。在Interceptor.intercept()方法内部进行自身的逻辑处理以后,会调用next.proceed()进行一次传递,由下一个拦截器进行处理。而此次请求也是这样在各个拦截器中被 处理->传递->处理->…->返回结果

三、异步请求实现流程

3.1

public static final MediaType JSON
    = MediaType.get("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(json, JSON);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
      //client.newCall():同【2.2】
      //详见【3.2】RealCall.enqueue()
  try (Response response = client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

            }
        }) {
    return response.body().string();
  }
}

如上是一次OkHttp的异步请求使用方法,基本于【2.1】的同步请求一致,唯一不同的是,call的异步调用是通过RealCall.enqueue()实现的。而请求结果通过Callback回调到主线程。

【3.3】RealCall.enqueue()
 @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    transmitter.callStart();
    //详见【3.4】:AsyncCall()
    //详见【3.5】:Dispatcher.enqueue()
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

将用户创建的callback作为参数传入AsyncCall()构造函数。AsyncCall 继承于Runnable.

【3.4】AsyncCall;
AsyncCall.java
final class AsyncCall extends NamedRunnable {
    
    private volatile AtomicInteger callsPerHost = new AtomicInteger(0);

    ...
    /**
     * 该方法是在dispather需要执行此请求的时候,分配给它线程池,此异步请求便在这个线程池中执行网络请求。
     */
    void executeOn(ExecutorService executorService) {
      ...
      boolean success = false;
      try {
        //异步的关键:将请求放到线程池中执行。
        executorService.execute(this);
        success = true;
      } catch (RejectedExecutionException e) {
       ...
       success = false;
      } finally {
        if (!success) {
          client.dispatcher().finished(this); // 执行失败会通过Dispatcher进行finished,以后再也不会用此AsyncCall。
        }
      }
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      transmitter.timeoutEnter();
      try {
        //此处同【2.5】
        Response response = getResponseWithInterceptorChain();
        signalledCallback = true;
        //请求成功时,回调Response给到用户
        responseCallback.onResponse(RealCall.this, response);
      } catch (IOException e) {
        ...
        //请求错误时,回调错误接口给到用户
          responseCallback.onFailure(RealCall.this, e);
        
      } finally {
       //详见【3.6】,结束一次请求。
        client.dispatcher().finished(this);
      }
    }
  }

从上面可以看出,AsyncCall继承于Runnable,它提供了将Call放到线程池执行的能力,实现了请求的异步流程。

【3.5】Dispatcher.enqueue();
Dispatcher.java
//准备进行异步调用的请求。
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
 //正在执行的异步请求。
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
  
void enqueue(AsyncCall call) {
    synchronized (this) {
      //将异步请求加入到双端队列中
      readyAsyncCalls.add(call);

      // 寻找是否有同Host的请求,如果有进行复用
      if (!call.get().forWebSocket) {
        AsyncCall existingCall = findExistingCallWithHost(call.host());
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
      }
    }
    //【详见3.7】将符合条件的Ready的异步请求转入runningAsyncCalls,并执行
    promoteAndExecute();
  }

总结:

  1. 将此请求登记到Dispatcher的预备双端队列中。
  2. 以此次的请求的Host来查找可服用的异步请求,如果存在,进行复用。
  3. 尝试将刚刚加入预备队的请求执行。
【3.6】Dipatcher.finish()
Dipatcher.java
private <T> void finished(Deque<T> calls, T call) {
    Runnable idleCallback;
    synchronized (this) {
    ...
    //【详见3.7】一个请求完成后,检查此时是否有在等待执行的请求,并处理。
    boolean isRunning = promoteAndExecute();
    if (!isRunning && idleCallback != null) {
      //通知此时已经没有异步请求任务
      idleCallback.run();
    }
  }

总结:调度器结束一次请求

  1. 当一个异步任务完成后,调度器会触发一次预备任务执行流程。让之前因为最大请求数等限制而不能执行的请求有机会得到执行。
  2. 通过idleCallback.run()通知此时的调度器空闲状态。
【3.7】Dipatcher.promoteAndExecute()
 private boolean promoteAndExecute() {
    ...
    List<AsyncCall> executableCalls = new ArrayList<>();
    boolean isRunning;
    synchronized (this) {
      for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        AsyncCall asyncCall = i.next();
        
        //检查最大请求数限制和
        if (runningAsyncCalls.size() >= maxRequests) break;
        if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue;
        
        //满足条件,便把预备队的请求提升到执行队列。
        i.remove();
        asyncCall.callsPerHost().incrementAndGet();
        executableCalls.add(asyncCall);
        runningAsyncCalls.add(asyncCall);
      }
      isRunning = runningCallsCount() > 0;
    }

    //将可执行的异步请求放进线程池执行
    for (int i = 0, size = executableCalls.size(); i < size; i++) {
      AsyncCall asyncCall = executableCalls.get(i);
      //【详见3.4】
      asyncCall.executeOn(executorService());
    }

    return isRunning;
![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2020/6/21/172d4b4a7205d520~tplv-t2oaga2asx-image.image)
  }

总结 :该方法是对预备队列里的请求提升至执行队列并执行的一次尝试。如果不能执行,他启动时机将会延后到其他请求结束(如【3.6】逻辑)。

小篇结:本篇中以Okhttp的用法为主线,探究了它的同步请求、异步请求的代码逻辑。而OkHttp最主要的设计模式:责任链模式也在其中有涉及到。最后,我们通过2张图片来理解同步和异步的整个请求流程(图片不是本人画的):

                                同步请求    

                                异步请求流程

本文转自 [https://juejin.cn/post/6844903974110248974],如有侵权,请联系删除。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值