One Step By One Step 解析OkHttp3 - RealCall (二)

上一篇文章分析了Dispatcher的作用,用于管理请求的状态,那么,真正负责发起请求,获取请求结果的,是谁呢?答案是真的请求,翻译成英文就是RealCall(笑)。

Note: **Call** 和 **Request** 在本系列文章中,均被称为请求。
前者为OkHttp的请求(或者叫做命令),
后者为HTTP的请求的封装类。请读者自行区分。

概述



通常,我们需要发送一个请求,那么,我们会先使用创建一个 Request ,然后使用okHttpClient.newCall(request),创建一个Call,然后使用这个Call,发送出(同步/异步)请求,没错,这个Call,就是RealCall,不多说,下面开始看源码。

源码


先看RealCall的类结构

RealCall为接口Call的实现类。

/**
 * 一个Call封装一对Request和Response,能且仅能被执行一次。并且Call可以被取消。
 */
public interface Call {
  /** 返回初始化此Call的原始请求 */
  Request request();

  /**
   * 立即发出请求,一直阻塞到响应可以被处理,或者发生了错误。
   *
   * 可以调用Response#body方法获取到相应的body。为了连接服用,调用者需要调用ResponseBody#close()来关闭响应体。
   *
   * 注意:传输层成功(收到响应码,响应头,响应体),不代表应用层成功。依然可能有404,或者500这些坑爹的响应码。
   *
   * @throws IOException 如果一个请求因为被取消、连接问题、超时,那么抛出此异常。可能是服务器在发生错误之前接收到了请求,造成网络在交互过程中出错。
   * @throws IllegalStateException 当一个请求已经被执行,抛出此异常
   */
  Response execute() throws IOException;

  /**
   * 安排请求在未来的某一刻执行。
   *
   * OkHttpClient#dispatcher决定这个请求什么时候被执行:通常立即就被执行了,除非是目前有其它的请求被执行。
   * 根据上篇的分析,如果不能立即执行,会被移动到就绪队列中。
   *
   * 稍后,responseCallback会被调用,可能是一个正常的HTTP返回,或者是一个失败的异常。
   *
   * @throws IllegalStateException 当一个请求已经被执行,抛出此异常
   */
  void enqueue(Callback responseCallback);

  /** 取消请求,如果可能的话。如果请求已经有返回了,那么就不能被取消了。 */
  void cancel();

  /**
   * 返回true,如果 execute() 或者 enqueue(Callback) 被执行过了。执行请求两次会出错的,还是判断下好。
   */
  boolean isExecuted();

  /**
   * 返回true,如果这个请求被取消了。
   */
  boolean isCanceled();

  /**
    这是一个生成请求的工厂接口。
  */
  interface Factory {
    // 根据一个Http请求生成一个OKHttp请求。
    Call newCall(Request request);
  }
}

再来看RealCall中,这几个方法是如何实现的。

详细的分析过程,都写在了注释当中。

  /**
    构造器,原始的请求保存为成员变量
  */
  protected RealCall(OkHttpClient client, Request originalRequest) {
    this.client = client;
    this.originalRequest = originalRequest;
  }
/**
直接返回原始的请求
*/
@Override public Request request() {
  return originalRequest;
}
/**
同步执行请求,会造成线程阻塞

*/
@Override public Response execute() throws IOException {
    //如果已经被执行过了, 抛异常。
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      //设置该请求已经被执行。
      executed = true;
    }
    try {
      //这里调用的dispatcher的executed方法,将此请求加入到同步请求运行队列中
      client.dispatcher().executed(this);
      //下面的一行是此方法的核心,组成连接器链,将这个请求加入到拦截器链中
      //在所有的拦截器都拦截一遍后,发送http请求,获取response
      //下面会详细讲解这个方法
      Response result = getResponseWithInterceptorChain(false);
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      //将运行中队列中的请求移除
      client.dispatcher().finished(this);
    }
  }
/**
进入到拦截器链中,并获取响应
*/
private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
  // 新建一个ApplicationInterceptorChain实例,请求作为构造器参数传入
  Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
  // 调用此方法,进入拦截器链
  return chain.proceed(originalRequest);
}

ApplicationInterceptorChain

