业余时间把源码clone下来大致溜了一遍,并且也参阅了其余大神的博客,在这里把自己的心得记录下来共享之,如有不当的地方欢迎批评指正。本文是Okttp源码解析系列的第一篇,不会深入写太多的东西,本篇只是从代码流程上简单的做个梳理,算是为后面的系列博文做个铺垫。

网络请求其实笼统的说一句废话就是发送请求(Request)并接受响应(Response)的过程,当然内部细节在此处不深究

Okhttp完成一个网络请求无非也是这样,一行代码就可以概括(这里先分析同步调用):

  1. Response response = new OkHttpClient().newCall(request).execute();

所以沿着这段代码的调用过程,抽丝剥茧,很容易得出Okhttp的执行流程,newCall()返回的是一个RealCall对象,该对象的execute()方法如下:

  1. public Call newCall(Request request) {
  2. return new RealCall(this, request, false /* for web socket */);
  3. }
  4. public Response execute() throws IOException {
  5. //省略部分与本文无关的代码
  6. try {
  7. //把当前的RealCall对象交给dispatcher保存
  8. client.dispatcher().executed(this);
  9. //调用getResponseWithInterceptorChain()来返回结果
  10. Response result = getResponseWithInterceptorChain();
  11. if (result == null) throw new IOException("Canceled");
  12. return result;
  13. } finally {
  14. client.dispatcher().finished(this);
  15. }
  16. }

很简单上面的两个方法也就做了如下几点功能

1)需要事先组织Request请求对象,该对象包含了请求的url,请求体等内容

2) 将Request对象交给Call对象(RealCall)

3) 执行RealCall对象的execute方法,该方最终返回服务器的响应内容Response对象。

该Response对象是通过getResponseWithInterceptorChain()方法返回而来的!下面就着重看看这个方法都做了些神马功能!

  1. final Request originalRequest;//通过newCall方法传过来
  2. Response getResponseWithInterceptorChain() throws IOException {
  3. List<Interceptor> interceptors = new ArrayList<>();
  4. //获取用户自定义的拦截器对象:也可以不设置
  5. interceptors.addAll(client.interceptors());
  6. //以下拦截器是Okhttp内置的拦截器
  7. interceptors.add(retryAndFollowUpInterceptor);
  8. interceptors.add(new BridgeInterceptor(client.cookieJar()));
  9. interceptors.add(new CacheInterceptor(client.internalCache()));
  10. interceptors.add(new ConnectInterceptor(client));
  11. if (!forWebSocket) {
  12. interceptors.addAll(client.networkInterceptors());
  13. }
  14. interceptors.add(new CallServerInterceptor(forWebSocket));
  15. //将拦截器集合交给RealInterceptorChain这个Chain对象来处理
  16. Interceptor.Chain chain = new RealInterceptorChain(
  17. interceptors, null, null, null, 0, originalRequest);
  18. //执行
  19. return chain.proceed(originalRequest);
  20. }

这个方法从代码上来看也很简单,就是将客户端自定义的拦截器(此处先不管Interceptor对象是来干什么的,先当做普通的Java对象类看待)和Okhttp内置的拦截器放到一个List集合interceptors里面,然后跟最初构建的Request对象一块创建了RealInterceptorChain对象,RealInterceptorChain是Chain接口的实现类。这样就把

一系列拦截器组合成一个链跟请求绑定起来,最终调用RealInterceptorChain的proceed来返回一个Response;

简单用图来表示一下此时这个链的关系,可以如下表示:

那么怎么样让 这些拦截器对象逐一运行呢?getResponseWithInterceptorChain方法的最后调用了RealInterceptorChain的proceed方法,该方法直接调用了该对象的重载方法:

  1. //正式开始调用拦截器工作
  2. public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection){
  3. //省略部分与本文无关的代码
  4. // 调用链中的下一个拦截器
  5. RealInterceptorChain next = new RealInterceptorChain(
  6. interceptors, streamAllocation, httpCodec, connection, index + 1, request);
  7. Interceptor interceptor = interceptors.get(index);
  8. Response response = interceptor.intercept(next);
  9. //确保每个拦截器都调用了proceed方法()
  10. if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
  11. throw new IllegalStateException("network interceptor " + interceptor
  12. + " must call proceed() exactly once");
  13. }
  14. //省略部分与本文无关的代码
  15. return response;
  16. }

