okhttp 源码学习(二)基本流程

本节将对okhttp中的关键的类和方法进行简单介绍,并梳理出执行的基本流程进行,让大家对源码有一个全局的认识。

Class Call

回顾我们上节中出现的Call对象,这个Call是什么呢?

Call对象其实就是一个准备好执行网络请求的request,是对request进行了封装,支持取消。由于一个Call对象表示的是请求和响应流,因此同一个Call不能执行两次。

Class Dispatcher

在讲Dispatcher之前我们还是来回顾一下如何我们在执行同步或异步请求,分别是执行了Call的execute()和enqueue()方法。下面我们就来看一下这两个方法的源码。

首先,我们来看一下execute():

@Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  eventListener.callStart(this);
  try {
    client.dispatcher().executed(this);
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } catch (IOException e) {
    eventListener.callFailed(this, e);
    throw e;
  } finally {
    client.dispatcher().finished(this);
  }
}

其中client.dispatcher()返回就是Dispatcher对象,跟踪源码我们发现这个Dispatcher其实是client的成员变量。接下来我们再看看enqueue():

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

这里同样使用了client.dispatcher(),看到这里大家就知道了,同步和异步请求其实都是通过这个Dispatcher来完成的。那么我们就来了解一下Dispatcher是什么?

Dispatcher负责okhttp的任务调度,管理同步/异步的请求状态,并维护一个线程池,用于执行请求,是Okhttp的核心类。

Method getResponseWithInterceptorChain()

接下来我将带大家了解一下这个关键的方法,顾名思义,这个方法是用于获取请求的Response的。
回到上面同步请求execute()方法中,我们可以发现该方法内部就是通过调用这个getResponseWithInterceptorChain()来获取请求的Response。其实异步请求enqueue()方法最终也是通过调用这个方法来获取请求的Response的。这里我带大家了解一下enqueue()方法如何调用这个方法的。
回到enqueue()方法中,我们可以发现异步请求是通过client.dispatcher().enqueue(new AsyncCall(responseCallback))来实现的。我们先看一下enqueue()方法中参数new AsyncCall(responseCallback)的源码。

 final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

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

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }

我们发现这个AsyncCall内部类其实是一个Runnable。我们再来看一下Dispatcher的enqueue()方法的源码。

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

我们发现executorService().execute(call)其实就是通过Dispatcher中的线程池来执行这个AsyncCall Runnable的。这样我们再回过头来看看这个AsyncCallexecute ()方法,其实就是在这里调用了getResponseWithInterceptorChain()来获取请求的Response。
讲到这里,大家是不是开始对这个getResponseWithInterceptorChain()方法十分好奇了,这个方法是如何获取请求的Response。当然我们还是一起来看一下源码。

  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, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    return chain.proceed(originalRequest);
  }

这段代码大家看起来可能会很奇怪,这里怎么出现这么多interceptor,大伙也没看到请求,为啥这里就直接返回了response了。其实,这里采用了责任链模式,来实现最终的请求response。具体如何实现的我会在后面的文章中给大家一一说明,这里就不展开了说。

Interceptor

Interceptor也是okhttp的核心之一,它把实际的网络重试、重定向、缓存、透明压缩、网络连接等功能拆分到独立的interceptor中,再通过Interceptor.Chain将这些功能环环相扣起来,最终完美实现网络请求。
我将在接下来的文章中讲述以上方法中的5个重要的拦截器interceptor:

  • RetryAndFollowUpInterceptor: 主要用于重试和重定向请求
  • CacheInterceptor: 处理缓存拦截器
  • BridgeInterceptor: 负责okhttp请求和响应对象与实际http协议中请求和响应对象之间的转换。同时处理cookies相关内容
  • ConnectionInterceptor: 负责建立连接和流对象
  • CallServerInterceptor: 负责完成最终的网络请求,发送请求和读取响应

基本流程图

本节最后放一张流程图,将以上几个重要的概念串联起来,便于大家从全局来认识一下okhttp。


okhttp基本流程图

在接下来的章节中,我将结合以上的基本流程图带着大家深入分析源码。
上一节 okhttp 源码学习(一)基本用法
下一节 okhttp 源码学习(三)Dispatcher 深入解析

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

推荐阅读更多精彩内容