/**
ApplicationInterceptorChain 的构造器
index 为这一环节的索引
request 上一个环节拦截后的请求
*/
ApplicationInterceptorChain(int index, Request request, boolean forWebSocket) {
  this.index = index;
  this.request = request;
  this.forWebSocket = forWebSocket;
}

/**
这个方法用于连接每个拦截器,并执行http请求
除了进入拦截器链的时候,由RealCall#getResponseWithInterceptorChain方法调用,
其它时刻都是在拦截器的Interceptor#intercept方法中,由用户主动调用此方法
因为需要实现拦截器的功能,必须要调用Interceptor#intercept,因此是用户参与了此拦截器链的建立
*/
@Override public Response proceed(Request request) throws IOException {
      // 如果此环节的索引没有超过拦截器的大小,则再建立一个ApplicationInterceptorChain对象,
      // 传递给Interceptor的intercept方法,实现递归调用
      if (index < client.interceptors().size()) {
        // 新建一个环节,并且将索引+1,这里的request可能不再是originalRequest了,因为在拦截器中可能被修改了
        Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
        // 获取到对应的拦截器
        Interceptor interceptor = client.interceptors().get(index);
        // 执行拦截器的intercept方法,参数是上面新建的环节,这个方法里面会调用chain.proceed(),递归了。
        // 在最深的一层调用getResponse之后,响应会一层层的往外传
        Response interceptedResponse = interceptor.intercept(chain);

        if (interceptedResponse == null) {
          throw new NullPointerException("application interceptor " + interceptor
              + " returned null");
        }
        // 返回这一层拦截器处理用的响应
        return interceptedResponse;
      }

      // 递归到了最深的一层,拦截器都进入过了,发送httpRequest,获取到response.
      // 这个方法相当复杂, 因此放在最后讲解
      return getResponse(request, forWebSocket);
    }

上面的代码讲解可能还未理解,下面祭出我的灵魂画作。

getResponseWithInterceptorChain()进入到拦截器链中,然后请求被ApplicationInterceptorChain乘载,传递到各个拦截器中,进入到所有的拦截器中后,调用realCall.getResponse()获取到响应,然后再一层层往上返回。

下面这个图的意思是一样的,看能不能更清楚点。

下面继续看RealCall的其它方法

/**
  调用的是下面的重载的方法
*/
@Override public void enqueue(Callback responseCallback) {
  enqueue(responseCallback, false);
}

/**
同样的,已经执行了,抛出异常
然后将此realCall包装为AsyncCall,调用Dispatcher#enqueue方法。
上一篇文章已经将结果Dispatcher了,这里就不再赘述。
*/
void enqueue(Callback responseCallback, boolean forWebSocket) {
    //如果已经被执行过了, 抛异常。
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    // 标识为已执行
    executed = true;
  }
  // AsyncCall是RealCall的内部类,相当于是对RealCall的一层封装,然后将其传入就绪或运行队列中,等待线程池执行请求
  // 下面会分析AsyncCall
  client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}

AsyncCall