剔除了与本文无关紧要的代码之后,上面的代码意思也很明显,在总结上面的方法之前看看RealInterceptorChain的构造器是都做了什么初始化工作

  1. //拦截器当前索引
  2. private final int index;
  3. private final Request request;
  4. public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
  5. HttpCodec httpCodec, RealConnection connection, int index, Request request) {
  6. this.interceptors = interceptors;
  7. //省略部分与本文无关的代码
  8. this.index = index;//0
  9. this.request = request;
  10. }

通过其构造函数可以RealInterceptorChain有一个index变量获取拦截器列表中对应位置的拦截器对象,但是procced方法并没有用for循环来遍历interceptors集合,而是重新new 一个RealInterceptorChain对象,且新对象的index在原来RealInterceptorChain对象index之上进行index+1,并把新的拦截器链对象RealInterceptorChain交给当前拦截器Interceptor 的intercept方法:

  1. //生成新的拦截器链对象
  2. RealInterceptorChain next = new RealInterceptorChain(
  3. interceptors, streamAllocation, httpCodec, connection, index + 1, request);
  4. Interceptor interceptor = interceptors.get(index);
  5. //开始执行拦截:将新的next对象交给interceptor处理
  6. Response response = interceptor.intercept(next);

抛却自定义的拦截器对象先不谈,上面的表述可以用下图来表示:

既然Interceptor的intercept方法接受的是一个新RealInterceptorChain对象,通过上面的代码来看目前index只能达到index=1,那么剩下的CacheIntercept对象是如何调用的呢?其实也可以大胆的猜测一下:

既然拦截器组成了一个链,那么肯定是 第一个内置拦截器RetryAndFollowUpInterceptor对象接受的Chain对象在intercept方法里面继续对新的Chain做了遍历下一个拦截器的操作!

所以大致看下RetryAndFollowUpInterceptor对象的intercept方法都做了神马:

  1. Response intercept(Chain chain) throws IOException {
  2. Request request = chain.request();
  3. //省略部分与本文无关的代码
  4. Response priorResponse = null;
  5. while (true) {//死循环
  6. //省略部分与本文无关的代码
  7. //上一个拦截器返回的响应对象
  8. Response response = null;
  9. //省略部分与本文无关的代码
  10. //继续执行下一个chain的下一个
  11. response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
  12. //省略部分与本文无关的代码
  13. Request followUp = followUpRequest(response);
  14. if (followUp == null) {
  15. //返回return
  16. return response;
  17. }
  18. //省略部分与本文无关的代码
  19. priorResponse = response;
  20. }//end while
  21. }

可以发现RetryAndFollowUpInterceptor对象的intercept有一个while(true)的循环,在循环里面有一行很重要的代码:

  1. response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);

看!上面的也跟getResponseWithInterceptorChain()一样调用了proceed方法,根据上面的的讲解:调用Chain的proceed方法的时候会新生成一个RealInterceptorChain,其index的值是上一个拦截器所持有的Chain的index+1,这样就确保拦截器链逐条运行。查看BridgeInterceptor、CacheInterceptor等Okhttp内置拦截器就可以印证这一点:在它们intercept的内部都调用了chain.proceed()方法,且每次调用都在会创建一个RealInterceptorChain对象(当然最后一个拦截器CallServerInterceptor除外)!所以拦截器的工作流方式可以用如下图来表示:



如果从发起请求Request到得到响应Response的过程,加上拦截器链的话工作在内,其i整体执行过程就是如下了:

这也是为什么RealInterceptorChain的procced方法中有如下的异常抛出,目的就是为了让拦截器链一个个执行下去,确保整个请求过程的完成:

  1. if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
  2. throw new IllegalStateException("network interceptor " + interceptor
  3. + " must call proceed() exactly once");
  4. }

也即是说自定义的拦截器Interceptor必须调Chain的proceed一次(可多次调用),那么到此为止一个完 整的Okttp(同步)请求的流程就已经完成,那么就简单的用一个流程图来总结本文的说明吧:

