OKHttp全解析系列(三) -- OkHttp 请求流程介绍

通过前两篇文章,大家对HTTP的许多基础知识及OkHttp的使用及类结构已经有了解,这篇主要讲一下OKHttp的整个请求调用流程以及这中间涉及到的一些设计模式,过程中会忽略掉一些局部比较细节的问题,这些忽略的部分会在后面的文章中继续进行深入讲解。

发起请求

我们先看一下OkHttp发起请求的简单形式:

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
        .url(url)
        .build();
Call call = client.newCall(request);
//同步请求
Response response = call.execute();

//异步请求
call.enqueue(new Callback() {
      @Override
      public void onFailure(Call call, IOException e) {
        //请求失败
      }

      @Override
      public void onResponse(Call call, Response response) throws IOException {
        //请求成功
      }
    });

如上代码概括为4步:
1 创建OkHttpClient实例
2 创建Request实例
3 用client实例的newCall方法创建一个Call实例
4 调用Call的同步或异步请求方法,得到Response实例
对外部使用来说,这就完成了一次请求。
其中真正发起网络请求的,是第4步,即调用Call的execute或enqueue方法。

Call & RealCall

先看一下上面请求流程的第3步,即OkHttpClient的newCall方法:

@Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }

可以看到这个方法返回的Call实际上是一个RealCall对象,Call是个接口,RealCall是它唯一的实现子类。
请求流程第4步调用的即RealCall的execute/enqueue方法,先看execute方法的关键代码(enqueue方法相当于在异步线程调用execute方法,原理是一样的):

  @Override public Response execute() throws IOException {
      ...
    try {
      //将这个请求交给dispatcher管理
      client.dispatcher().executed(this);
      //真正请求开始
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

其中比较关键的是这两句:
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
第一句将这次请求加入到分发器的管理之中,分发器即Dispatcher类,这个类中有几个保存请求的队列,包括等待队列、同步执行队列、异步执行队列,我们发出的每个请求都会添加到相应的队列中,我们可以在请求流程中对每个请求进行状态查询或取消等操作。后续文章会对线程池与消息队列这块做详细介绍。
第二句是真正的发起请求,我们看到调用了getResponseWithInterceptorChain方法,返回值是Response,说明调用完这个方法,我们就获得了服务器发回的请求结果。下面详细介绍这个方法。

责任链和拦截器

getResponseWithInterceptorChain方法源码如下:

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    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, this, eventListener);
    return chain.proceed(originalRequest);
  }
}

从源码看,这个方法很简单,就是在一个列表(interceptors )里添加一系列的拦截器(xxxInterceptor),然后创建了一个RealInterceptorChain对象,在这个对象的构造方法传入上面收集的拦截器们,并调用这个对象的proceed方法,整个方法就走完了。但这个方法在整个请求流程中是非常重要的,而且这里还涉及一个责任链模式。

先说一下责任链模式,GOF是这样定义的:

使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。

可以简单理解为,当有一个请求需要处理,这时候有一条处理链,链上每个环都有可能处理这个请求,但是最终是哪些环参与了处理,是不一定的,只知道最终这个请求会被处理。因为最终会被哪些环节处理以及顺序是怎样的都不一定,所以动态性很强。

对应到OkHttp,我们发出一个请求,这个请求也会经历一系列的处理环节,上面代码中添加的每个拦截器都是责任链的一环,会根据每次请求的实际情况由不同的环节处理,比如一个请求是使用缓存的,恰好我们有可用的缓存,那在CacheInterceptor这一环就会返回结果,处在这一环下游的环节就不会走到了。

源码中责任链模式的实现主要是在RealInterceptorChain的proceed方法,下面是这个方法的主要代码:

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

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

   ...

    return response;
  }

代码首先创建一个RealInterceptorChain对象,然后从interceptors中拿出第index个拦截器,接着调用这个拦截器的intercept方法,传入刚生成的RealInterceptorChain对象。这里有关责任链比较重要的是RealInterceptorChain构造函数的两个参数:interceptors 这是我们在外部收集的所有拦截器;index 指定目前这一环使用第几个拦截器进行处理。在proceed方法中每次创建这个对象index都会+1,这样就会一个个取出拦截器,并调拦截器的intercept方法,intercept方法中如果不返回结果,那还会继续调用RealInterceptorChain的proceed方法,这样整个链条就动起来了。