/**
  AsyncCall的代码较短,因此直接贴出整个类
  AsyncCall并不是Call接口的实现类,而是继承自NamedRunnable抽象类,而这个父类,只是一个带设置线程名称的Runnable而已。
  我们主要看execute()方法,这个方法是由Dispatcher中的线程池调用的。
*/
final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;
    private final boolean forWebSocket;

    private AsyncCall(Callback responseCallback, boolean forWebSocket) {
      super("OkHttp %s", originalRequest.url().toString());
      this.responseCallback = responseCallback;
      this.forWebSocket = forWebSocket;
    }

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    Object tag() {
      return originalRequest.tag();
    }

    void cancel() {
      RealCall.this.cancel();
    }

    RealCall get() {
      return RealCall.this;
    }

    /**
      过一遍拦截器链,并执行请求,然后调用回调函数。
    */
    @Override protected void execute() {
      // 保证onFailure最多只会被调用一次
      boolean signalledCallback = false;
      try {
        // 进入连接器链,并执行请求
        Response response = getResponseWithInterceptorChain(forWebSocket);
        // 如果请求被取消,调用onFailure
        if (canceled) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          // 正常情况,调用onResponse
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        // 如果上面有调用过回调,就不调了,这里保证onFailure只会被调用一次
        if (signalledCallback) {
          logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        // 运行队列移除请求
        client.dispatcher().finished(this);
      }
    }
  }
  /**
   * 执行请求,并获取响应结果。分多钟情况:
      1. 请求被取消,抛出异常
      2. 需要重定向,如果没有超过最大次数,重现创建HttpEngine,再次发出请求;如果超过最大次数,抛出异常
      3. 连接出错,尝试恢复HttpEngine,再次发出请求;恢复失败,抛出异常
      4. 发送的请求有问题,直接抛出异常
   */
  Response getResponse(Request request, boolean forWebSocket) throws IOException {
    // 复制请求头,并设置适合的属性。如果长度不为-1,则设置Content-Length,否则使用chunked方式传输。
    // 如果对chunked不熟悉,请参考其他资料
    RequestBody body = request.body();
    if (body != null) {
      // 根据传进来的request创建新的Builder.
      Request.Builder requestBuilder = request.newBuilder();

      // 设置Content-Type
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      // 判断使用何种方式传输
      long contentLength = body.contentLength();
      // body长度不为-1,设置Content-Length
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        //  body 长度为 -1 ,使用chunked传输
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }

      //创建新请求
      request = requestBuilder.build();
    }

    // 创建一个新的引擎,每个引擎代表一次请求/响应对
    engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);

    int followUpCount = 0; //重试次数
    //死循环 出口:
    // 1. 请求被取消
    // 2. 请求有问题
    // 3. 捕获到异常,且尝试恢复失败
    // 4. 获取到响应,且无需重定向
    // 5. 重定向次数超过最大限制
    while (true) {
      // 被取消的情况
      if (canceled) {
        engine.releaseStreamAllocation();
        throw new IOException("Canceled");
      }

      boolean releaseConnection = true;
      try {
        // 发送请求
        engine.sendRequest();
        // 读取响应
        engine.readResponse();
        releaseConnection = false;
      } catch (RequestException e) {
        // 请求失败,请求本身有问题,或者是网络不通
        throw e.getCause();
      } catch (RouteException e) {
        // 连接到服务器的路由发生异常,请求还没被发送
        // 通过上一次连接异常恢复引擎
        HttpEngine retryEngine = engine.recover(e.getLastConnectException(), null);
        // 如果恢复成功,将当前的引擎设置为这个恢复好的引擎
        if (retryEngine != null) {
          releaseConnection = false;
          engine = retryEngine;
          continue;
        }
        // 没法恢复,抛出异常
        throw e.getLastConnectException();
      } catch (IOException e) {
        // 与服务器交互失败,这时,请求可能已经被发送
        // 恢复引擎
        HttpEngine retryEngine = engine.recover(e, null);
        //如果恢复成功,将当前的引擎设置为这个恢复好的引擎
        if (retryEngine != null) {
          releaseConnection = false;
          engine = retryEngine;
          continue;
        }

        // 没法恢复,抛出异常
        throw e;
      } finally {
        // 如果需要释放连接,则将连接释放
        if (releaseConnection) {
          StreamAllocation streamAllocation = engine.close();
          streamAllocation.release();
        }
      }

      // 获取响应
      Response response = engine.getResponse();
      // 下一步的请求,如果存在,则需要重定向
      Request followUp = engine.followUpRequest();

      //如果不需要重定向
      if (followUp == null) {
        if (!forWebSocket) {
          // 释放连接
          engine.releaseStreamAllocation();
        }
        //返回响应
        return response;
      }

      // 如果需要重定向,关闭当前引擎
      StreamAllocation streamAllocation = engine.close();

      // 如果超过最大数,释放连接,并抛出异常
      if (++followUpCount > MAX_FOLLOW_UPS) {
        // 释放连接
        streamAllocation.release();
        // 抛出异常
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      // 如果重定向的地址和当前的地址一样,则不需要释放连接
      if (!engine.sameConnection(followUp.url())) {
        streamAllocation.release();
        streamAllocation = null;
      }

      // 使用重定向后的请求,重新实例一个引擎,开始下一次循环
      request = followUp;
      engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,
          response);
    }
  }

总结



RealCall的作用一句话概括——发送请求。其中细节有拦截器的建立过程,异步回调的调用,HttpEngine的使用过程。

HttpEngine的代码没有往里Step in,本系列以后的文章会有分析。