Okhttp源码简单解析(一)的更多相关文章

  1. node-pre-gyp以及node-gyp的源码简单解析(以安装sqlite3为例)

    title: node-pre-gyp以及node-gyp的源码简单解析(以安装sqlite3为例) date: 2020-11-27 tags: node native sqlite3 前言 简单来 ...

  2. springmvc(2)Controller源码简单解析

    前面简单的分析了一下DispatcherServlet,接下来分析一下Controller,在web的MVC中,Controller就是其中的C,启动的一些页面逻辑处理,页面映射的功能: 首先看看超类 ...

  3. ForkJoinPool源码简单解析

    ForkJoin框架之ForkJoinTask  java  阅读约 62 分钟 前言 在前面的文章"CompletableFuture和响应式编程"中提到了ForkJoinTas ...

  4. StringBudiler源码简单解析

    StringBudiler源码 继承关系树 底层实现 默认容量() 特别的添加方法(append) 1.继承关系树 继承自AbstractStringBuilder与StringBuffer同族 2. ...

  5. springmvc(1)DispatcherServlet源码简单解析

    springmvc的简单配置 1.首先需要在web.xml中配置DispatcherServlet,这个类是springmvc的核心类,所以的操作都是由这里开始,并且大部分都是在这里面实现的,比如各种 ...

  6. OKHttp源码解析

    http://frodoking.github.io/2015/03/12/android-okhttp/ Android为我们提供了两种HTTP交互的方式:HttpURLConnection 和 A ...

  7. 【转载】okhttp源码解析

    转自:http://www.open-open.com/lib/view/open1472216742720.html https://blog.piasy.com/2016/07/11/Unders ...

  8. FFmpeg的HEVC解码器源码简单分析:解析器(Parser)部分

    ===================================================== HEVC源码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpeg ...

  9. Retrofit源码设计模式解析(下)

    本文将接着<Retrofit源码设计模式解析(上)>,继续分享以下设计模式在Retrofit中的应用: 适配器模式 策略模式 观察者模式 单例模式 原型模式 享元模式 一.适配器模式 在上 ...

随机推荐

  1. Django框架之单表操作

    一.添加表记录 对于单表有两种方式 # 添加数据的两种方式 # 方式一:实例化对象就是一条表记录 Frank_obj = models.Student(name ="海东",cou ...

  2. Delphi 正则表达式语法(8): 引用子表达式 - 也叫反向引用

    Delphi 正则表达式语法(8): 引用子表达式 - 也叫反向引用 //准备: 我们先写一个搜索所有英文单词的表达式 var   reg: TPerlRegEx; begin   reg := TP ...

  3. PL/SQL编程—变量

    SQL> declare c_tax_rate ,):=0.03; v_name ); v_passwd ); v_sale ,); v_tax_sale ,); begin select na ...

  4. POJ 3463 Sightseeing (次短路)

    题意:求两点之间最短路的数目加上比最短路长度大1的路径数目 分析:可以转化为求最短路和次短路的问题,如果次短路比最短路大1,那么结果就是最短路数目加上次短路数目,否则就不加. 求解次短路的过程也是基于 ...

  5. linux java环境配置

    一.安装 创建安装目录,在/usr/java下建立安装路径,并将文件考到该路径下: # mkdir /usr/java 1.jdk-6u11-linux-i586.bin 这个是自解压的文件,在lin ...

  6. SQL语句2

    1. SELECT * FROM Persons WHERE City NOT LIKE '%lon%' 2. SELECT * FROM Persons WHERE FirstName LIKE ' ...

  7. nginx ip无法访问

    CentOS 7.0默认使用的是firewall作为防火墙,这里改为iptables防火墙步骤. 1.关闭firewall: systemctl stop firewalld.service #停止f ...

  8. Python面试题之Python的Super方法

    我们最常见的,可以说几乎唯一能见到的使用super的形式是: class SubClass(BaseClass): def method(self): super(SubClass, self).me ...

  9. 禁止电脑登陆QQ聊天系统的四种方法

    一.使用防火墙禁止端口法 QQ使用的默认端口是 UDP 4000,使用防火墙将该端口关闭,那么别人就不能使用QQ了,当自己需要上网时只需开放该端口就可以了. 下面以我使用的“金山网镖6”进行说明,点击 ...

  10. Metasploit 内网渗透篇

    0x01 reverse the shell File 通常做法是使用msfpayload生成一个backdoor.exe然后上传到目标机器执行.本地监听即可获得meterpreter shell. ...