责任链

有了责任链这个架构之后,我们再看链中实际起作用的拦截器们。

添加自定义拦截器:

interceptors.addAll(client.interceptors());

连接器列表首先收集了我们在client中调用addInterceptors方法设置的拦截器,并且放在了最开始的位置,除了拦截器本身的功能,这个位置也很重要,放在最开始,表示当一个请求送过来处理,首先就会交给这个拦截器,而且结果返回的最后一环也是这里,所以这个位置的拦截器可以处理最开始的请求和最后返回结果的一些东西。其他的拦截器也是同理,所处的位置不同,在处理的流程上就会有不同作用,这个顺序实际上限制了拦截器的职能范围,有些事只能在某些位置才能处理,这些顺序是不能错乱的。比如一个银行的办事流程,不可能把行长放在第一环,第一环可能是某办事员,那很明显办事员的职责范围是有限的,行长放在最后一环,但是职责也是有限的,如果一个办事员把客户的需求都办好了,这样的客户行长是接触不到的。

添加RetryAndFollowUpInterceptor:

interceptors.add(retryAndFollowUpInterceptor);

从这个名字可见,这个拦截器负责两部分功能,retry和followup。
retry即重试,当一个请求进来,它会首先扔给后面的环节处理,当它下游的环节都搞不定,请求失败并抛出异常,这时候由这一环负责决定是否再来一次,判断是否重试的条件主要在RetryAndFollowUpInterceptor类的recover方法,细节可以参考源码。
followup功能在RetryAndFollowUpInterceptor的followup方法中,主要是检查response的返回码,根据返回码采取相应措施,比如代理验证、重定向、请求超时等。

添加桥接拦截器

interceptors.add(new BridgeInterceptor(client.cookieJar()))

桥接拦截器主要功能是把便于用户识别和设置的请求信息转换成网络标准信息,网络返回结果后,再把网络返回的信息转变成用户便于识别的应用信息。

添加缓存拦截器

interceptors.add(new CacheInterceptor(client.internalCache()))

缓存拦截器处理请求中缓存相关逻辑,主要是请求前查找本地缓存及相应后将结果保存到缓存,OkHttp使用标准的Http缓存策略,具体详情后面会有文章详细介绍。

添加链接拦截器

interceptors.add(new ConnectInterceptor(client))

顾名思义,次拦截器为请求建立一个网络链接,流程比较复杂,后面会有文章单独讲解。

添加用户调用setNetworkInterceptors设置的拦截器

interceptors.addAll(client.networkInterceptors())

用户可以调用client的addNetworkInterceptors方法添加拦截器,这部分拦截器和addInterceptors方法添加的拦截器只有位置上的不同,前面说过拦截器的位置的重要性,所以OkHttp在不同位置为用户留下了添加拦截器的入口,方便用户更灵活的实现自己的功能。

添加最终对链接进行读写的拦截器

interceptors.add(new CallServerInterceptor(forWebSocket))

这个拦截器具体实现链接建立后读写请求和响应内容的功能,具体细节这里也不再详述。

一个请求通过如上责任链一环一环的处理后,会返回Response,则整个请求就完成了。

整个责任链模式及模式中涉及的各个拦截器组成了整个OkHttp框架的重要部分,如上串讲了从一个请求的创建到各个环节调用及处理的大致过程,其中涉及细节较多,没有详述的,可以参考后续文章。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 通过前两篇文章,大家对HTTP的许多基础知识及OkHttp的使用及类结构已经有了解,这篇主要讲一下OKHttp的整...
    lbhl阅读 672评论 0 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,057评论 25 707
  • 前言:对于OkHttp我接触的时间其实不太长,一直都是使用Retrofit + OkHttp 来做网络请求的,但是...
    mecury阅读 40,950评论 23 178
  • 上小学的时候,很羡慕那个可以把字写得像大人一样的女生,有点龙飞凤舞的感觉。那时候,我爸让我练楷书,让我把字写得尽量...
    松塔阅读 485评论 0 0
  • 没有什么奇技淫巧,只有投入后的答疑解惑,这就是老师存在的意义。 悟已往之不谏,知来者之可追。实迷途其未远,觉今是而...
    今天我长这样阅读 194评论 0 0