欢迎交流 QQ:2424334647

时间: 2024-10-27 05:20:19

One Step By One Step 解析OkHttp3 - RealCall (二)的相关文章

Step Detector and Step Counter Sensors on Android

Step Detector and Step Counter Sensors on Android 时间 2014-03-31 11:56:00 Tech Droid 原文  http://techdroid.kbeanie.com/2014/03/step-detector-and-step-counter-sensors.html Android KitKat has added a few more hardware sensors to it's API list. Step Senso

使用java解析和制作二维码

项目结构 文件源码 QR.zip 第一步:导入zxing的两个架包 core.jar和javase.jar 第二步:使用工具类 MatrixToImageWriter.java 1 package util; 2 import com.google.zxing.common.BitMatrix; 3 import javax.imageio.ImageIO; 4 import java.io.File; 5 import java.io.OutputStream; 6 import java.i

【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源热更新 Android热更新开源项目Tinker源码解析系类之三:so热更新 转载请标明本文来源:http://www.cnblogs

step over、step into、step into my code、step out、run to cursor

红 step over 跳过子函数 黄 step into 进入子函数 蓝 step into my code 不执行源码的子函数执行自己的 黑 step out 跳出当前函数 绿 run to cursor 执行到光标处 原文地址:https://www.cnblogs.com/jiangxue2019/p/11966928.html

Intellij IDEA调试功能使用总结(step over / step into / force step into/step out等)

1.设置断点 选定要设置断点的代码行,在行号的区域后面单击鼠标左键即可. 2.开启调试会话 点击红色箭头指向的小虫子,开始进入调试. IDE下方出现Debug视图,红色的箭头指向的是现在调试程序停留的代码行,方法f2()中,程序的第11行.红色箭头悬停的区域是程序的方法调用栈区.在这个区域中显示了程序执行到断点处所调用过的所用方法,越下面的方法被调用的越早. 3.单步调试 3.1 step over 点击红色箭头指向的按钮,程序向下执行一行(如果当前行有方法调用,这个方法将被执行完毕返回,然后到

Android 10大开源常用框架源码解析 系列 (二)网络框架之一 OkHttp杂题

1.Android基础网络编程:socket.HttpClient.HttpURLConnection     1.1 Socket 定义 是一个对TCP/IP协议进行封装的编程调用接口,本身不是一种协议是接口Api!!     成堆出现,一对套接字:包括ip地址和端口号   基于应用层和传输层抽象出来的一个层.App可以通过该层发送.接收数据,并通过Socket将App添加到网络当中 简单来说就是应用与外部通信的端口,提供了两端数据的传输的通道     1.2 Socket通信模型 基于TCP

json-lib解析json之二维JSONArray

在接口测试中,不管是接口的请求报文还是接口的响应报文,数据全部使用json,在工作中避免不了去解析响应报文以获取某个键的值,解析josn有两种方式,一种是利用jackson,还有一种就是利用json-lib,本例子用的是josn-lib,所使用的jar包是json-lib-2.2.3-jdk15.jar 下面我们来解析这个josn,获取id的值,number是一个二维JSONArray 首先要导入几个jar包,如图所示 直接上代码

网络爬虫模拟登陆获取数据并解析实战(二)

目录 分析要获取的数据 程序的结构 构建封装数据的model 模拟登陆程序并解析数据 结果展示 分析要获取的数据 下面继续实战,写一个模拟登陆获取汽车之家,用户信息的程序.如果大家对模拟登陆获取数据不太了解,建议看完http://blog.csdn.net/qy20115549/article/details/52249232,我写的这篇含有抓包获取人人网数据的案例程序,研究透之后,再来看这个要轻松很多. 首先,大家打开汽车之家这个网站(http://i.autohome.com.cn/7741

java zxing实现二维码生成和解析zxing实现二维码生成和解析

zxing实现二维码生成和解析 二维码 zxing 二维码的生成与解析.有多种途径.我选择用大品牌,google老大的zxing. gitHub链接是(我用的3.0.0,已经是nio了) https://github.com/zxing/zxing/tree/zxing-3.0.0 Java代码   // 其中输出图像和读取图像的类在core包 MultiFormatReader MultiFormatWriter // 生成矩阵的类在javase的包里 MatrixToImageWriter